Skip to main content

solana_nft_token_metadata/
utils.rs

1use crate::{
2    assertions::{collection::assert_collection_update_is_valid, uses::assert_valid_use},
3    error::MetadataError,
4    state::{
5        get_reservation_list, Data, DataV2, EditionMarker, Key, MasterEditionV1, Metadata,
6        TokenStandard, Uses, EDITION, EDITION_MARKER_BIT_SIZE, MAX_CREATOR_LIMIT, MAX_EDITION_LEN,
7        MAX_EDITION_MARKER_SIZE, MAX_MASTER_EDITION_LEN, MAX_METADATA_LEN, MAX_NAME_LENGTH,
8        MAX_SYMBOL_LENGTH, MAX_URI_LENGTH, PREFIX,
9    },
10};
11use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
12use borsh::{BorshDeserialize, BorshSerialize};
13use solana_program::{
14    account_info::AccountInfo,
15    borsh::try_from_slice_unchecked,
16    entrypoint::ProgramResult,
17    msg,
18    program::{invoke, invoke_signed},
19    program_error::ProgramError,
20    program_option::COption,
21    program_pack::{IsInitialized, Pack},
22    pubkey::Pubkey,
23    system_instruction,
24    sysvar::{rent::Rent, Sysvar},
25};
26use spl_token::{
27    instruction::{set_authority, AuthorityType},
28    state::{Account, Mint},
29};
30use std::convert::TryInto;
31
32pub fn assert_data_valid(
33    data: &Data,
34    update_authority: &Pubkey,
35    existing_metadata: &Metadata,
36    allow_direct_creator_writes: bool,
37    update_authority_is_signer: bool,
38    is_updating: bool,
39) -> ProgramResult {
40    if data.name.len() > MAX_NAME_LENGTH {
41        return Err(MetadataError::NameTooLong.into());
42    }
43
44    if data.symbol.len() > MAX_SYMBOL_LENGTH {
45        return Err(MetadataError::SymbolTooLong.into());
46    }
47
48    if data.uri.len() > MAX_URI_LENGTH {
49        return Err(MetadataError::UriTooLong.into());
50    }
51
52    if data.seller_fee_basis_points > 10000 {
53        return Err(MetadataError::InvalidBasisPoints.into());
54    }
55
56    if data.creators.is_some() {
57        if let Some(creators) = &data.creators {
58            if creators.len() > MAX_CREATOR_LIMIT {
59                return Err(MetadataError::CreatorsTooLong.into());
60            }
61
62            if creators.is_empty() {
63                return Err(MetadataError::CreatorsMustBeAtleastOne.into());
64            } else {
65                let mut found = false;
66                let mut total: u8 = 0;
67                for i in 0..creators.len() {
68                    let creator = &creators[i];
69                    for j in (i + 1)..creators.len() {
70                        if creators[j].address == creator.address {
71                            return Err(MetadataError::DuplicateCreatorAddress.into());
72                        }
73                    }
74
75                    total = total
76                        .checked_add(creator.share)
77                        .ok_or(MetadataError::NumericalOverflowError)?;
78
79                    if creator.address == *update_authority {
80                        found = true;
81                    }
82
83                    // Dont allow metadata owner to unilaterally say a creator verified...
84                    // cross check with array, only let them say verified=true here if
85                    // it already was true and in the array.
86                    // Conversely, dont let a verified creator be wiped.
87                    if (!update_authority_is_signer || creator.address != *update_authority)
88                        && !allow_direct_creator_writes
89                    {
90                        if let Some(existing_creators) = &existing_metadata.data.creators {
91                            match existing_creators
92                                .iter()
93                                .find(|c| c.address == creator.address)
94                            {
95                                Some(existing_creator) => {
96                                    if creator.verified && !existing_creator.verified {
97                                        return Err(
98                                            MetadataError::CannotVerifyAnotherCreator.into()
99                                        );
100                                    } else if !creator.verified && existing_creator.verified {
101                                        return Err(
102                                            MetadataError::CannotUnverifyAnotherCreator.into()
103                                        );
104                                    }
105                                }
106                                None => {
107                                    if creator.verified {
108                                        return Err(
109                                            MetadataError::CannotVerifyAnotherCreator.into()
110                                        );
111                                    }
112                                }
113                            }
114                        } else {
115                            if creator.verified {
116                                return Err(MetadataError::CannotVerifyAnotherCreator.into());
117                            }
118                        }
119                    }
120                }
121
122                if !found && !allow_direct_creator_writes && !is_updating {
123                    return Err(MetadataError::MustBeOneOfCreators.into());
124                }
125                if total != 100 {
126                    return Err(MetadataError::ShareTotalMustBe100.into());
127                }
128            }
129        }
130    }
131
132    Ok(())
133}
134
135/// assert initialized account
136pub fn assert_initialized<T: Pack + IsInitialized>(
137    account_info: &AccountInfo,
138) -> Result<T, ProgramError> {
139    let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
140    if !account.is_initialized() {
141        Err(MetadataError::Uninitialized.into())
142    } else {
143        Ok(account)
144    }
145}
146
147/// Create account almost from scratch, lifted from
148/// https://github.com/solana-labs/solana-program-library/tree/master/associated-token-account/program/src/processor.rs#L51-L98
149#[inline(always)]
150pub fn create_or_allocate_account_raw<'a>(
151    program_id: Pubkey,
152    new_account_info: &AccountInfo<'a>,
153    rent_sysvar_info: &AccountInfo<'a>,
154    system_program_info: &AccountInfo<'a>,
155    payer_info: &AccountInfo<'a>,
156    size: usize,
157    signer_seeds: &[&[u8]],
158) -> ProgramResult {
159    let rent = &Rent::from_account_info(rent_sysvar_info)?;
160    let required_lamports = rent
161        .minimum_balance(size)
162        .max(1)
163        .saturating_sub(new_account_info.lamports());
164
165    if required_lamports > 0 {
166        msg!("Transfer {} lamports to the new account", required_lamports);
167        invoke(
168            &system_instruction::transfer(&payer_info.key, new_account_info.key, required_lamports),
169            &[
170                payer_info.clone(),
171                new_account_info.clone(),
172                system_program_info.clone(),
173            ],
174        )?;
175    }
176
177    let accounts = &[new_account_info.clone(), system_program_info.clone()];
178
179    msg!("Allocate space for the account");
180    invoke_signed(
181        &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()),
182        accounts,
183        &[&signer_seeds],
184    )?;
185
186    msg!("Assign the account to the owning program");
187    invoke_signed(
188        &system_instruction::assign(new_account_info.key, &program_id),
189        accounts,
190        &[&signer_seeds],
191    )?;
192
193    Ok(())
194}
195
196pub fn assert_update_authority_is_correct(
197    metadata: &Metadata,
198    update_authority_info: &AccountInfo,
199) -> ProgramResult {
200    if metadata.update_authority != *update_authority_info.key {
201        return Err(MetadataError::UpdateAuthorityIncorrect.into());
202    }
203
204    if !update_authority_info.is_signer {
205        return Err(MetadataError::UpdateAuthorityIsNotSigner.into());
206    }
207
208    Ok(())
209}
210
211/// Unpacks COption from a slice, taken from token program
212fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
213    let (tag, body) = array_refs![src, 4, 32];
214    match *tag {
215        [0, 0, 0, 0] => Ok(COption::None),
216        [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
217        _ => Err(ProgramError::InvalidAccountData),
218    }
219}
220
221/// Cheap method to just grab owner Pubkey from token account, instead of deserializing entire thing
222pub fn get_owner_from_token_account(
223    token_account_info: &AccountInfo,
224) -> Result<Pubkey, ProgramError> {
225    // TokeAccount layout:   mint(32), owner(32), ...
226    let data = token_account_info.try_borrow_data()?;
227    let owner_data = array_ref![data, 32, 32];
228    Ok(Pubkey::new_from_array(*owner_data))
229}
230
231pub fn get_mint_authority(account_info: &AccountInfo) -> Result<COption<Pubkey>, ProgramError> {
232    // In token program, 36, 8, 1, 1 is the layout, where the first 36 is mint_authority
233    // so we start at 0.
234    let data = account_info.try_borrow_data().unwrap();
235    let authority_bytes = array_ref![data, 0, 36];
236
237    Ok(unpack_coption_key(&authority_bytes)?)
238}
239
240pub fn get_mint_freeze_authority(
241    account_info: &AccountInfo,
242) -> Result<COption<Pubkey>, ProgramError> {
243    let data = account_info.try_borrow_data().unwrap();
244    let authority_bytes = array_ref![data, 36 + 8 + 1 + 1, 36];
245
246    Ok(unpack_coption_key(&authority_bytes)?)
247}
248
249/// cheap method to just get supply off a mint without unpacking whole object
250pub fn get_mint_supply(account_info: &AccountInfo) -> Result<u64, ProgramError> {
251    // In token program, 36, 8, 1, 1 is the layout, where the first 8 is supply u64.
252    // so we start at 36.
253    let data = account_info.try_borrow_data().unwrap();
254    let bytes = array_ref![data, 36, 8];
255
256    Ok(u64::from_le_bytes(*bytes))
257}
258
259/// cheap method to just get supply off a mint without unpacking whole object
260pub fn get_mint_decimals(account_info: &AccountInfo) -> Result<u8, ProgramError> {
261    // In token program, 36, 8, 1, 1, is the layout, where the first 1 is decimals u8.
262    // so we start at 36.
263    let data = account_info.try_borrow_data().unwrap();
264    Ok(data[44])
265}
266
267pub fn assert_mint_authority_matches_mint(
268    mint_authority: &COption<Pubkey>,
269    mint_authority_info: &AccountInfo,
270) -> ProgramResult {
271    match mint_authority {
272        COption::None => {
273            return Err(MetadataError::InvalidMintAuthority.into());
274        }
275        COption::Some(key) => {
276            if mint_authority_info.key != key {
277                return Err(MetadataError::InvalidMintAuthority.into());
278            }
279        }
280    }
281
282    if !mint_authority_info.is_signer {
283        return Err(MetadataError::NotMintAuthority.into());
284    }
285
286    Ok(())
287}
288
289pub fn assert_supply_invariance(
290    master_edition: &MasterEditionV1,
291    printing_mint: &Mint,
292    new_supply: u64,
293) -> ProgramResult {
294    // The supply of printed tokens and the supply of the master edition should, when added, never exceed max supply.
295    // Every time a printed token is burned, master edition.supply goes up by 1.
296    if let Some(max_supply) = master_edition.max_supply {
297        let current_supply = printing_mint
298            .supply
299            .checked_add(master_edition.supply)
300            .ok_or(MetadataError::NumericalOverflowError)?;
301        let new_proposed_supply = current_supply
302            .checked_add(new_supply)
303            .ok_or(MetadataError::NumericalOverflowError)?;
304        if new_proposed_supply > max_supply {
305            return Err(MetadataError::PrintingWouldBreachMaximumSupply.into());
306        }
307    }
308
309    Ok(())
310}
311
312pub fn transfer_mint_authority<'a>(
313    edition_key: &Pubkey,
314    edition_account_info: &AccountInfo<'a>,
315    mint_info: &AccountInfo<'a>,
316    mint_authority_info: &AccountInfo<'a>,
317    token_program_info: &AccountInfo<'a>,
318) -> ProgramResult {
319    msg!("Setting mint authority");
320    let accounts = &[
321        mint_authority_info.clone(),
322        mint_info.clone(),
323        token_program_info.clone(),
324        edition_account_info.clone(),
325    ];
326    invoke_signed(
327        &set_authority(
328            token_program_info.key,
329            mint_info.key,
330            Some(edition_key),
331            AuthorityType::MintTokens,
332            mint_authority_info.key,
333            &[&mint_authority_info.key],
334        )
335        .unwrap(),
336        accounts,
337        &[],
338    )?;
339    msg!("Setting freeze authority");
340    let freeze_authority = get_mint_freeze_authority(mint_info)?;
341    if freeze_authority.is_some() {
342        invoke_signed(
343            &set_authority(
344                token_program_info.key,
345                mint_info.key,
346                Some(&edition_key),
347                AuthorityType::FreezeAccount,
348                mint_authority_info.key,
349                &[&mint_authority_info.key],
350            )
351            .unwrap(),
352            accounts,
353            &[],
354        )?;
355        msg!("Finished setting freeze authority");
356    } else {
357        msg!("Skipping freeze authority because this mint has none")
358    }
359
360    Ok(())
361}
362
363pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
364    if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
365        Err(MetadataError::NotRentExempt.into())
366    } else {
367        Ok(())
368    }
369}
370
371// Todo deprecate this for assert derivation
372pub fn assert_edition_valid(
373    program_id: &Pubkey,
374    mint: &Pubkey,
375    edition_account_info: &AccountInfo,
376) -> ProgramResult {
377    let edition_seeds = &[
378        PREFIX.as_bytes(),
379        program_id.as_ref(),
380        &mint.as_ref(),
381        EDITION.as_bytes(),
382    ];
383    let (edition_key, _) = Pubkey::find_program_address(edition_seeds, program_id);
384    if edition_key != *edition_account_info.key {
385        return Err(MetadataError::InvalidEditionKey.into());
386    }
387
388    Ok(())
389}
390
391pub fn extract_edition_number_from_deprecated_reservation_list(
392    account: &AccountInfo,
393    mint_authority_info: &AccountInfo,
394) -> Result<u64, ProgramError> {
395    let mut reservation_list = get_reservation_list(account)?;
396
397    if let Some(supply_snapshot) = reservation_list.supply_snapshot() {
398        let mut prev_total_offsets: u64 = 0;
399        let mut offset: Option<u64> = None;
400        let mut reservations = reservation_list.reservations();
401        for i in 0..reservations.len() {
402            let mut reservation = &mut reservations[i];
403
404            if reservation.address == *mint_authority_info.key {
405                offset = Some(
406                    prev_total_offsets
407                        .checked_add(reservation.spots_remaining)
408                        .ok_or(MetadataError::NumericalOverflowError)?,
409                );
410                // You get your editions in reverse order but who cares, saves a byte
411                reservation.spots_remaining = reservation
412                    .spots_remaining
413                    .checked_sub(1)
414                    .ok_or(MetadataError::NumericalOverflowError)?;
415
416                reservation_list.set_reservations(reservations)?;
417                reservation_list.save(account)?;
418                break;
419            }
420
421            if reservation.address == solana_program::system_program::id() {
422                // This is an anchor point in the array...it means we reset our math to
423                // this offset because we may be missing information in between this point and
424                // the points before it.
425                prev_total_offsets = reservation.total_spots;
426            } else {
427                prev_total_offsets = prev_total_offsets
428                    .checked_add(reservation.total_spots)
429                    .ok_or(MetadataError::NumericalOverflowError)?;
430            }
431        }
432
433        match offset {
434            Some(val) => Ok(supply_snapshot
435                .checked_add(val)
436                .ok_or(MetadataError::NumericalOverflowError)?),
437            None => {
438                return Err(MetadataError::AddressNotInReservation.into());
439            }
440        }
441    } else {
442        return Err(MetadataError::ReservationNotSet.into());
443    }
444}
445
446pub fn calculate_edition_number(
447    mint_authority_info: &AccountInfo,
448    reservation_list_info: Option<&AccountInfo>,
449    edition_override: Option<u64>,
450    me_supply: u64,
451) -> Result<u64, ProgramError> {
452    let edition = match reservation_list_info {
453        Some(account) => {
454            extract_edition_number_from_deprecated_reservation_list(account, mint_authority_info)?
455        }
456        None => {
457            if let Some(edit) = edition_override {
458                edit
459            } else {
460                me_supply
461                    .checked_add(1)
462                    .ok_or(MetadataError::NumericalOverflowError)?
463            }
464        }
465    };
466
467    Ok(edition)
468}
469
470fn get_max_supply_off_master_edition(
471    master_edition_account_info: &AccountInfo,
472) -> Result<Option<u64>, ProgramError> {
473    let data = master_edition_account_info.try_borrow_data()?;
474    // this is an option, 9 bytes, first is 0 means is none
475    if data[9] == 0 {
476        Ok(None)
477    } else {
478        let amount_data = array_ref![data, 10, 8];
479        Ok(Some(u64::from_le_bytes(*amount_data)))
480    }
481}
482
483pub fn get_supply_off_master_edition(
484    master_edition_account_info: &AccountInfo,
485) -> Result<u64, ProgramError> {
486    let data = master_edition_account_info.try_borrow_data()?;
487    // this is an option, 9 bytes, first is 0 means is none
488
489    let amount_data = array_ref![data, 1, 8];
490    Ok(u64::from_le_bytes(*amount_data))
491}
492
493pub fn calculate_supply_change<'a>(
494    master_edition_account_info: &AccountInfo<'a>,
495    reservation_list_info: Option<&AccountInfo<'a>>,
496    edition_override: Option<u64>,
497    me_supply: u64,
498) -> ProgramResult {
499    if reservation_list_info.is_none() {
500        let new_supply: u64;
501        if let Some(edition) = edition_override {
502            if edition > me_supply {
503                new_supply = edition;
504            } else {
505                new_supply = me_supply
506            }
507        } else {
508            new_supply = me_supply
509                .checked_add(1)
510                .ok_or(MetadataError::NumericalOverflowError)?;
511        }
512
513        if let Some(max) = get_max_supply_off_master_edition(master_edition_account_info)? {
514            if new_supply > max {
515                return Err(MetadataError::MaxEditionsMintedAlready.into());
516            }
517        }
518        // Doing old school serialization to protect CPU credits.
519        let edition_data = &mut master_edition_account_info.data.borrow_mut();
520        let output = array_mut_ref![edition_data, 0, MAX_MASTER_EDITION_LEN];
521
522        let (_key, supply, _the_rest) =
523            mut_array_refs![output, 1, 8, MAX_MASTER_EDITION_LEN - 8 - 1];
524        *supply = new_supply.to_le_bytes();
525    }
526
527    Ok(())
528}
529
530#[allow(clippy::too_many_arguments)]
531pub fn mint_limited_edition<'a>(
532    program_id: &'a Pubkey,
533    master_metadata: Metadata,
534    new_metadata_account_info: &'a AccountInfo<'a>,
535    new_edition_account_info: &'a AccountInfo<'a>,
536    master_edition_account_info: &'a AccountInfo<'a>,
537    mint_info: &'a AccountInfo<'a>,
538    mint_authority_info: &'a AccountInfo<'a>,
539    payer_account_info: &'a AccountInfo<'a>,
540    update_authority_info: &'a AccountInfo<'a>,
541    token_program_account_info: &'a AccountInfo<'a>,
542    system_account_info: &'a AccountInfo<'a>,
543    rent_info: &'a AccountInfo<'a>,
544    // Only present with MasterEditionV1 calls, if present, use edition based off address in res list,
545    // otherwise, pull off the top
546    reservation_list_info: Option<&'a AccountInfo<'a>>,
547    // Only present with MasterEditionV2 calls, if present, means
548    // directing to a specific version, otherwise just pull off the top
549    edition_override: Option<u64>,
550) -> ProgramResult {
551    let me_supply = get_supply_off_master_edition(master_edition_account_info)?;
552    let mint_authority = get_mint_authority(mint_info)?;
553    let mint_supply = get_mint_supply(mint_info)?;
554    assert_mint_authority_matches_mint(&mint_authority, mint_authority_info)?;
555
556    assert_edition_valid(
557        program_id,
558        &master_metadata.mint,
559        master_edition_account_info,
560    )?;
561
562    let edition_seeds = &[
563        PREFIX.as_bytes(),
564        program_id.as_ref(),
565        &mint_info.key.as_ref(),
566        EDITION.as_bytes(),
567    ];
568    let (edition_key, bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
569    if edition_key != *new_edition_account_info.key {
570        return Err(MetadataError::InvalidEditionKey.into());
571    }
572
573    if reservation_list_info.is_some() && edition_override.is_some() {
574        return Err(MetadataError::InvalidOperation.into());
575    }
576
577    calculate_supply_change(
578        master_edition_account_info,
579        reservation_list_info,
580        edition_override,
581        me_supply,
582    )?;
583
584    if mint_supply != 1 {
585        return Err(MetadataError::EditionsMustHaveExactlyOneToken.into());
586    }
587    let master_data = master_metadata.data;
588    // bundle data into v2
589    let data_v2 = DataV2 {
590        name: master_data.name,
591        symbol: master_data.symbol,
592        uri: master_data.uri,
593        seller_fee_basis_points: master_data.seller_fee_basis_points,
594        creators: master_data.creators,
595        collection: master_metadata.collection,
596        uses: master_metadata.uses.map(|u| Uses {
597            use_method: u.use_method,
598            remaining: u.total, // reset remaining uses per edition for extra fun
599            total: u.total,
600        }),
601    };
602    // create the metadata the normal way...
603
604    process_create_metadata_accounts_logic(
605        &program_id,
606        CreateMetadataAccountsLogicArgs {
607            metadata_account_info: new_metadata_account_info,
608            mint_info,
609            mint_authority_info,
610            payer_account_info,
611            update_authority_info,
612            system_account_info,
613            rent_info,
614        },
615        data_v2,
616        true,
617        false,
618        true,
619        true,
620    )?;
621    let edition_authority_seeds = &[
622        PREFIX.as_bytes(),
623        program_id.as_ref(),
624        &mint_info.key.as_ref(),
625        EDITION.as_bytes(),
626        &[bump_seed],
627    ];
628
629    create_or_allocate_account_raw(
630        *program_id,
631        new_edition_account_info,
632        rent_info,
633        system_account_info,
634        payer_account_info,
635        MAX_EDITION_LEN,
636        edition_authority_seeds,
637    )?;
638
639    // Doing old school serialization to protect CPU credits.
640    let edition_data = &mut new_edition_account_info.data.borrow_mut();
641    let output = array_mut_ref![edition_data, 0, MAX_EDITION_LEN];
642
643    let (key, parent, edition, _padding) = mut_array_refs![output, 1, 32, 8, 200];
644
645    *key = [Key::EditionV1 as u8];
646    parent.copy_from_slice(master_edition_account_info.key.as_ref());
647
648    *edition = calculate_edition_number(
649        mint_authority_info,
650        reservation_list_info,
651        edition_override,
652        me_supply,
653    )?
654    .to_le_bytes();
655
656    // Now make sure this mint can never be used by anybody else.
657    transfer_mint_authority(
658        &edition_key,
659        new_edition_account_info,
660        mint_info,
661        mint_authority_info,
662        token_program_account_info,
663    )?;
664
665    Ok(())
666}
667
668pub fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
669    let TokenBurnParams {
670        mint,
671        source,
672        authority,
673        token_program,
674        amount,
675        authority_signer_seeds,
676    } = params;
677    let mut seeds: Vec<&[&[u8]]> = vec![];
678    if let Some(seed) = authority_signer_seeds {
679        seeds.push(seed);
680    }
681    let result = invoke_signed(
682        &spl_token::instruction::burn(
683            token_program.key,
684            source.key,
685            mint.key,
686            authority.key,
687            &[],
688            amount,
689        )?,
690        &[source, mint, authority, token_program],
691        seeds.as_slice(),
692    );
693    result.map_err(|_| MetadataError::TokenBurnFailed.into())
694}
695
696/// TokenBurnParams
697pub struct TokenBurnParams<'a: 'b, 'b> {
698    /// mint
699    pub mint: AccountInfo<'a>,
700    /// source
701    pub source: AccountInfo<'a>,
702    /// amount
703    pub amount: u64,
704    /// authority
705    pub authority: AccountInfo<'a>,
706    /// authority_signer_seeds
707    pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
708    /// token_program
709    pub token_program: AccountInfo<'a>,
710}
711
712pub fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
713    let TokenMintToParams {
714        mint,
715        destination,
716        authority,
717        token_program,
718        amount,
719        authority_signer_seeds,
720    } = params;
721    let mut seeds: Vec<&[&[u8]]> = vec![];
722    if let Some(seed) = authority_signer_seeds {
723        seeds.push(seed);
724    }
725    let result = invoke_signed(
726        &spl_token::instruction::mint_to(
727            token_program.key,
728            mint.key,
729            destination.key,
730            authority.key,
731            &[],
732            amount,
733        )?,
734        &[mint, destination, authority, token_program],
735        seeds.as_slice(),
736    );
737    result.map_err(|_| MetadataError::TokenMintToFailed.into())
738}
739
740/// TokenMintToParams
741pub struct TokenMintToParams<'a: 'b, 'b> {
742    /// mint
743    pub mint: AccountInfo<'a>,
744    /// destination
745    pub destination: AccountInfo<'a>,
746    /// amount
747    pub amount: u64,
748    /// authority
749    pub authority: AccountInfo<'a>,
750    /// authority_signer_seeds
751    pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
752    /// token_program
753    pub token_program: AccountInfo<'a>,
754}
755
756pub fn assert_derivation(
757    program_id: &Pubkey,
758    account: &AccountInfo,
759    path: &[&[u8]],
760) -> Result<u8, ProgramError> {
761    let (key, bump) = Pubkey::find_program_address(&path, program_id);
762    if key != *account.key {
763        return Err(MetadataError::DerivedKeyInvalid.into());
764    }
765    Ok(bump)
766}
767
768pub fn assert_signer(account_info: &AccountInfo) -> ProgramResult {
769    if !account_info.is_signer {
770        Err(ProgramError::MissingRequiredSignature)
771    } else {
772        Ok(())
773    }
774}
775
776pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
777    if account.owner != owner {
778        Err(MetadataError::IncorrectOwner.into())
779    } else {
780        Ok(())
781    }
782}
783
784pub fn assert_token_program_matches_package(token_program_info: &AccountInfo) -> ProgramResult {
785    if *token_program_info.key != spl_token::id() {
786        return Err(MetadataError::InvalidTokenProgram.into());
787    }
788
789    Ok(())
790}
791
792pub fn try_from_slice_checked<T: BorshDeserialize>(
793    data: &[u8],
794    data_type: Key,
795    data_size: usize,
796) -> Result<T, ProgramError> {
797    if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
798        || data.len() != data_size
799    {
800        return Err(MetadataError::DataTypeMismatch.into());
801    }
802
803    let result: T = try_from_slice_unchecked(data)?;
804
805    Ok(result)
806}
807
808pub struct CreateMetadataAccountsLogicArgs<'a> {
809    pub metadata_account_info: &'a AccountInfo<'a>,
810    pub mint_info: &'a AccountInfo<'a>,
811    pub mint_authority_info: &'a AccountInfo<'a>,
812    pub payer_account_info: &'a AccountInfo<'a>,
813    pub update_authority_info: &'a AccountInfo<'a>,
814    pub system_account_info: &'a AccountInfo<'a>,
815    pub rent_info: &'a AccountInfo<'a>,
816}
817
818// This equals the program address of the metadata program:
819// AqH29mZfQFgRpfwaPoTMWSKJ5kqauoc1FwVBRksZyQrt
820// IMPORTANT NOTE
821// This allows the upgrade authority of the Token Metadata program to create metadata for SPL tokens.
822// This only allows the upgrade authority to do create general metadata for the SPL token, it does not
823// allow the upgrade authority to add or change creators.
824const SEED_AUTHORITY: Pubkey = Pubkey::new_from_array([
825    0x92, 0x17, 0x2c, 0xc4, 0x72, 0x5d, 0xc0, 0x41, 0xf9, 0xdd, 0x8c, 0x51, 0x52, 0x60, 0x04, 0x26,
826    0x00, 0x93, 0xa3, 0x0b, 0x02, 0x73, 0xdc, 0xfa, 0x74, 0x92, 0x17, 0xfc, 0x94, 0xa2, 0x40, 0x49,
827]);
828/// Create a new account instruction
829pub fn process_create_metadata_accounts_logic(
830    program_id: &Pubkey,
831    accounts: CreateMetadataAccountsLogicArgs,
832    data: DataV2,
833    allow_direct_creator_writes: bool,
834    mut is_mutable: bool,
835    is_edition: bool,
836    add_token_standard: bool,
837) -> ProgramResult {
838    let CreateMetadataAccountsLogicArgs {
839        metadata_account_info,
840        mint_info,
841        mint_authority_info,
842        payer_account_info,
843        update_authority_info,
844        system_account_info,
845        rent_info,
846    } = accounts;
847
848    let mut update_authority_key = *update_authority_info.key;
849    let existing_mint_authority = get_mint_authority(mint_info)?;
850    // IMPORTANT NOTE
851    // This allows the Metaplex Foundation to Create but not update metadata for SPL tokens that have not populated their metadata.
852    assert_mint_authority_matches_mint(&existing_mint_authority, mint_authority_info).or_else(
853        |e| {
854            // Allow seeding by the authority seed populator
855            if mint_authority_info.key == &SEED_AUTHORITY && mint_authority_info.is_signer {
856                // When metadata is seeded, the mint authority should be able to change it
857                if let COption::Some(auth) = existing_mint_authority {
858                    update_authority_key = auth;
859                    is_mutable = true;
860                }
861                Ok(())
862            } else {
863                Err(e)
864            }
865        },
866    )?;
867    assert_owned_by(mint_info, &spl_token::id())?;
868
869    let metadata_seeds = &[
870        PREFIX.as_bytes(),
871        program_id.as_ref(),
872        mint_info.key.as_ref(),
873    ];
874    let (metadata_key, metadata_bump_seed) =
875        Pubkey::find_program_address(metadata_seeds, program_id);
876    let metadata_authority_signer_seeds = &[
877        PREFIX.as_bytes(),
878        program_id.as_ref(),
879        mint_info.key.as_ref(),
880        &[metadata_bump_seed],
881    ];
882
883    if metadata_account_info.key != &metadata_key {
884        return Err(MetadataError::InvalidMetadataKey.into());
885    }
886
887    create_or_allocate_account_raw(
888        *program_id,
889        metadata_account_info,
890        rent_info,
891        system_account_info,
892        payer_account_info,
893        MAX_METADATA_LEN,
894        metadata_authority_signer_seeds,
895    )?;
896
897    let mut metadata = Metadata::from_account_info(metadata_account_info)?;
898    let compatible_data = data.to_v1();
899    assert_data_valid(
900        &compatible_data,
901        &update_authority_key,
902        &metadata,
903        allow_direct_creator_writes,
904        update_authority_info.is_signer,
905        false,
906    )?;
907
908    let mint_decimals = get_mint_decimals(mint_info)?;
909
910    metadata.mint = *mint_info.key;
911    metadata.key = Key::MetadataV1;
912    metadata.data = data.to_v1();
913    metadata.is_mutable = is_mutable;
914    metadata.update_authority = update_authority_key;
915    assert_valid_use(&data.uses, &None)?;
916    metadata.uses = data.uses;
917    assert_collection_update_is_valid(&None, &data.collection)?;
918    metadata.collection = data.collection;
919    if add_token_standard {
920        let token_standard = if is_edition {
921            TokenStandard::NonFungibleEdition
922        } else if mint_decimals == 0 {
923            TokenStandard::FungibleAsset
924        } else {
925            TokenStandard::Fungible
926        };
927        metadata.token_standard = Some(token_standard);
928    } else {
929        metadata.token_standard = None;
930    }
931
932    puff_out_data_fields(&mut metadata);
933
934    let edition_seeds = &[
935        PREFIX.as_bytes(),
936        program_id.as_ref(),
937        metadata.mint.as_ref(),
938        EDITION.as_bytes(),
939    ];
940    let (_, edition_bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
941    metadata.edition_nonce = Some(edition_bump_seed);
942    metadata.serialize(&mut *metadata_account_info.data.borrow_mut())?;
943
944    Ok(())
945}
946
947/// Strings need to be appended with `\0`s in order to have a deterministic length.
948/// This supports the `memcmp` filter  on get program account calls.
949/// NOTE: it is assumed that the metadata fields are never larger than the respective MAX_LENGTH
950pub fn puff_out_data_fields(metadata: &mut Metadata) {
951    metadata.data.name = puffed_out_string(&metadata.data.name, MAX_NAME_LENGTH);
952    metadata.data.symbol = puffed_out_string(&metadata.data.symbol, MAX_SYMBOL_LENGTH);
953    metadata.data.uri = puffed_out_string(&metadata.data.uri, MAX_URI_LENGTH);
954}
955
956/// Pads the string to the desired size with `0u8`s.
957/// NOTE: it is assumed that the string's size is never larger than the given size.
958pub fn puffed_out_string(s: &String, size: usize) -> String {
959    let mut array_of_zeroes = vec![];
960    let puff_amount = size - s.len();
961    while array_of_zeroes.len() < puff_amount {
962        array_of_zeroes.push(0u8);
963    }
964    s.clone() + std::str::from_utf8(&array_of_zeroes).unwrap()
965}
966
967/// Pads the string to the desired size with `0u8`s.
968/// NOTE: it is assumed that the string's size is never larger than the given size.
969pub fn zero_account(s: &String, size: usize) -> String {
970    let mut array_of_zeroes = vec![];
971    let puff_amount = size - s.len();
972    while array_of_zeroes.len() < puff_amount {
973        array_of_zeroes.push(0u8);
974    }
975    s.clone() + std::str::from_utf8(&array_of_zeroes).unwrap()
976}
977
978pub struct MintNewEditionFromMasterEditionViaTokenLogicArgs<'a> {
979    pub new_metadata_account_info: &'a AccountInfo<'a>,
980    pub new_edition_account_info: &'a AccountInfo<'a>,
981    pub master_edition_account_info: &'a AccountInfo<'a>,
982    pub mint_info: &'a AccountInfo<'a>,
983    pub edition_marker_info: &'a AccountInfo<'a>,
984    pub mint_authority_info: &'a AccountInfo<'a>,
985    pub payer_account_info: &'a AccountInfo<'a>,
986    pub owner_account_info: &'a AccountInfo<'a>,
987    pub token_account_info: &'a AccountInfo<'a>,
988    pub update_authority_info: &'a AccountInfo<'a>,
989    pub master_metadata_account_info: &'a AccountInfo<'a>,
990    pub token_program_account_info: &'a AccountInfo<'a>,
991    pub system_account_info: &'a AccountInfo<'a>,
992    pub rent_info: &'a AccountInfo<'a>,
993}
994
995pub fn process_mint_new_edition_from_master_edition_via_token_logic<'a>(
996    program_id: &'a Pubkey,
997    accounts: MintNewEditionFromMasterEditionViaTokenLogicArgs<'a>,
998    edition: u64,
999    ignore_owner_signer: bool,
1000) -> ProgramResult {
1001    let MintNewEditionFromMasterEditionViaTokenLogicArgs {
1002        new_metadata_account_info,
1003        new_edition_account_info,
1004        master_edition_account_info,
1005        mint_info,
1006        edition_marker_info,
1007        mint_authority_info,
1008        payer_account_info,
1009        owner_account_info,
1010        token_account_info,
1011        update_authority_info,
1012        master_metadata_account_info,
1013        token_program_account_info,
1014        system_account_info,
1015        rent_info,
1016    } = accounts;
1017
1018    assert_token_program_matches_package(token_program_account_info)?;
1019    assert_owned_by(mint_info, &spl_token::id())?;
1020    assert_owned_by(token_account_info, &spl_token::id())?;
1021    assert_owned_by(master_edition_account_info, program_id)?;
1022    assert_owned_by(master_metadata_account_info, program_id)?;
1023
1024    let master_metadata = Metadata::from_account_info(master_metadata_account_info)?;
1025    let token_account: Account = assert_initialized(token_account_info)?;
1026
1027    if !ignore_owner_signer {
1028        assert_signer(owner_account_info)?;
1029
1030        if token_account.owner != *owner_account_info.key {
1031            return Err(MetadataError::InvalidOwner.into());
1032        }
1033    }
1034
1035    if token_account.mint != master_metadata.mint {
1036        return Err(MetadataError::TokenAccountMintMismatchV2.into());
1037    }
1038
1039    if token_account.amount < 1 {
1040        return Err(MetadataError::NotEnoughTokens.into());
1041    }
1042
1043    if !new_metadata_account_info.data_is_empty() {
1044        return Err(MetadataError::AlreadyInitialized.into());
1045    }
1046
1047    if !new_edition_account_info.data_is_empty() {
1048        return Err(MetadataError::AlreadyInitialized.into());
1049    }
1050
1051    let edition_number = edition.checked_div(EDITION_MARKER_BIT_SIZE).unwrap();
1052    let as_string = edition_number.to_string();
1053
1054    let bump = assert_derivation(
1055        program_id,
1056        edition_marker_info,
1057        &[
1058            PREFIX.as_bytes(),
1059            program_id.as_ref(),
1060            master_metadata.mint.as_ref(),
1061            EDITION.as_bytes(),
1062            as_string.as_bytes(),
1063        ],
1064    )?;
1065
1066    if edition_marker_info.data_is_empty() {
1067        let seeds = &[
1068            PREFIX.as_bytes(),
1069            program_id.as_ref(),
1070            master_metadata.mint.as_ref(),
1071            EDITION.as_bytes(),
1072            as_string.as_bytes(),
1073            &[bump],
1074        ];
1075
1076        create_or_allocate_account_raw(
1077            *program_id,
1078            edition_marker_info,
1079            rent_info,
1080            system_account_info,
1081            payer_account_info,
1082            MAX_EDITION_MARKER_SIZE,
1083            seeds,
1084        )?;
1085    }
1086
1087    let mut edition_marker = EditionMarker::from_account_info(edition_marker_info)?;
1088    edition_marker.key = Key::EditionMarker;
1089    if edition_marker.edition_taken(edition)? {
1090        return Err(MetadataError::AlreadyInitialized.into());
1091    } else {
1092        edition_marker.insert_edition(edition)?
1093    }
1094    edition_marker.serialize(&mut *edition_marker_info.data.borrow_mut())?;
1095
1096    mint_limited_edition(
1097        program_id,
1098        master_metadata,
1099        new_metadata_account_info,
1100        new_edition_account_info,
1101        master_edition_account_info,
1102        mint_info,
1103        mint_authority_info,
1104        payer_account_info,
1105        update_authority_info,
1106        token_program_account_info,
1107        system_account_info,
1108        rent_info,
1109        None,
1110        Some(edition),
1111    )?;
1112    Ok(())
1113}
1114pub fn assert_currently_holding(
1115    program_id: &Pubkey,
1116    owner_info: &AccountInfo,
1117    metadata_info: &AccountInfo,
1118    metadata: &Metadata,
1119    mint_info: &AccountInfo,
1120    token_account_info: &AccountInfo,
1121) -> ProgramResult {
1122    assert_owned_by(metadata_info, program_id)?;
1123    assert_owned_by(mint_info, &spl_token::id())?;
1124
1125    let token_account: Account = assert_initialized(token_account_info)?;
1126
1127    assert_owned_by(token_account_info, &spl_token::id())?;
1128
1129    if token_account.owner != *owner_info.key {
1130        return Err(MetadataError::InvalidOwner.into());
1131    }
1132
1133    if token_account.mint != *mint_info.key {
1134        return Err(MetadataError::MintMismatch.into());
1135    }
1136
1137    if token_account.amount < 1 {
1138        return Err(MetadataError::NotEnoughTokens.into());
1139    }
1140
1141    if token_account.mint != metadata.mint {
1142        return Err(MetadataError::MintMismatch.into());
1143    }
1144    Ok(())
1145}