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 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 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 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 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 if reservation_list_info.is_some() {
269 return Err(MetadataError::ReservationListDeprecated.into());
270 }
271
272 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 let new_supply = if let Some(max_supply) = max_supply {
291 if edition > max_supply {
293 return Err(MetadataError::EditionNumberGreaterThanMaxSupply.into());
294 }
295
296 if current_supply < max_supply {
298 current_supply
299 .checked_add(1)
300 .ok_or(MetadataError::NumericalOverflowError)?
301 }
302 else {
308 current_supply
309 }
310 }
311 else {
313 current_supply
314 .checked_add(1)
315 .ok_or(MetadataError::NumericalOverflowError)?
316 };
317
318 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 reservation_list_info: Option<&'a AccountInfo<'a>>,
344 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 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, total: u.total,
396 }),
397 };
398 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, )?;
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 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 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
464pub 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 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}