metaplex/rust/token-metadata/program/src/utils.rs

1061 lines
34 KiB
Rust

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<T: Pack + IsInitialized>(
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
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<COption<Pubkey>, 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<Pubkey, ProgramError> {
// 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<COption<Pubkey>, 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<COption<Pubkey>, 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<u64, ProgramError> {
// 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<Pubkey>,
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<u64, ProgramError> {
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<u64> = 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<u64>,
me_supply: u64,
) -> Result<u64, ProgramError> {
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<Option<u64>, 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<u64, ProgramError> {
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<u64>,
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<u64>,
) -> 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<u8, ProgramError> {
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<T: BorshDeserialize>(
data: &[u8],
data_type: Key,
data_size: usize,
) -> Result<T, ProgramError> {
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(())
}