use { crate::{ error::MetadataError, state::{ get_reservation_list, Data, EditionMarker, Key, MasterEditionV1, Metadata, EDITION, EDITION_MARKER_BIT_SIZE, MAX_CREATOR_LIMIT, MAX_EDITION_LEN, MAX_EDITION_MARKER_SIZE, MAX_MASTER_EDITION_LEN, MAX_METADATA_LEN, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH, MAX_URI_LENGTH, PREFIX, }, }, arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs}, borsh::{BorshDeserialize, BorshSerialize}, solana_program::{ account_info::AccountInfo, borsh::try_from_slice_unchecked, entrypoint::ProgramResult, msg, program::{invoke, invoke_signed}, program_error::ProgramError, program_option::COption, program_pack::{IsInitialized, Pack}, pubkey::Pubkey, system_instruction, sysvar::{rent::Rent, Sysvar}, }, spl_token::{ instruction::{set_authority, AuthorityType}, state::{Account, Mint}, }, std::convert::TryInto, }; pub fn assert_data_valid( data: &Data, update_authority: &Pubkey, existing_metadata: &Metadata, allow_direct_creator_writes: bool, update_authority_is_signer: bool, ) -> ProgramResult { if data.name.len() > MAX_NAME_LENGTH { return Err(MetadataError::NameTooLong.into()); } if data.symbol.len() > MAX_SYMBOL_LENGTH { return Err(MetadataError::SymbolTooLong.into()); } if data.uri.len() > MAX_URI_LENGTH { return Err(MetadataError::UriTooLong.into()); } if data.seller_fee_basis_points > 10000 { return Err(MetadataError::InvalidBasisPoints.into()); } if data.creators.is_some() { if let Some(creators) = &data.creators { if creators.len() > MAX_CREATOR_LIMIT { return Err(MetadataError::CreatorsTooLong.into()); } if creators.is_empty() { return Err(MetadataError::CreatorsMustBeAtleastOne.into()); } else { let mut found = false; let mut total: u8 = 0; for i in 0..creators.len() { let creator = &creators[i]; for j in (i + 1)..creators.len() { if creators[j].address == creator.address { return Err(MetadataError::DuplicateCreatorAddress.into()); } } total = total .checked_add(creator.share) .ok_or(MetadataError::NumericalOverflowError)?; if creator.address == *update_authority { found = true; } // Dont allow metadata owner to unilaterally say a creator verified... // cross check with array, only let them say verified=true here if // it already was true and in the array. // Conversely, dont let a verified creator be wiped. if (!update_authority_is_signer || creator.address != *update_authority) && !allow_direct_creator_writes { if let Some(existing_creators) = &existing_metadata.data.creators { match existing_creators .iter() .find(|c| c.address == creator.address) { Some(existing_creator) => { if creator.verified && !existing_creator.verified { return Err( MetadataError::CannotVerifyAnotherCreator.into() ); } else if !creator.verified && existing_creator.verified { return Err( MetadataError::CannotUnverifyAnotherCreator.into() ); } } None => { if creator.verified { return Err( MetadataError::CannotVerifyAnotherCreator.into() ); } } } } else { if creator.verified { return Err(MetadataError::CannotVerifyAnotherCreator.into()); } } } } if !found && !allow_direct_creator_writes { return Err(MetadataError::MustBeOneOfCreators.into()); } if total != 100 { return Err(MetadataError::ShareTotalMustBe100.into()); } } } } Ok(()) } /// assert initialized account pub fn assert_initialized( account_info: &AccountInfo, ) -> Result { let account: T = T::unpack_unchecked(&account_info.data.borrow())?; if !account.is_initialized() { Err(MetadataError::Uninitialized.into()) } else { Ok(account) } } /// Create account almost from scratch, lifted from /// https://github.com/solana-labs/solana-program-library/tree/master/associated-token-account/program/src/processor.rs#L51-L98 #[inline(always)] pub fn create_or_allocate_account_raw<'a>( program_id: Pubkey, new_account_info: &AccountInfo<'a>, rent_sysvar_info: &AccountInfo<'a>, system_program_info: &AccountInfo<'a>, payer_info: &AccountInfo<'a>, size: usize, signer_seeds: &[&[u8]], ) -> ProgramResult { let rent = &Rent::from_account_info(rent_sysvar_info)?; let required_lamports = rent .minimum_balance(size) .max(1) .saturating_sub(new_account_info.lamports()); if required_lamports > 0 { msg!("Transfer {} lamports to the new account", required_lamports); invoke( &system_instruction::transfer(&payer_info.key, new_account_info.key, required_lamports), &[ payer_info.clone(), new_account_info.clone(), system_program_info.clone(), ], )?; } let accounts = &[new_account_info.clone(), system_program_info.clone()]; msg!("Allocate space for the account"); invoke_signed( &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()), accounts, &[&signer_seeds], )?; msg!("Assign the account to the owning program"); invoke_signed( &system_instruction::assign(new_account_info.key, &program_id), accounts, &[&signer_seeds], )?; Ok(()) } pub fn assert_update_authority_is_correct( metadata: &Metadata, update_authority_info: &AccountInfo, ) -> ProgramResult { if metadata.update_authority != *update_authority_info.key { return Err(MetadataError::UpdateAuthorityIncorrect.into()); } if !update_authority_info.is_signer { return Err(MetadataError::UpdateAuthorityIsNotSigner.into()); } Ok(()) } /// Unpacks COption from a slice, taken from token program fn unpack_coption_key(src: &[u8; 36]) -> Result, ProgramError> { let (tag, body) = array_refs![src, 4, 32]; match *tag { [0, 0, 0, 0] => Ok(COption::None), [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))), _ => Err(ProgramError::InvalidAccountData), } } /// Cheap method to just grab owner Pubkey from token account, instead of deserializing entire thing pub fn get_owner_from_token_account( token_account_info: &AccountInfo, ) -> Result { // TokeAccount layout: mint(32), owner(32), ... let data = token_account_info.try_borrow_data()?; let owner_data = array_ref![data, 32, 32]; Ok(Pubkey::new_from_array(*owner_data)) } pub fn get_mint_authority(account_info: &AccountInfo) -> Result, ProgramError> { // In token program, 36, 8, 1, 1 is the layout, where the first 36 is mint_authority // so we start at 0. let data = account_info.try_borrow_data().unwrap(); let authority_bytes = array_ref![data, 0, 36]; Ok(unpack_coption_key(&authority_bytes)?) } pub fn get_mint_freeze_authority( account_info: &AccountInfo, ) -> Result, ProgramError> { let data = account_info.try_borrow_data().unwrap(); let authority_bytes = array_ref![data, 36 + 8 + 1 + 1, 36]; Ok(unpack_coption_key(&authority_bytes)?) } /// cheap method to just get supply off a mint without unpacking whole object pub fn get_mint_supply(account_info: &AccountInfo) -> Result { // In token program, 36, 8, 1, 1 is the layout, where the first 8 is supply u64. // so we start at 36. let data = account_info.try_borrow_data().unwrap(); let bytes = array_ref![data, 36, 8]; Ok(u64::from_le_bytes(*bytes)) } pub fn assert_mint_authority_matches_mint( mint_authority: &COption, mint_authority_info: &AccountInfo, ) -> ProgramResult { match mint_authority { COption::None => { return Err(MetadataError::InvalidMintAuthority.into()); } COption::Some(key) => { if mint_authority_info.key != key { return Err(MetadataError::InvalidMintAuthority.into()); } } } if !mint_authority_info.is_signer { return Err(MetadataError::NotMintAuthority.into()); } Ok(()) } pub fn assert_supply_invariance( master_edition: &MasterEditionV1, printing_mint: &Mint, new_supply: u64, ) -> ProgramResult { // The supply of printed tokens and the supply of the master edition should, when added, never exceed max supply. // Every time a printed token is burned, master edition.supply goes up by 1. if let Some(max_supply) = master_edition.max_supply { let current_supply = printing_mint .supply .checked_add(master_edition.supply) .ok_or(MetadataError::NumericalOverflowError)?; let new_proposed_supply = current_supply .checked_add(new_supply) .ok_or(MetadataError::NumericalOverflowError)?; if new_proposed_supply > max_supply { return Err(MetadataError::PrintingWouldBreachMaximumSupply.into()); } } Ok(()) } pub fn transfer_mint_authority<'a>( edition_key: &Pubkey, edition_account_info: &AccountInfo<'a>, mint_info: &AccountInfo<'a>, mint_authority_info: &AccountInfo<'a>, token_program_info: &AccountInfo<'a>, ) -> ProgramResult { msg!("Setting mint authority"); let accounts = &[ mint_authority_info.clone(), mint_info.clone(), token_program_info.clone(), edition_account_info.clone(), ]; invoke_signed( &set_authority( token_program_info.key, mint_info.key, Some(edition_key), AuthorityType::MintTokens, mint_authority_info.key, &[&mint_authority_info.key], ) .unwrap(), accounts, &[], )?; msg!("Setting freeze authority"); let freeze_authority = get_mint_freeze_authority(mint_info)?; if freeze_authority.is_some() { invoke_signed( &set_authority( token_program_info.key, mint_info.key, Some(&edition_key), AuthorityType::FreezeAccount, mint_authority_info.key, &[&mint_authority_info.key], ) .unwrap(), accounts, &[], )?; msg!("Finished setting freeze authority"); } else { msg!("Skipping freeze authority because this mint has none") } Ok(()) } pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult { if !rent.is_exempt(account_info.lamports(), account_info.data_len()) { Err(MetadataError::NotRentExempt.into()) } else { Ok(()) } } // Todo deprecate this for assert derivation pub fn assert_edition_valid( program_id: &Pubkey, mint: &Pubkey, edition_account_info: &AccountInfo, ) -> ProgramResult { let edition_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), &mint.as_ref(), EDITION.as_bytes(), ]; let (edition_key, _) = Pubkey::find_program_address(edition_seeds, program_id); if edition_key != *edition_account_info.key { return Err(MetadataError::InvalidEditionKey.into()); } Ok(()) } pub fn extract_edition_number_from_deprecated_reservation_list( account: &AccountInfo, mint_authority_info: &AccountInfo, ) -> Result { let mut reservation_list = get_reservation_list(account)?; if let Some(supply_snapshot) = reservation_list.supply_snapshot() { let mut prev_total_offsets: u64 = 0; let mut offset: Option = None; let mut reservations = reservation_list.reservations(); for i in 0..reservations.len() { let mut reservation = &mut reservations[i]; if reservation.address == *mint_authority_info.key { offset = Some( prev_total_offsets .checked_add(reservation.spots_remaining) .ok_or(MetadataError::NumericalOverflowError)?, ); // You get your editions in reverse order but who cares, saves a byte reservation.spots_remaining = reservation .spots_remaining .checked_sub(1) .ok_or(MetadataError::NumericalOverflowError)?; reservation_list.set_reservations(reservations)?; reservation_list.save(account)?; break; } if reservation.address == solana_program::system_program::id() { // This is an anchor point in the array...it means we reset our math to // this offset because we may be missing information in between this point and // the points before it. prev_total_offsets = reservation.total_spots; } else { prev_total_offsets = prev_total_offsets .checked_add(reservation.total_spots) .ok_or(MetadataError::NumericalOverflowError)?; } } match offset { Some(val) => Ok(supply_snapshot .checked_add(val) .ok_or(MetadataError::NumericalOverflowError)?), None => { return Err(MetadataError::AddressNotInReservation.into()); } } } else { return Err(MetadataError::ReservationNotSet.into()); } } pub fn calculate_edition_number( mint_authority_info: &AccountInfo, reservation_list_info: Option<&AccountInfo>, edition_override: Option, me_supply: u64, ) -> Result { let edition = match reservation_list_info { Some(account) => { extract_edition_number_from_deprecated_reservation_list(account, mint_authority_info)? } None => { if let Some(edit) = edition_override { edit } else { me_supply .checked_add(1) .ok_or(MetadataError::NumericalOverflowError)? } } }; Ok(edition) } fn get_max_supply_off_master_edition( master_edition_account_info: &AccountInfo, ) -> Result, ProgramError> { let data = master_edition_account_info.try_borrow_data()?; // this is an option, 9 bytes, first is 0 means is none if data[9] == 0 { Ok(None) } else { let amount_data = array_ref![data, 10, 8]; Ok(Some(u64::from_le_bytes(*amount_data))) } } pub fn get_supply_off_master_edition( master_edition_account_info: &AccountInfo, ) -> Result { let data = master_edition_account_info.try_borrow_data()?; // this is an option, 9 bytes, first is 0 means is none let amount_data = array_ref![data, 1, 8]; Ok(u64::from_le_bytes(*amount_data)) } pub fn calculate_supply_change<'a>( master_edition_account_info: &AccountInfo<'a>, reservation_list_info: Option<&AccountInfo<'a>>, edition_override: Option, me_supply: u64, ) -> ProgramResult { if reservation_list_info.is_none() { let new_supply: u64; if let Some(edition) = edition_override { if edition > me_supply { new_supply = edition; } else { new_supply = me_supply } } else { new_supply = me_supply .checked_add(1) .ok_or(MetadataError::NumericalOverflowError)?; } if let Some(max) = get_max_supply_off_master_edition(master_edition_account_info)? { if new_supply > max { return Err(MetadataError::MaxEditionsMintedAlready.into()); } } // Doing old school serialization to protect CPU credits. let edition_data = &mut master_edition_account_info.data.borrow_mut(); let output = array_mut_ref![edition_data, 0, MAX_MASTER_EDITION_LEN]; let (_key, supply, _the_rest) = mut_array_refs![output, 1, 8, MAX_MASTER_EDITION_LEN - 8 - 1]; *supply = new_supply.to_le_bytes(); } Ok(()) } #[allow(clippy::too_many_arguments)] pub fn mint_limited_edition<'a>( program_id: &'a Pubkey, master_metadata: Metadata, new_metadata_account_info: &'a AccountInfo<'a>, new_edition_account_info: &'a AccountInfo<'a>, master_edition_account_info: &'a AccountInfo<'a>, mint_info: &'a AccountInfo<'a>, mint_authority_info: &'a AccountInfo<'a>, payer_account_info: &'a AccountInfo<'a>, update_authority_info: &'a AccountInfo<'a>, token_program_account_info: &'a AccountInfo<'a>, system_account_info: &'a AccountInfo<'a>, rent_info: &'a AccountInfo<'a>, // Only present with MasterEditionV1 calls, if present, use edition based off address in res list, // otherwise, pull off the top reservation_list_info: Option<&'a AccountInfo<'a>>, // Only present with MasterEditionV2 calls, if present, means // directing to a specific version, otherwise just pull off the top edition_override: Option, ) -> ProgramResult { let me_supply = get_supply_off_master_edition(master_edition_account_info)?; let mint_authority = get_mint_authority(mint_info)?; let mint_supply = get_mint_supply(mint_info)?; assert_mint_authority_matches_mint(&mint_authority, mint_authority_info)?; assert_edition_valid( program_id, &master_metadata.mint, master_edition_account_info, )?; let edition_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), &mint_info.key.as_ref(), EDITION.as_bytes(), ]; let (edition_key, bump_seed) = Pubkey::find_program_address(edition_seeds, program_id); if edition_key != *new_edition_account_info.key { return Err(MetadataError::InvalidEditionKey.into()); } if reservation_list_info.is_some() && edition_override.is_some() { return Err(MetadataError::InvalidOperation.into()); } calculate_supply_change( master_edition_account_info, reservation_list_info, edition_override, me_supply, )?; if mint_supply != 1 { return Err(MetadataError::EditionsMustHaveExactlyOneToken.into()); } // create the metadata the normal way... process_create_metadata_accounts_logic( &program_id, CreateMetadataAccountsLogicArgs { metadata_account_info: new_metadata_account_info, mint_info, mint_authority_info, payer_account_info, update_authority_info, system_account_info, rent_info, }, master_metadata.data, true, false, )?; let edition_authority_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), &mint_info.key.as_ref(), EDITION.as_bytes(), &[bump_seed], ]; create_or_allocate_account_raw( *program_id, new_edition_account_info, rent_info, system_account_info, payer_account_info, MAX_EDITION_LEN, edition_authority_seeds, )?; // Doing old school serialization to protect CPU credits. let edition_data = &mut new_edition_account_info.data.borrow_mut(); let output = array_mut_ref![edition_data, 0, MAX_EDITION_LEN]; let (key, parent, edition, _padding) = mut_array_refs![output, 1, 32, 8, 200]; *key = [Key::EditionV1 as u8]; parent.copy_from_slice(master_edition_account_info.key.as_ref()); *edition = calculate_edition_number( mint_authority_info, reservation_list_info, edition_override, me_supply, )? .to_le_bytes(); // Now make sure this mint can never be used by anybody else. transfer_mint_authority( &edition_key, new_edition_account_info, mint_info, mint_authority_info, token_program_account_info, )?; Ok(()) } pub fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult { let TokenBurnParams { mint, source, authority, token_program, amount, authority_signer_seeds, } = params; let mut seeds: Vec<&[&[u8]]> = vec![]; if let Some(seed) = authority_signer_seeds { seeds.push(seed); } let result = invoke_signed( &spl_token::instruction::burn( token_program.key, source.key, mint.key, authority.key, &[], amount, )?, &[source, mint, authority, token_program], seeds.as_slice(), ); result.map_err(|_| MetadataError::TokenBurnFailed.into()) } /// TokenBurnParams pub struct TokenBurnParams<'a: 'b, 'b> { /// mint pub mint: AccountInfo<'a>, /// source pub source: AccountInfo<'a>, /// amount pub amount: u64, /// authority pub authority: AccountInfo<'a>, /// authority_signer_seeds pub authority_signer_seeds: Option<&'b [&'b [u8]]>, /// token_program pub token_program: AccountInfo<'a>, } pub fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult { let TokenMintToParams { mint, destination, authority, token_program, amount, authority_signer_seeds, } = params; let mut seeds: Vec<&[&[u8]]> = vec![]; if let Some(seed) = authority_signer_seeds { seeds.push(seed); } let result = invoke_signed( &spl_token::instruction::mint_to( token_program.key, mint.key, destination.key, authority.key, &[], amount, )?, &[mint, destination, authority, token_program], seeds.as_slice(), ); result.map_err(|_| MetadataError::TokenMintToFailed.into()) } /// TokenMintToParams pub struct TokenMintToParams<'a: 'b, 'b> { /// mint pub mint: AccountInfo<'a>, /// destination pub destination: AccountInfo<'a>, /// amount pub amount: u64, /// authority pub authority: AccountInfo<'a>, /// authority_signer_seeds pub authority_signer_seeds: Option<&'b [&'b [u8]]>, /// token_program pub token_program: AccountInfo<'a>, } pub fn assert_derivation( program_id: &Pubkey, account: &AccountInfo, path: &[&[u8]], ) -> Result { let (key, bump) = Pubkey::find_program_address(&path, program_id); if key != *account.key { return Err(MetadataError::DerivedKeyInvalid.into()); } Ok(bump) } pub fn assert_signer(account_info: &AccountInfo) -> ProgramResult { if !account_info.is_signer { Err(ProgramError::MissingRequiredSignature) } else { Ok(()) } } pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult { if account.owner != owner { Err(MetadataError::IncorrectOwner.into()) } else { Ok(()) } } pub fn assert_token_program_matches_package(token_program_info: &AccountInfo) -> ProgramResult { if *token_program_info.key != spl_token::id() { return Err(MetadataError::InvalidTokenProgram.into()); } Ok(()) } pub fn try_from_slice_checked( data: &[u8], data_type: Key, data_size: usize, ) -> Result { if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8) || data.len() != data_size { return Err(MetadataError::DataTypeMismatch.into()); } let result: T = try_from_slice_unchecked(data)?; Ok(result) } pub struct CreateMetadataAccountsLogicArgs<'a> { pub metadata_account_info: &'a AccountInfo<'a>, pub mint_info: &'a AccountInfo<'a>, pub mint_authority_info: &'a AccountInfo<'a>, pub payer_account_info: &'a AccountInfo<'a>, pub update_authority_info: &'a AccountInfo<'a>, pub system_account_info: &'a AccountInfo<'a>, pub rent_info: &'a AccountInfo<'a>, } // This equals the upgrade authority of the metadata program: // AqH29mZfQFgRpfwaPoTMWSKJ5kqauoc1FwVBRksZyQrt // IMPORTANT NOTE // This allows the upgrade authority of the Token Metadata program to create metadata for SPL tokens. // This only allows the upgrade authority to do create general metadata for the SPL token, it does not // allow the upgrade authority to add or change creators. const SEED_AUTHORITY: Pubkey = Pubkey::new_from_array([ 0x92, 0x17, 0x2c, 0xc4, 0x72, 0x5d, 0xc0, 0x41, 0xf9, 0xdd, 0x8c, 0x51, 0x52, 0x60, 0x04, 0x26, 0x00, 0x93, 0xa3, 0x0b, 0x02, 0x73, 0xdc, 0xfa, 0x74, 0x92, 0x17, 0xfc, 0x94, 0xa2, 0x40, 0x49, ]); /// Create a new account instruction pub fn process_create_metadata_accounts_logic( program_id: &Pubkey, accounts: CreateMetadataAccountsLogicArgs, data: Data, allow_direct_creator_writes: bool, mut is_mutable: bool, ) -> ProgramResult { let CreateMetadataAccountsLogicArgs { metadata_account_info, mint_info, mint_authority_info, payer_account_info, update_authority_info, system_account_info, rent_info, } = accounts; let mut update_authority_key = *update_authority_info.key; let mint_authority = get_mint_authority(mint_info)?; assert_mint_authority_matches_mint(&mint_authority, mint_authority_info).or_else(|e| { // Allow seeding by the authority seed populator if mint_authority_info.key == &SEED_AUTHORITY { // Seed authority should not be able to set creators if data.creators.is_some() { return Err(MetadataError::InvalidOperation.into()); } // When metadata is seeded, the mint authority should be able to change it if let COption::Some(auth) = mint_authority { update_authority_key = auth; is_mutable = true; } Ok(()) } else { Err(e) } })?; assert_owned_by(mint_info, &spl_token::id())?; let metadata_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), mint_info.key.as_ref(), ]; let (metadata_key, metadata_bump_seed) = Pubkey::find_program_address(metadata_seeds, program_id); let metadata_authority_signer_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), mint_info.key.as_ref(), &[metadata_bump_seed], ]; if metadata_account_info.key != &metadata_key { return Err(MetadataError::InvalidMetadataKey.into()); } create_or_allocate_account_raw( *program_id, metadata_account_info, rent_info, system_account_info, payer_account_info, MAX_METADATA_LEN, metadata_authority_signer_seeds, )?; let mut metadata = Metadata::from_account_info(metadata_account_info)?; assert_data_valid( &data, &update_authority_key, &metadata, allow_direct_creator_writes, update_authority_info.is_signer, )?; metadata.mint = *mint_info.key; metadata.key = Key::MetadataV1; metadata.data = data; metadata.is_mutable = is_mutable; metadata.update_authority = update_authority_key; puff_out_data_fields(&mut metadata); let edition_seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), metadata.mint.as_ref(), EDITION.as_bytes(), ]; let (_, edition_bump_seed) = Pubkey::find_program_address(edition_seeds, program_id); metadata.edition_nonce = Some(edition_bump_seed); metadata.serialize(&mut *metadata_account_info.data.borrow_mut())?; Ok(()) } pub fn puff_out_data_fields(metadata: &mut Metadata) { let mut array_of_zeroes = vec![]; while array_of_zeroes.len() < MAX_NAME_LENGTH - metadata.data.name.len() { array_of_zeroes.push(0u8); } metadata.data.name = metadata.data.name.clone() + std::str::from_utf8(&array_of_zeroes).unwrap(); let mut array_of_zeroes = vec![]; while array_of_zeroes.len() < MAX_SYMBOL_LENGTH - metadata.data.symbol.len() { array_of_zeroes.push(0u8); } metadata.data.symbol = metadata.data.symbol.clone() + std::str::from_utf8(&array_of_zeroes).unwrap(); let mut array_of_zeroes = vec![]; while array_of_zeroes.len() < MAX_URI_LENGTH - metadata.data.uri.len() { array_of_zeroes.push(0u8); } metadata.data.uri = metadata.data.uri.clone() + std::str::from_utf8(&array_of_zeroes).unwrap(); } pub struct MintNewEditionFromMasterEditionViaTokenLogicArgs<'a> { pub new_metadata_account_info: &'a AccountInfo<'a>, pub new_edition_account_info: &'a AccountInfo<'a>, pub master_edition_account_info: &'a AccountInfo<'a>, pub mint_info: &'a AccountInfo<'a>, pub edition_marker_info: &'a AccountInfo<'a>, pub mint_authority_info: &'a AccountInfo<'a>, pub payer_account_info: &'a AccountInfo<'a>, pub owner_account_info: &'a AccountInfo<'a>, pub token_account_info: &'a AccountInfo<'a>, pub update_authority_info: &'a AccountInfo<'a>, pub master_metadata_account_info: &'a AccountInfo<'a>, pub token_program_account_info: &'a AccountInfo<'a>, pub system_account_info: &'a AccountInfo<'a>, pub rent_info: &'a AccountInfo<'a>, } pub fn process_mint_new_edition_from_master_edition_via_token_logic<'a>( program_id: &'a Pubkey, accounts: MintNewEditionFromMasterEditionViaTokenLogicArgs<'a>, edition: u64, ignore_owner_signer: bool, ) -> ProgramResult { let MintNewEditionFromMasterEditionViaTokenLogicArgs { new_metadata_account_info, new_edition_account_info, master_edition_account_info, mint_info, edition_marker_info, mint_authority_info, payer_account_info, owner_account_info, token_account_info, update_authority_info, master_metadata_account_info, token_program_account_info, system_account_info, rent_info, } = accounts; assert_token_program_matches_package(token_program_account_info)?; assert_owned_by(mint_info, &spl_token::id())?; assert_owned_by(token_account_info, &spl_token::id())?; assert_owned_by(master_edition_account_info, program_id)?; assert_owned_by(master_metadata_account_info, program_id)?; let master_metadata = Metadata::from_account_info(master_metadata_account_info)?; let token_account: Account = assert_initialized(token_account_info)?; if !ignore_owner_signer { assert_signer(owner_account_info)?; if token_account.owner != *owner_account_info.key { return Err(MetadataError::InvalidOwner.into()); } } if token_account.mint != master_metadata.mint { return Err(MetadataError::TokenAccountMintMismatchV2.into()); } if token_account.amount < 1 { return Err(MetadataError::NotEnoughTokens.into()); } if !new_metadata_account_info.data_is_empty() { return Err(MetadataError::AlreadyInitialized.into()); } if !new_edition_account_info.data_is_empty() { return Err(MetadataError::AlreadyInitialized.into()); } let edition_number = edition.checked_div(EDITION_MARKER_BIT_SIZE).unwrap(); let as_string = edition_number.to_string(); let bump = assert_derivation( program_id, edition_marker_info, &[ PREFIX.as_bytes(), program_id.as_ref(), master_metadata.mint.as_ref(), EDITION.as_bytes(), as_string.as_bytes(), ], )?; if edition_marker_info.data_is_empty() { let seeds = &[ PREFIX.as_bytes(), program_id.as_ref(), master_metadata.mint.as_ref(), EDITION.as_bytes(), as_string.as_bytes(), &[bump], ]; create_or_allocate_account_raw( *program_id, edition_marker_info, rent_info, system_account_info, payer_account_info, MAX_EDITION_MARKER_SIZE, seeds, )?; } let mut edition_marker = EditionMarker::from_account_info(edition_marker_info)?; edition_marker.key = Key::EditionMarker; if edition_marker.edition_taken(edition)? { return Err(MetadataError::AlreadyInitialized.into()); } else { edition_marker.insert_edition(edition)? } edition_marker.serialize(&mut *edition_marker_info.data.borrow_mut())?; mint_limited_edition( program_id, master_metadata, new_metadata_account_info, new_edition_account_info, master_edition_account_info, mint_info, mint_authority_info, payer_account_info, update_authority_info, token_program_account_info, system_account_info, rent_info, None, Some(edition), )?; Ok(()) }