use anchor_lang::prelude::*;
use arrayref::array_ref;
use mpl_core::{
self,
accounts::BaseCollectionV1,
fetch_plugin,
instructions::CreateV1CpiBuilder,
types::{PluginAuthorityPair, PluginType, UpdateDelegate},
};
use solana_program::sysvar;
use crate::{
constants::{AUTHORITY_SEED, EMPTY_STR, HIDDEN_SECTION, NULL_STRING},
utils::*,
CandyError, CandyMachine, ConfigLine, MintAssetArgs,
};
pub(crate) struct MintAccounts<'info> {
pub authority_pda: AccountInfo<'info>,
pub payer: AccountInfo<'info>,
pub asset_owner: AccountInfo<'info>,
pub asset: AccountInfo<'info>,
pub collection: AccountInfo<'info>,
pub mpl_core_program: AccountInfo<'info>,
pub system_program: AccountInfo<'info>,
pub sysvar_instructions: Option<AccountInfo<'info>>,
pub recent_slothashes: AccountInfo<'info>,
}
pub fn mint_asset<'info>(
ctx: Context<'_, '_, '_, 'info, MintAsset<'info>>,
mint_args: MintAssetArgs,
) -> Result<()> {
let accounts = MintAccounts {
authority_pda: ctx.accounts.authority_pda.to_account_info(),
collection: ctx.accounts.collection.to_account_info(),
asset_owner: ctx.accounts.asset_owner.to_account_info(),
asset: ctx.accounts.asset.to_account_info(),
payer: ctx.accounts.payer.to_account_info(),
recent_slothashes: ctx.accounts.recent_slothashes.to_account_info(),
system_program: ctx.accounts.system_program.to_account_info(),
mpl_core_program: ctx.accounts.mpl_core_program.to_account_info(),
sysvar_instructions: Some(ctx.accounts.sysvar_instructions.to_account_info()),
};
process_mint_asset(
&mut ctx.accounts.candy_machine,
accounts,
ctx.bumps["authority_pda"],
&mint_args,
)
}
pub(crate) fn process_mint_asset(
candy_machine: &mut Box<Account<'_, CandyMachine>>,
accounts: MintAccounts,
bump: u8,
mint_args: &MintAssetArgs,
) -> Result<()> {
if !accounts.asset.data_is_empty() {
return err!(CandyError::MetadataAccountMustBeEmpty);
}
if candy_machine.items_redeemed >= candy_machine.data.items_available {
return err!(CandyError::CandyMachineEmpty);
}
if !cmp_pubkeys(&accounts.collection.key(), &candy_machine.collection_mint) {
return err!(CandyError::CollectionKeyMismatch);
}
if !cmp_pubkeys(accounts.collection.owner, &mpl_core::ID) {
return err!(CandyError::IncorrectOwner);
}
let (auth, _, _) = fetch_plugin::<BaseCollectionV1, UpdateDelegate>(
&accounts.collection,
PluginType::UpdateDelegate,
)?;
assert_plugin_pubkey_authority(&auth, &accounts.authority_pda.key())?;
let recent_slothashes = &accounts.recent_slothashes;
let data = recent_slothashes.data.borrow();
let most_recent = array_ref![data, 12, 8];
let clock = Clock::get()?;
let seed = u64::from_le_bytes(*most_recent).saturating_sub(clock.unix_timestamp as u64);
let remainder: usize = seed
.checked_rem(candy_machine.data.items_available - candy_machine.items_redeemed)
.ok_or(CandyError::NumericalOverflowError)? as usize;
let config_line = get_config_line(candy_machine, remainder, candy_machine.items_redeemed)?;
candy_machine.items_redeemed = candy_machine
.items_redeemed
.checked_add(1)
.ok_or(CandyError::NumericalOverflowError)?;
drop(data);
create_and_mint(
candy_machine,
accounts,
bump,
config_line,
&mint_args.plugins,
)
}
pub fn get_config_line(
candy_machine: &Account<'_, CandyMachine>,
index: usize,
mint_number: u64,
) -> Result<ConfigLine> {
if let Some(hs) = &candy_machine.data.hidden_settings {
return Ok(ConfigLine {
name: replace_patterns(hs.name.clone(), mint_number as usize),
uri: replace_patterns(hs.uri.clone(), mint_number as usize),
});
}
let settings = if let Some(settings) = &candy_machine.data.config_line_settings {
settings
} else {
return err!(CandyError::MissingConfigLinesSettings);
};
let account_info = candy_machine.to_account_info();
let mut account_data = account_info.data.borrow_mut();
let config_count = get_config_count(&account_data)? as u64;
if config_count != candy_machine.data.items_available {
return err!(CandyError::NotFullyLoaded);
}
let value_to_use = if settings.is_sequential {
mint_number as usize
} else {
let items_available = candy_machine.data.items_available;
let indices_start = HIDDEN_SECTION
+ 4
+ (items_available as usize) * candy_machine.data.get_config_line_size()
+ (items_available
.checked_div(8)
.ok_or(CandyError::NumericalOverflowError)?
+ 1) as usize;
let mint_index = indices_start + index * 4;
let value_to_use = u32::from_le_bytes(*array_ref![account_data, mint_index, 4]) as usize;
let last_index = indices_start + ((items_available - mint_number - 1) * 4) as usize;
let last_value = u32::from_le_bytes(*array_ref![account_data, last_index, 4]);
account_data[mint_index..mint_index + 4].copy_from_slice(&u32::to_le_bytes(last_value));
value_to_use
};
let mut position =
HIDDEN_SECTION + 4 + value_to_use * candy_machine.data.get_config_line_size();
let name_length = settings.name_length as usize;
let uri_length = settings.uri_length as usize;
let name = if name_length > 0 {
let name_slice: &mut [u8] = &mut account_data[position..position + name_length];
let name = String::from_utf8(name_slice.to_vec())
.map_err(|_| CandyError::CouldNotRetrieveConfigLineData)?;
name.trim_end_matches(NULL_STRING).to_string()
} else {
EMPTY_STR.to_string()
};
position += name_length;
let uri = if uri_length > 0 {
let uri_slice: &mut [u8] = &mut account_data[position..position + uri_length];
let uri = String::from_utf8(uri_slice.to_vec())
.map_err(|_| CandyError::CouldNotRetrieveConfigLineData)?;
uri.trim_end_matches(NULL_STRING).to_string()
} else {
EMPTY_STR.to_string()
};
let complete_name = replace_patterns(settings.prefix_name.clone(), value_to_use) + &name;
let complete_uri = replace_patterns(settings.prefix_uri.clone(), value_to_use) + &uri;
Ok(ConfigLine {
name: complete_name,
uri: complete_uri,
})
}
fn create_and_mint(
candy_machine: &mut Box<Account<'_, CandyMachine>>,
accounts: MintAccounts,
bump: u8,
config_line: ConfigLine,
plugins: &[PluginAuthorityPair],
) -> Result<()> {
let candy_machine_key = candy_machine.key();
let authority_seeds = [
AUTHORITY_SEED.as_bytes(),
candy_machine_key.as_ref(),
&[bump],
];
let _sysvar_instructions_info = accounts
.sysvar_instructions
.as_ref()
.ok_or(CandyError::MissingInstructionsSysvar)?;
CreateV1CpiBuilder::new(&accounts.mpl_core_program)
.payer(&accounts.payer)
.asset(&accounts.asset)
.owner(Some(&accounts.asset_owner))
.name(config_line.name)
.uri(config_line.uri)
.collection(Some(&accounts.collection))
.plugins(plugins.to_vec())
.data_state(mpl_core::types::DataState::AccountState)
.authority(Some(&accounts.authority_pda))
.system_program(&accounts.system_program)
.invoke_signed(&[&authority_seeds])
.map_err(|error| error.into())
}
#[derive(Accounts)]
pub struct MintAsset<'info> {
#[account(mut, has_one = mint_authority)]
candy_machine: Box<Account<'info, CandyMachine>>,
#[account(mut, seeds = [AUTHORITY_SEED.as_bytes(), candy_machine.key().as_ref()], bump)]
authority_pda: UncheckedAccount<'info>,
mint_authority: Signer<'info>,
#[account(mut)]
payer: Signer<'info>,
asset_owner: UncheckedAccount<'info>,
#[account(mut)]
asset: Signer<'info>,
#[account(mut)]
collection: UncheckedAccount<'info>,
#[account(address = mpl_core::ID)]
mpl_core_program: UncheckedAccount<'info>,
system_program: Program<'info, System>,
#[account(address = sysvar::instructions::id())]
sysvar_instructions: UncheckedAccount<'info>,
#[account(address = sysvar::slot_hashes::id())]
recent_slothashes: UncheckedAccount<'info>,
}