gpl_metaplex_token_metadata/
utils.rs

1use {
2    crate::{
3        error::MetadataError,
4        state::{
5            get_reservation_list, Data, EditionMarker, Key, MasterEditionV1, Metadata, EDITION,
6            EDITION_MARKER_BIT_SIZE, MAX_CREATOR_LIMIT, MAX_EDITION_LEN, MAX_EDITION_MARKER_SIZE,
7            MAX_MASTER_EDITION_LEN, MAX_METADATA_LEN, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH,
8            MAX_URI_LENGTH, PREFIX,
9        },
10    },
11    arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
12    borsh::{BorshDeserialize, BorshSerialize},
13    gemachain_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    },
26    gpl_token::{
27        instruction::{set_authority, AuthorityType},
28        state::{Account, Mint},
29    },
30    std::convert::TryInto,
31};
32
33pub fn assert_data_valid(
34    data: &Data,
35    update_authority: &Pubkey,
36    existing_metadata: &Metadata,
37    allow_direct_creator_writes: bool,
38    update_authority_is_signer: 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 {
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/gemachain-labs/gemachain-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_carats = rent
161        .minimum_balance(size)
162        .max(1)
163        .saturating_sub(new_account_info.carats());
164
165    if required_carats > 0 {
166        msg!("Transfer {} carats to the new account", required_carats);
167        invoke(
168            &system_instruction::transfer(&payer_info.key, new_account_info.key, required_carats),
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
259pub fn assert_mint_authority_matches_mint(
260    mint_authority: &COption<Pubkey>,
261    mint_authority_info: &AccountInfo,
262) -> ProgramResult {
263    match mint_authority {
264        COption::None => {
265            return Err(MetadataError::InvalidMintAuthority.into());
266        }
267        COption::Some(key) => {
268            if mint_authority_info.key != key {
269                return Err(MetadataError::InvalidMintAuthority.into());
270            }
271        }
272    }
273
274    if !mint_authority_info.is_signer {
275        return Err(MetadataError::NotMintAuthority.into());
276    }
277
278    Ok(())
279}
280
281pub fn assert_supply_invariance(
282    master_edition: &MasterEditionV1,
283    printing_mint: &Mint,
284    new_supply: u64,
285) -> ProgramResult {
286    // The supply of printed tokens and the supply of the master edition should, when added, never exceed max supply.
287    // Every time a printed token is burned, master edition.supply goes up by 1.
288    if let Some(max_supply) = master_edition.max_supply {
289        let current_supply = printing_mint
290            .supply
291            .checked_add(master_edition.supply)
292            .ok_or(MetadataError::NumericalOverflowError)?;
293        let new_proposed_supply = current_supply
294            .checked_add(new_supply)
295            .ok_or(MetadataError::NumericalOverflowError)?;
296        if new_proposed_supply > max_supply {
297            return Err(MetadataError::PrintingWouldBreachMaximumSupply.into());
298        }
299    }
300
301    Ok(())
302}
303pub fn transfer_mint_authority<'a>(
304    edition_key: &Pubkey,
305    edition_account_info: &AccountInfo<'a>,
306    mint_info: &AccountInfo<'a>,
307    mint_authority_info: &AccountInfo<'a>,
308    token_program_info: &AccountInfo<'a>,
309) -> ProgramResult {
310    msg!("Setting mint authority");
311    let accounts = &[
312        mint_authority_info.clone(),
313        mint_info.clone(),
314        token_program_info.clone(),
315        edition_account_info.clone(),
316    ];
317    invoke_signed(
318        &set_authority(
319            token_program_info.key,
320            mint_info.key,
321            Some(edition_key),
322            AuthorityType::MintTokens,
323            mint_authority_info.key,
324            &[&mint_authority_info.key],
325        )
326        .unwrap(),
327        accounts,
328        &[],
329    )?;
330    msg!("Setting freeze authority");
331    let freeze_authority = get_mint_freeze_authority(mint_info)?;
332    if freeze_authority.is_some() {
333        invoke_signed(
334            &set_authority(
335                token_program_info.key,
336                mint_info.key,
337                Some(&edition_key),
338                AuthorityType::FreezeAccount,
339                mint_authority_info.key,
340                &[&mint_authority_info.key],
341            )
342            .unwrap(),
343            accounts,
344            &[],
345        )?;
346        msg!("Finished setting freeze authority");
347    } else {
348        msg!("Skipping freeze authority because this mint has none")
349    }
350
351    Ok(())
352}
353
354pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
355    if !rent.is_exempt(account_info.carats(), account_info.data_len()) {
356        Err(MetadataError::NotRentExempt.into())
357    } else {
358        Ok(())
359    }
360}
361
362// Todo deprecate this for assert derivation
363pub fn assert_edition_valid(
364    program_id: &Pubkey,
365    mint: &Pubkey,
366    edition_account_info: &AccountInfo,
367) -> ProgramResult {
368    let edition_seeds = &[
369        PREFIX.as_bytes(),
370        program_id.as_ref(),
371        &mint.as_ref(),
372        EDITION.as_bytes(),
373    ];
374    let (edition_key, _) = Pubkey::find_program_address(edition_seeds, program_id);
375    if edition_key != *edition_account_info.key {
376        return Err(MetadataError::InvalidEditionKey.into());
377    }
378
379    Ok(())
380}
381
382pub fn extract_edition_number_from_deprecated_reservation_list(
383    account: &AccountInfo,
384    mint_authority_info: &AccountInfo,
385) -> Result<u64, ProgramError> {
386    let mut reservation_list = get_reservation_list(account)?;
387
388    if let Some(supply_snapshot) = reservation_list.supply_snapshot() {
389        let mut prev_total_offsets: u64 = 0;
390        let mut offset: Option<u64> = None;
391        let mut reservations = reservation_list.reservations();
392        for i in 0..reservations.len() {
393            let mut reservation = &mut reservations[i];
394
395            if reservation.address == *mint_authority_info.key {
396                offset = Some(
397                    prev_total_offsets
398                        .checked_add(reservation.spots_remaining)
399                        .ok_or(MetadataError::NumericalOverflowError)?,
400                );
401                // You get your editions in reverse order but who cares, saves a byte
402                reservation.spots_remaining = reservation
403                    .spots_remaining
404                    .checked_sub(1)
405                    .ok_or(MetadataError::NumericalOverflowError)?;
406
407                reservation_list.set_reservations(reservations)?;
408                reservation_list.save(account)?;
409                break;
410            }
411
412            if reservation.address == gemachain_program::system_program::id() {
413                // This is an anchor point in the array...it means we reset our math to
414                // this offset because we may be missing information in between this point and
415                // the points before it.
416                prev_total_offsets = reservation.total_spots;
417            } else {
418                prev_total_offsets = prev_total_offsets
419                    .checked_add(reservation.total_spots)
420                    .ok_or(MetadataError::NumericalOverflowError)?;
421            }
422        }
423
424        match offset {
425            Some(val) => Ok(supply_snapshot
426                .checked_add(val)
427                .ok_or(MetadataError::NumericalOverflowError)?),
428            None => {
429                return Err(MetadataError::AddressNotInReservation.into());
430            }
431        }
432    } else {
433        return Err(MetadataError::ReservationNotSet.into());
434    }
435}
436
437pub fn calculate_edition_number(
438    mint_authority_info: &AccountInfo,
439    reservation_list_info: Option<&AccountInfo>,
440    edition_override: Option<u64>,
441    me_supply: u64,
442) -> Result<u64, ProgramError> {
443    let edition = match reservation_list_info {
444        Some(account) => {
445            extract_edition_number_from_deprecated_reservation_list(account, mint_authority_info)?
446        }
447        None => {
448            if let Some(edit) = edition_override {
449                edit
450            } else {
451                me_supply
452                    .checked_add(1)
453                    .ok_or(MetadataError::NumericalOverflowError)?
454            }
455        }
456    };
457
458    Ok(edition)
459}
460
461fn get_max_supply_off_master_edition(
462    master_edition_account_info: &AccountInfo,
463) -> Result<Option<u64>, ProgramError> {
464    let data = master_edition_account_info.try_borrow_data()?;
465    // this is an option, 9 bytes, first is 0 means is none
466    if data[9] == 0 {
467        Ok(None)
468    } else {
469        let amount_data = array_ref![data, 10, 8];
470        Ok(Some(u64::from_le_bytes(*amount_data)))
471    }
472}
473
474pub fn get_supply_off_master_edition(
475    master_edition_account_info: &AccountInfo,
476) -> Result<u64, ProgramError> {
477    let data = master_edition_account_info.try_borrow_data()?;
478    // this is an option, 9 bytes, first is 0 means is none
479
480    let amount_data = array_ref![data, 1, 8];
481    Ok(u64::from_le_bytes(*amount_data))
482}
483
484pub fn calculate_supply_change<'a>(
485    master_edition_account_info: &AccountInfo<'a>,
486    reservation_list_info: Option<&AccountInfo<'a>>,
487    edition_override: Option<u64>,
488    me_supply: u64,
489) -> ProgramResult {
490    if reservation_list_info.is_none() {
491        let new_supply: u64;
492        if let Some(edition) = edition_override {
493            if edition > me_supply {
494                new_supply = edition;
495            } else {
496                new_supply = me_supply
497            }
498        } else {
499            new_supply = me_supply
500                .checked_add(1)
501                .ok_or(MetadataError::NumericalOverflowError)?;
502        }
503
504        if let Some(max) = get_max_supply_off_master_edition(master_edition_account_info)? {
505            if new_supply > max {
506                return Err(MetadataError::MaxEditionsMintedAlready.into());
507            }
508        }
509        // Doing old school serialization to protect CPU credits.
510        let edition_data = &mut master_edition_account_info.data.borrow_mut();
511        let output = array_mut_ref![edition_data, 0, MAX_MASTER_EDITION_LEN];
512
513        let (_key, supply, _the_rest) =
514            mut_array_refs![output, 1, 8, MAX_MASTER_EDITION_LEN - 8 - 1];
515        *supply = new_supply.to_le_bytes();
516    }
517
518    Ok(())
519}
520
521#[allow(clippy::too_many_arguments)]
522pub fn mint_limited_edition<'a>(
523    program_id: &'a Pubkey,
524    master_metadata: Metadata,
525    new_metadata_account_info: &'a AccountInfo<'a>,
526    new_edition_account_info: &'a AccountInfo<'a>,
527    master_edition_account_info: &'a AccountInfo<'a>,
528    mint_info: &'a AccountInfo<'a>,
529    mint_authority_info: &'a AccountInfo<'a>,
530    payer_account_info: &'a AccountInfo<'a>,
531    update_authority_info: &'a AccountInfo<'a>,
532    token_program_account_info: &'a AccountInfo<'a>,
533    system_account_info: &'a AccountInfo<'a>,
534    rent_info: &'a AccountInfo<'a>,
535    // Only present with MasterEditionV1 calls, if present, use edition based off address in res list,
536    // otherwise, pull off the top
537    reservation_list_info: Option<&'a AccountInfo<'a>>,
538    // Only present with MasterEditionV2 calls, if present, means
539    // directing to a specific version, otherwise just pull off the top
540    edition_override: Option<u64>,
541) -> ProgramResult {
542    let me_supply = get_supply_off_master_edition(master_edition_account_info)?;
543    let mint_authority = get_mint_authority(mint_info)?;
544    let mint_supply = get_mint_supply(mint_info)?;
545    assert_mint_authority_matches_mint(&mint_authority, mint_authority_info)?;
546
547    assert_edition_valid(
548        program_id,
549        &master_metadata.mint,
550        master_edition_account_info,
551    )?;
552
553    let edition_seeds = &[
554        PREFIX.as_bytes(),
555        program_id.as_ref(),
556        &mint_info.key.as_ref(),
557        EDITION.as_bytes(),
558    ];
559    let (edition_key, bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
560    if edition_key != *new_edition_account_info.key {
561        return Err(MetadataError::InvalidEditionKey.into());
562    }
563
564    if reservation_list_info.is_some() && edition_override.is_some() {
565        return Err(MetadataError::InvalidOperation.into());
566    }
567
568    calculate_supply_change(
569        master_edition_account_info,
570        reservation_list_info,
571        edition_override,
572        me_supply,
573    )?;
574
575    if mint_supply != 1 {
576        return Err(MetadataError::EditionsMustHaveExactlyOneToken.into());
577    }
578
579    // create the metadata the normal way...
580    process_create_metadata_accounts_logic(
581        &program_id,
582        CreateMetadataAccountsLogicArgs {
583            metadata_account_info: new_metadata_account_info,
584            mint_info,
585            mint_authority_info,
586            payer_account_info,
587            update_authority_info,
588            system_account_info,
589            rent_info,
590        },
591        master_metadata.data,
592        true,
593        false,
594    )?;
595    let edition_authority_seeds = &[
596        PREFIX.as_bytes(),
597        program_id.as_ref(),
598        &mint_info.key.as_ref(),
599        EDITION.as_bytes(),
600        &[bump_seed],
601    ];
602
603    create_or_allocate_account_raw(
604        *program_id,
605        new_edition_account_info,
606        rent_info,
607        system_account_info,
608        payer_account_info,
609        MAX_EDITION_LEN,
610        edition_authority_seeds,
611    )?;
612
613    // Doing old school serialization to protect CPU credits.
614    let edition_data = &mut new_edition_account_info.data.borrow_mut();
615    let output = array_mut_ref![edition_data, 0, MAX_EDITION_LEN];
616
617    let (key, parent, edition, _padding) = mut_array_refs![output, 1, 32, 8, 200];
618
619    *key = [Key::EditionV1 as u8];
620    parent.copy_from_slice(master_edition_account_info.key.as_ref());
621
622    *edition = calculate_edition_number(
623        mint_authority_info,
624        reservation_list_info,
625        edition_override,
626        me_supply,
627    )?
628    .to_le_bytes();
629
630    // Now make sure this mint can never be used by anybody else.
631    transfer_mint_authority(
632        &edition_key,
633        new_edition_account_info,
634        mint_info,
635        mint_authority_info,
636        token_program_account_info,
637    )?;
638
639    Ok(())
640}
641
642pub fn gpl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
643    let TokenBurnParams {
644        mint,
645        source,
646        authority,
647        token_program,
648        amount,
649        authority_signer_seeds,
650    } = params;
651    let mut seeds: Vec<&[&[u8]]> = vec![];
652    if let Some(seed) = authority_signer_seeds {
653        seeds.push(seed);
654    }
655    let result = invoke_signed(
656        &gpl_token::instruction::burn(
657            token_program.key,
658            source.key,
659            mint.key,
660            authority.key,
661            &[],
662            amount,
663        )?,
664        &[source, mint, authority, token_program],
665        seeds.as_slice(),
666    );
667    result.map_err(|_| MetadataError::TokenBurnFailed.into())
668}
669
670/// TokenBurnParams
671pub struct TokenBurnParams<'a: 'b, 'b> {
672    /// mint
673    pub mint: AccountInfo<'a>,
674    /// source
675    pub source: AccountInfo<'a>,
676    /// amount
677    pub amount: u64,
678    /// authority
679    pub authority: AccountInfo<'a>,
680    /// authority_signer_seeds
681    pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
682    /// token_program
683    pub token_program: AccountInfo<'a>,
684}
685
686pub fn gpl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
687    let TokenMintToParams {
688        mint,
689        destination,
690        authority,
691        token_program,
692        amount,
693        authority_signer_seeds,
694    } = params;
695    let mut seeds: Vec<&[&[u8]]> = vec![];
696    if let Some(seed) = authority_signer_seeds {
697        seeds.push(seed);
698    }
699    let result = invoke_signed(
700        &gpl_token::instruction::mint_to(
701            token_program.key,
702            mint.key,
703            destination.key,
704            authority.key,
705            &[],
706            amount,
707        )?,
708        &[mint, destination, authority, token_program],
709        seeds.as_slice(),
710    );
711    result.map_err(|_| MetadataError::TokenMintToFailed.into())
712}
713
714/// TokenMintToParams
715pub struct TokenMintToParams<'a: 'b, 'b> {
716    /// mint
717    pub mint: AccountInfo<'a>,
718    /// destination
719    pub destination: AccountInfo<'a>,
720    /// amount
721    pub amount: u64,
722    /// authority
723    pub authority: AccountInfo<'a>,
724    /// authority_signer_seeds
725    pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
726    /// token_program
727    pub token_program: AccountInfo<'a>,
728}
729
730pub fn assert_derivation(
731    program_id: &Pubkey,
732    account: &AccountInfo,
733    path: &[&[u8]],
734) -> Result<u8, ProgramError> {
735    let (key, bump) = Pubkey::find_program_address(&path, program_id);
736    if key != *account.key {
737        return Err(MetadataError::DerivedKeyInvalid.into());
738    }
739    Ok(bump)
740}
741
742pub fn assert_signer(account_info: &AccountInfo) -> ProgramResult {
743    if !account_info.is_signer {
744        Err(ProgramError::MissingRequiredSignature)
745    } else {
746        Ok(())
747    }
748}
749
750pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
751    if account.owner != owner {
752        Err(MetadataError::IncorrectOwner.into())
753    } else {
754        Ok(())
755    }
756}
757
758pub fn assert_token_program_matches_package(token_program_info: &AccountInfo) -> ProgramResult {
759    if *token_program_info.key != gpl_token::id() {
760        return Err(MetadataError::InvalidTokenProgram.into());
761    }
762
763    Ok(())
764}
765
766pub fn try_from_slice_checked<T: BorshDeserialize>(
767    data: &[u8],
768    data_type: Key,
769    data_size: usize,
770) -> Result<T, ProgramError> {
771    if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
772        || data.len() != data_size
773    {
774        return Err(MetadataError::DataTypeMismatch.into());
775    }
776
777    let result: T = try_from_slice_unchecked(data)?;
778
779    Ok(result)
780}
781
782pub struct CreateMetadataAccountsLogicArgs<'a> {
783    pub metadata_account_info: &'a AccountInfo<'a>,
784    pub mint_info: &'a AccountInfo<'a>,
785    pub mint_authority_info: &'a AccountInfo<'a>,
786    pub payer_account_info: &'a AccountInfo<'a>,
787    pub update_authority_info: &'a AccountInfo<'a>,
788    pub system_account_info: &'a AccountInfo<'a>,
789    pub rent_info: &'a AccountInfo<'a>,
790}
791
792/// Create a new account instruction
793pub fn process_create_metadata_accounts_logic(
794    program_id: &Pubkey,
795    accounts: CreateMetadataAccountsLogicArgs,
796    data: Data,
797    allow_direct_creator_writes: bool,
798    is_mutable: bool,
799) -> ProgramResult {
800    let CreateMetadataAccountsLogicArgs {
801        metadata_account_info,
802        mint_info,
803        mint_authority_info,
804        payer_account_info,
805        update_authority_info,
806        system_account_info,
807        rent_info,
808    } = accounts;
809
810    let mint_authority = get_mint_authority(mint_info)?;
811    assert_mint_authority_matches_mint(&mint_authority, mint_authority_info)?;
812    assert_owned_by(mint_info, &gpl_token::id())?;
813
814    let metadata_seeds = &[
815        PREFIX.as_bytes(),
816        program_id.as_ref(),
817        mint_info.key.as_ref(),
818    ];
819    let (metadata_key, metadata_bump_seed) =
820        Pubkey::find_program_address(metadata_seeds, program_id);
821    let metadata_authority_signer_seeds = &[
822        PREFIX.as_bytes(),
823        program_id.as_ref(),
824        mint_info.key.as_ref(),
825        &[metadata_bump_seed],
826    ];
827
828    if metadata_account_info.key != &metadata_key {
829        return Err(MetadataError::InvalidMetadataKey.into());
830    }
831
832    create_or_allocate_account_raw(
833        *program_id,
834        metadata_account_info,
835        rent_info,
836        system_account_info,
837        payer_account_info,
838        MAX_METADATA_LEN,
839        metadata_authority_signer_seeds,
840    )?;
841
842    let mut metadata = Metadata::from_account_info(metadata_account_info)?;
843    assert_data_valid(
844        &data,
845        update_authority_info.key,
846        &metadata,
847        allow_direct_creator_writes,
848        update_authority_info.is_signer,
849    )?;
850
851    metadata.mint = *mint_info.key;
852    metadata.key = Key::MetadataV1;
853    metadata.data = data;
854    metadata.is_mutable = is_mutable;
855    metadata.update_authority = *update_authority_info.key;
856
857    puff_out_data_fields(&mut metadata);
858
859    let edition_seeds = &[
860        PREFIX.as_bytes(),
861        program_id.as_ref(),
862        metadata.mint.as_ref(),
863        EDITION.as_bytes(),
864    ];
865    let (_, edition_bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
866    metadata.edition_nonce = Some(edition_bump_seed);
867
868    metadata.serialize(&mut *metadata_account_info.data.borrow_mut())?;
869
870    Ok(())
871}
872
873pub fn puff_out_data_fields(metadata: &mut Metadata) {
874    let mut array_of_zeroes = vec![];
875    while array_of_zeroes.len() < MAX_NAME_LENGTH - metadata.data.name.len() {
876        array_of_zeroes.push(0u8);
877    }
878    metadata.data.name =
879        metadata.data.name.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
880
881    let mut array_of_zeroes = vec![];
882    while array_of_zeroes.len() < MAX_SYMBOL_LENGTH - metadata.data.symbol.len() {
883        array_of_zeroes.push(0u8);
884    }
885    metadata.data.symbol =
886        metadata.data.symbol.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
887
888    let mut array_of_zeroes = vec![];
889    while array_of_zeroes.len() < MAX_URI_LENGTH - metadata.data.uri.len() {
890        array_of_zeroes.push(0u8);
891    }
892    metadata.data.uri = metadata.data.uri.clone() + std::str::from_utf8(&array_of_zeroes).unwrap();
893}
894
895pub struct MintNewEditionFromMasterEditionViaTokenLogicArgs<'a> {
896    pub new_metadata_account_info: &'a AccountInfo<'a>,
897    pub new_edition_account_info: &'a AccountInfo<'a>,
898    pub master_edition_account_info: &'a AccountInfo<'a>,
899    pub mint_info: &'a AccountInfo<'a>,
900    pub edition_marker_info: &'a AccountInfo<'a>,
901    pub mint_authority_info: &'a AccountInfo<'a>,
902    pub payer_account_info: &'a AccountInfo<'a>,
903    pub owner_account_info: &'a AccountInfo<'a>,
904    pub token_account_info: &'a AccountInfo<'a>,
905    pub update_authority_info: &'a AccountInfo<'a>,
906    pub master_metadata_account_info: &'a AccountInfo<'a>,
907    pub token_program_account_info: &'a AccountInfo<'a>,
908    pub system_account_info: &'a AccountInfo<'a>,
909    pub rent_info: &'a AccountInfo<'a>,
910}
911
912pub fn process_mint_new_edition_from_master_edition_via_token_logic<'a>(
913    program_id: &'a Pubkey,
914    accounts: MintNewEditionFromMasterEditionViaTokenLogicArgs<'a>,
915    edition: u64,
916    ignore_owner_signer: bool,
917) -> ProgramResult {
918    let MintNewEditionFromMasterEditionViaTokenLogicArgs {
919        new_metadata_account_info,
920        new_edition_account_info,
921        master_edition_account_info,
922        mint_info,
923        edition_marker_info,
924        mint_authority_info,
925        payer_account_info,
926        owner_account_info,
927        token_account_info,
928        update_authority_info,
929        master_metadata_account_info,
930        token_program_account_info,
931        system_account_info,
932        rent_info,
933    } = accounts;
934
935    assert_token_program_matches_package(token_program_account_info)?;
936    assert_owned_by(mint_info, &gpl_token::id())?;
937    assert_owned_by(token_account_info, &gpl_token::id())?;
938    assert_owned_by(master_edition_account_info, program_id)?;
939    assert_owned_by(master_metadata_account_info, program_id)?;
940
941    let master_metadata = Metadata::from_account_info(master_metadata_account_info)?;
942    let token_account: Account = assert_initialized(token_account_info)?;
943
944    if !ignore_owner_signer {
945        assert_signer(owner_account_info)?;
946
947        if token_account.owner != *owner_account_info.key {
948            return Err(MetadataError::InvalidOwner.into());
949        }
950    }
951
952    if token_account.mint != master_metadata.mint {
953        return Err(MetadataError::TokenAccountMintMismatchV2.into());
954    }
955
956    if token_account.amount < 1 {
957        return Err(MetadataError::NotEnoughTokens.into());
958    }
959
960    if !new_metadata_account_info.data_is_empty() {
961        return Err(MetadataError::AlreadyInitialized.into());
962    }
963
964    if !new_edition_account_info.data_is_empty() {
965        return Err(MetadataError::AlreadyInitialized.into());
966    }
967
968    let edition_number = edition.checked_div(EDITION_MARKER_BIT_SIZE).unwrap();
969    let as_string = edition_number.to_string();
970
971    let bump = assert_derivation(
972        program_id,
973        edition_marker_info,
974        &[
975            PREFIX.as_bytes(),
976            program_id.as_ref(),
977            master_metadata.mint.as_ref(),
978            EDITION.as_bytes(),
979            as_string.as_bytes(),
980        ],
981    )?;
982
983    if edition_marker_info.data_is_empty() {
984        let seeds = &[
985            PREFIX.as_bytes(),
986            program_id.as_ref(),
987            master_metadata.mint.as_ref(),
988            EDITION.as_bytes(),
989            as_string.as_bytes(),
990            &[bump],
991        ];
992
993        create_or_allocate_account_raw(
994            *program_id,
995            edition_marker_info,
996            rent_info,
997            system_account_info,
998            payer_account_info,
999            MAX_EDITION_MARKER_SIZE,
1000            seeds,
1001        )?;
1002    }
1003
1004    let mut edition_marker = EditionMarker::from_account_info(edition_marker_info)?;
1005    edition_marker.key = Key::EditionMarker;
1006    if edition_marker.edition_taken(edition)? {
1007        return Err(MetadataError::AlreadyInitialized.into());
1008    } else {
1009        edition_marker.insert_edition(edition)?
1010    }
1011    edition_marker.serialize(&mut *edition_marker_info.data.borrow_mut())?;
1012
1013    mint_limited_edition(
1014        program_id,
1015        master_metadata,
1016        new_metadata_account_info,
1017        new_edition_account_info,
1018        master_edition_account_info,
1019        mint_info,
1020        mint_authority_info,
1021        payer_account_info,
1022        update_authority_info,
1023        token_program_account_info,
1024        system_account_info,
1025        rent_info,
1026        None,
1027        Some(edition),
1028    )?;
1029    Ok(())
1030}