mpl_token_metadata/utils/
master_edition.rs

1use arrayref::{array_mut_ref, array_ref, mut_array_refs};
2use borsh::BorshSerialize;
3use mpl_utils::{
4    assert_signer, create_or_allocate_account_raw,
5    token::{get_mint_authority, get_mint_supply},
6};
7use solana_program::{
8    account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
9    pubkey::Pubkey,
10};
11use spl_token::state::{Account, Mint};
12
13use super::*;
14use crate::{
15    assertions::{
16        assert_derivation, assert_initialized, assert_mint_authority_matches_mint, assert_owned_by,
17        assert_token_program_matches_package, edition::assert_edition_valid,
18        metadata::assert_update_authority_is_correct,
19    },
20    error::MetadataError,
21    state::{
22        get_reservation_list, DataV2, EditionMarker, Key, MasterEdition, Metadata,
23        TokenMetadataAccount, Uses, EDITION, EDITION_MARKER_BIT_SIZE, MAX_EDITION_LEN,
24        MAX_EDITION_MARKER_SIZE, MAX_MASTER_EDITION_LEN, PREFIX,
25    },
26};
27
28pub struct MintNewEditionFromMasterEditionViaTokenLogicArgs<'a> {
29    pub new_metadata_account_info: &'a AccountInfo<'a>,
30    pub new_edition_account_info: &'a AccountInfo<'a>,
31    pub master_edition_account_info: &'a AccountInfo<'a>,
32    pub mint_info: &'a AccountInfo<'a>,
33    pub edition_marker_info: &'a AccountInfo<'a>,
34    pub mint_authority_info: &'a AccountInfo<'a>,
35    pub payer_account_info: &'a AccountInfo<'a>,
36    pub owner_account_info: &'a AccountInfo<'a>,
37    pub token_account_info: &'a AccountInfo<'a>,
38    pub update_authority_info: &'a AccountInfo<'a>,
39    pub master_metadata_account_info: &'a AccountInfo<'a>,
40    pub token_program_account_info: &'a AccountInfo<'a>,
41    pub system_account_info: &'a AccountInfo<'a>,
42}
43
44pub fn process_mint_new_edition_from_master_edition_via_token_logic<'a>(
45    program_id: &'a Pubkey,
46    accounts: MintNewEditionFromMasterEditionViaTokenLogicArgs<'a>,
47    edition: u64,
48    ignore_owner_signer: bool,
49) -> ProgramResult {
50    let MintNewEditionFromMasterEditionViaTokenLogicArgs {
51        new_metadata_account_info,
52        new_edition_account_info,
53        master_edition_account_info,
54        mint_info,
55        edition_marker_info,
56        mint_authority_info,
57        payer_account_info,
58        owner_account_info,
59        token_account_info,
60        update_authority_info,
61        master_metadata_account_info,
62        token_program_account_info,
63        system_account_info,
64    } = accounts;
65
66    assert_token_program_matches_package(token_program_account_info)?;
67    assert_owned_by(mint_info, &spl_token::ID)?;
68    assert_owned_by(token_account_info, &spl_token::ID)?;
69    assert_owned_by(master_edition_account_info, program_id)?;
70    assert_owned_by(master_metadata_account_info, program_id)?;
71
72    let master_metadata = Metadata::from_account_info(master_metadata_account_info)?;
73    let token_account: Account = assert_initialized(token_account_info)?;
74
75    if !ignore_owner_signer {
76        assert_signer(owner_account_info)?;
77
78        if token_account.owner != *owner_account_info.key {
79            return Err(MetadataError::InvalidOwner.into());
80        }
81    }
82
83    if token_account.mint != master_metadata.mint {
84        return Err(MetadataError::TokenAccountMintMismatchV2.into());
85    }
86
87    if token_account.amount < 1 {
88        return Err(MetadataError::NotEnoughTokens.into());
89    }
90
91    if !new_metadata_account_info.data_is_empty() {
92        return Err(MetadataError::AlreadyInitialized.into());
93    }
94
95    if !new_edition_account_info.data_is_empty() {
96        return Err(MetadataError::AlreadyInitialized.into());
97    }
98
99    let edition_number = edition.checked_div(EDITION_MARKER_BIT_SIZE).unwrap();
100    let as_string = edition_number.to_string();
101
102    let bump = assert_derivation(
103        program_id,
104        edition_marker_info,
105        &[
106            PREFIX.as_bytes(),
107            program_id.as_ref(),
108            master_metadata.mint.as_ref(),
109            EDITION.as_bytes(),
110            as_string.as_bytes(),
111        ],
112    )?;
113
114    if edition_marker_info.data_is_empty() {
115        let seeds = &[
116            PREFIX.as_bytes(),
117            program_id.as_ref(),
118            master_metadata.mint.as_ref(),
119            EDITION.as_bytes(),
120            as_string.as_bytes(),
121            &[bump],
122        ];
123
124        create_or_allocate_account_raw(
125            *program_id,
126            edition_marker_info,
127            system_account_info,
128            payer_account_info,
129            MAX_EDITION_MARKER_SIZE,
130            seeds,
131        )?;
132    }
133
134    let mut edition_marker = EditionMarker::from_account_info(edition_marker_info)?;
135    edition_marker.key = Key::EditionMarker;
136    if edition_marker.edition_taken(edition)? {
137        return Err(MetadataError::AlreadyInitialized.into());
138    } else {
139        edition_marker.insert_edition(edition)?
140    }
141    edition_marker.serialize(&mut *edition_marker_info.data.borrow_mut())?;
142
143    mint_limited_edition(
144        program_id,
145        master_metadata,
146        new_metadata_account_info,
147        new_edition_account_info,
148        master_edition_account_info,
149        mint_info,
150        mint_authority_info,
151        payer_account_info,
152        update_authority_info,
153        token_program_account_info,
154        system_account_info,
155        None,
156        Some(edition),
157    )?;
158    Ok(())
159}
160
161pub fn extract_edition_number_from_deprecated_reservation_list(
162    account: &AccountInfo,
163    mint_authority_info: &AccountInfo,
164) -> Result<u64, ProgramError> {
165    let mut reservation_list = get_reservation_list(account)?;
166
167    if let Some(supply_snapshot) = reservation_list.supply_snapshot() {
168        let mut prev_total_offsets: u64 = 0;
169        let mut offset: Option<u64> = None;
170        let mut reservations = reservation_list.reservations();
171        for i in 0..reservations.len() {
172            let mut reservation = &mut reservations[i];
173
174            if reservation.address == *mint_authority_info.key {
175                offset = Some(
176                    prev_total_offsets
177                        .checked_add(reservation.spots_remaining)
178                        .ok_or(MetadataError::NumericalOverflowError)?,
179                );
180                // You get your editions in reverse order but who cares, saves a byte
181                reservation.spots_remaining = reservation
182                    .spots_remaining
183                    .checked_sub(1)
184                    .ok_or(MetadataError::NumericalOverflowError)?;
185
186                reservation_list.set_reservations(reservations)?;
187                reservation_list.save(account)?;
188                break;
189            }
190
191            if reservation.address == solana_program::system_program::ID {
192                // This is an anchor point in the array...it means we reset our math to
193                // this offset because we may be missing information in between this point and
194                // the points before it.
195                prev_total_offsets = reservation.total_spots;
196            } else {
197                prev_total_offsets = prev_total_offsets
198                    .checked_add(reservation.total_spots)
199                    .ok_or(MetadataError::NumericalOverflowError)?;
200            }
201        }
202
203        match offset {
204            Some(val) => Ok(supply_snapshot
205                .checked_add(val)
206                .ok_or(MetadataError::NumericalOverflowError)?),
207            None => Err(MetadataError::AddressNotInReservation.into()),
208        }
209    } else {
210        Err(MetadataError::ReservationNotSet.into())
211    }
212}
213
214pub fn calculate_edition_number(
215    mint_authority_info: &AccountInfo,
216    reservation_list_info: Option<&AccountInfo>,
217    edition_override: Option<u64>,
218    me_supply: u64,
219) -> Result<u64, ProgramError> {
220    let edition = match reservation_list_info {
221        Some(account) => {
222            extract_edition_number_from_deprecated_reservation_list(account, mint_authority_info)?
223        }
224        None => {
225            if let Some(edit) = edition_override {
226                edit
227            } else {
228                me_supply
229                    .checked_add(1)
230                    .ok_or(MetadataError::NumericalOverflowError)?
231            }
232        }
233    };
234
235    Ok(edition)
236}
237
238fn get_max_supply_off_master_edition(
239    master_edition_account_info: &AccountInfo,
240) -> Result<Option<u64>, ProgramError> {
241    let data = master_edition_account_info.try_borrow_data()?;
242    // this is an option, 9 bytes, first is 0 means is none
243    if data[9] == 0 {
244        Ok(None)
245    } else {
246        let amount_data = array_ref![data, 10, 8];
247        Ok(Some(u64::from_le_bytes(*amount_data)))
248    }
249}
250
251pub fn get_supply_off_master_edition(
252    master_edition_account_info: &AccountInfo,
253) -> Result<u64, ProgramError> {
254    let data = master_edition_account_info.try_borrow_data()?;
255    // this is an option, 9 bytes, first is 0 means is none
256
257    let amount_data = array_ref![data, 1, 8];
258    Ok(u64::from_le_bytes(*amount_data))
259}
260
261pub fn calculate_supply_change<'a>(
262    master_edition_account_info: &AccountInfo<'a>,
263    reservation_list_info: Option<&AccountInfo<'a>>,
264    edition_override: Option<u64>,
265    current_supply: u64,
266) -> ProgramResult {
267    // Reservation lists are deprecated.
268    if reservation_list_info.is_some() {
269        return Err(MetadataError::ReservationListDeprecated.into());
270    }
271
272    // This function requires passing in the edition number.
273    if edition_override.is_none() {
274        return Err(MetadataError::EditionOverrideCannotBeZero.into());
275    }
276
277    let edition = edition_override.unwrap();
278
279    if edition == 0 {
280        return Err(MetadataError::EditionOverrideCannotBeZero.into());
281    }
282
283    let max_supply = get_max_supply_off_master_edition(master_edition_account_info)?;
284
285    // Previously, the code used edition override to set the supply to the highest edition number minted,
286    // instead of properly tracking the supply.
287    // Now, we increment this by one if the edition number is less than the max supply.
288    // This allows users to mint out missing edition numbers that are less than the supply, but
289    // tracks the supply correctly for new Master Editions.
290    let new_supply = if let Some(max_supply) = max_supply {
291        // We should never be able to mint an edition number that is greater than the max supply.
292        if edition > max_supply {
293            return Err(MetadataError::EditionNumberGreaterThanMaxSupply.into());
294        }
295
296        // If the current supply is less than the max supply, then we can mint another addition so we increment the supply.
297        if current_supply < max_supply {
298            current_supply
299                .checked_add(1)
300                .ok_or(MetadataError::NumericalOverflowError)?
301        }
302        // If it's the same as max supply, we don't increment, but we return the supply
303        // so we can mint out missing edition numbers in old editions that use the previous
304        // edition override logic.
305        //
306        // The EditionMarker bitmask ensures we don't remint the same number twice.
307        else {
308            current_supply
309        }
310    }
311    // With no max supply we can increment each time.
312    else {
313        current_supply
314            .checked_add(1)
315            .ok_or(MetadataError::NumericalOverflowError)?
316    };
317
318    // Doing old school serialization to protect CPU credits.
319    let edition_data = &mut master_edition_account_info.data.borrow_mut();
320    let output = array_mut_ref![edition_data, 0, MAX_MASTER_EDITION_LEN];
321
322    let (_key, supply, _the_rest) = mut_array_refs![output, 1, 8, 273];
323    *supply = new_supply.to_le_bytes();
324
325    Ok(())
326}
327
328#[allow(clippy::too_many_arguments)]
329pub fn mint_limited_edition<'a>(
330    program_id: &'a Pubkey,
331    master_metadata: Metadata,
332    new_metadata_account_info: &'a AccountInfo<'a>,
333    new_edition_account_info: &'a AccountInfo<'a>,
334    master_edition_account_info: &'a AccountInfo<'a>,
335    mint_info: &'a AccountInfo<'a>,
336    mint_authority_info: &'a AccountInfo<'a>,
337    payer_account_info: &'a AccountInfo<'a>,
338    update_authority_info: &'a AccountInfo<'a>,
339    token_program_account_info: &'a AccountInfo<'a>,
340    system_account_info: &'a AccountInfo<'a>,
341    // Only present with MasterEditionV1 calls, if present, use edition based off address in res list,
342    // otherwise, pull off the top
343    reservation_list_info: Option<&'a AccountInfo<'a>>,
344    // Only present with MasterEditionV2 calls, if present, means
345    // directing to a specific version, otherwise just pull off the top
346    edition_override: Option<u64>,
347) -> ProgramResult {
348    let me_supply = get_supply_off_master_edition(master_edition_account_info)?;
349    let mint_authority = get_mint_authority(mint_info)?;
350    let mint_supply = get_mint_supply(mint_info)?;
351    assert_mint_authority_matches_mint(&mint_authority, mint_authority_info)?;
352
353    assert_edition_valid(
354        program_id,
355        &master_metadata.mint,
356        master_edition_account_info,
357    )?;
358
359    let edition_seeds = &[
360        PREFIX.as_bytes(),
361        program_id.as_ref(),
362        mint_info.key.as_ref(),
363        EDITION.as_bytes(),
364    ];
365    let (edition_key, bump_seed) = Pubkey::find_program_address(edition_seeds, program_id);
366    if edition_key != *new_edition_account_info.key {
367        return Err(MetadataError::InvalidEditionKey.into());
368    }
369
370    if reservation_list_info.is_some() && edition_override.is_some() {
371        return Err(MetadataError::InvalidOperation.into());
372    }
373    calculate_supply_change(
374        master_edition_account_info,
375        reservation_list_info,
376        edition_override,
377        me_supply,
378    )?;
379
380    if mint_supply != 1 {
381        return Err(MetadataError::EditionsMustHaveExactlyOneToken.into());
382    }
383    let master_data = master_metadata.data;
384    // bundle data into v2
385    let data_v2 = DataV2 {
386        name: master_data.name,
387        symbol: master_data.symbol,
388        uri: master_data.uri,
389        seller_fee_basis_points: master_data.seller_fee_basis_points,
390        creators: master_data.creators,
391        collection: master_metadata.collection,
392        uses: master_metadata.uses.map(|u| Uses {
393            use_method: u.use_method,
394            remaining: u.total, // reset remaining uses per edition for extra fun
395            total: u.total,
396        }),
397    };
398    // create the metadata the normal way, except `allow_direct_creator_writes` is set to true
399    // because we are directly copying from the Master Edition metadata.
400
401    process_create_metadata_accounts_logic(
402        program_id,
403        CreateMetadataAccountsLogicArgs {
404            metadata_account_info: new_metadata_account_info,
405            mint_info,
406            mint_authority_info,
407            payer_account_info,
408            update_authority_info,
409            system_account_info,
410        },
411        data_v2,
412        true,
413        false,
414        true,
415        true,
416        None, // Not a collection parent
417    )?;
418    let edition_authority_seeds = &[
419        PREFIX.as_bytes(),
420        program_id.as_ref(),
421        mint_info.key.as_ref(),
422        EDITION.as_bytes(),
423        &[bump_seed],
424    ];
425
426    create_or_allocate_account_raw(
427        *program_id,
428        new_edition_account_info,
429        system_account_info,
430        payer_account_info,
431        MAX_EDITION_LEN,
432        edition_authority_seeds,
433    )?;
434
435    // Doing old school serialization to protect CPU credits.
436    let edition_data = &mut new_edition_account_info.data.borrow_mut();
437    let output = array_mut_ref![edition_data, 0, MAX_EDITION_LEN];
438
439    let (key, parent, edition, _padding) = mut_array_refs![output, 1, 32, 8, 200];
440
441    *key = [Key::EditionV1 as u8];
442    parent.copy_from_slice(master_edition_account_info.key.as_ref());
443
444    *edition = calculate_edition_number(
445        mint_authority_info,
446        reservation_list_info,
447        edition_override,
448        me_supply,
449    )?
450    .to_le_bytes();
451
452    // Now make sure this mint can never be used by anybody else.
453    transfer_mint_authority(
454        &edition_key,
455        new_edition_account_info,
456        mint_info,
457        mint_authority_info,
458        token_program_account_info,
459    )?;
460
461    Ok(())
462}
463
464/// Creates a new master edition account for the specified `edition_account_info` and
465/// `mint_info`. Master editions only exist for non-fungible assets, therefore the supply
466/// of the mint must thei either 0 or 1; any value higher than that will generate an
467/// error.
468///
469/// After a master edition is created, it becomes the mint authority of the mint account.
470pub fn create_master_edition<'a>(
471    program_id: &Pubkey,
472    edition_account_info: &'a AccountInfo<'a>,
473    mint_info: &'a AccountInfo<'a>,
474    update_authority_info: &'a AccountInfo<'a>,
475    mint_authority_info: &'a AccountInfo<'a>,
476    payer_account_info: &'a AccountInfo<'a>,
477    metadata_account_info: &'a AccountInfo<'a>,
478    token_program_info: &'a AccountInfo<'a>,
479    system_account_info: &'a AccountInfo<'a>,
480    max_supply: Option<u64>,
481) -> ProgramResult {
482    let metadata = Metadata::from_account_info(metadata_account_info)?;
483    let mint: Mint = assert_initialized(mint_info)?;
484
485    let bump_seed = assert_derivation(
486        program_id,
487        edition_account_info,
488        &[
489            PREFIX.as_bytes(),
490            program_id.as_ref(),
491            mint_info.key.as_ref(),
492            EDITION.as_bytes(),
493        ],
494    )?;
495
496    assert_token_program_matches_package(token_program_info)?;
497    assert_mint_authority_matches_mint(&mint.mint_authority, mint_authority_info)?;
498    assert_owned_by(metadata_account_info, program_id)?;
499    assert_owned_by(mint_info, &spl_token::ID)?;
500
501    if metadata.mint != *mint_info.key {
502        return Err(MetadataError::MintMismatch.into());
503    }
504
505    if mint.decimals != 0 {
506        return Err(MetadataError::EditionMintDecimalsShouldBeZero.into());
507    }
508
509    assert_update_authority_is_correct(&metadata, update_authority_info)?;
510
511    if mint.supply > 1 {
512        return Err(MetadataError::EditionsMustHaveExactlyOneToken.into());
513    }
514
515    let edition_authority_seeds = &[
516        PREFIX.as_bytes(),
517        program_id.as_ref(),
518        mint_info.key.as_ref(),
519        EDITION.as_bytes(),
520        &[bump_seed],
521    ];
522
523    create_or_allocate_account_raw(
524        *program_id,
525        edition_account_info,
526        system_account_info,
527        payer_account_info,
528        MAX_MASTER_EDITION_LEN,
529        edition_authority_seeds,
530    )?;
531
532    let mut edition = MasterEditionV2::from_account_info(edition_account_info)?;
533
534    edition.key = Key::MasterEditionV2;
535    edition.supply = 0;
536    edition.max_supply = max_supply;
537    edition.save(edition_account_info)?;
538
539    if metadata_account_info.is_writable {
540        let mut metadata_mut = Metadata::from_account_info(metadata_account_info)?;
541        metadata_mut.token_standard = Some(TokenStandard::NonFungible);
542        metadata_mut.save(&mut metadata_account_info.try_borrow_mut_data()?)?;
543    }
544
545    // while you can't mint only mint 1 token from your master record, you can
546    // mint as many limited editions as you like within your max supply
547    transfer_mint_authority(
548        edition_account_info.key,
549        edition_account_info,
550        mint_info,
551        mint_authority_info,
552        token_program_info,
553    )
554}