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 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
135pub 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#[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
211fn 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
221pub fn get_owner_from_token_account(
223 token_account_info: &AccountInfo,
224) -> Result<Pubkey, ProgramError> {
225 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 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
249pub fn get_mint_supply(account_info: &AccountInfo) -> Result<u64, ProgramError> {
251 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 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
362pub 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 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 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 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 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 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 reservation_list_info: Option<&'a AccountInfo<'a>>,
538 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 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 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 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
670pub struct TokenBurnParams<'a: 'b, 'b> {
672 pub mint: AccountInfo<'a>,
674 pub source: AccountInfo<'a>,
676 pub amount: u64,
678 pub authority: AccountInfo<'a>,
680 pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
682 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
714pub struct TokenMintToParams<'a: 'b, 'b> {
716 pub mint: AccountInfo<'a>,
718 pub destination: AccountInfo<'a>,
720 pub amount: u64,
722 pub authority: AccountInfo<'a>,
724 pub authority_signer_seeds: Option<&'b [&'b [u8]]>,
726 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
792pub 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}