use anchor_lang::{prelude::*, solana_program::sysvar};
use mpl_token_metadata::{accounts::Metadata, types::TokenStandard};
use mpl_utils::resize_or_reallocate_account_raw;
use crate::{
approve_metadata_delegate, assert_token_standard, cmp_pubkeys,
constants::{AUTHORITY_SEED, MPL_TOKEN_AUTH_RULES_PROGRAM, RULE_SET_LENGTH, SET, UNSET},
revoke_collection_authority_helper, AccountVersion, ApproveMetadataDelegateHelperAccounts,
CandyError, CandyMachine, RevokeCollectionAuthorityHelperAccounts,
};
pub fn set_token_standard(ctx: Context<SetTokenStandard>, token_standard: u8) -> Result<()> {
let accounts = ctx.accounts;
let candy_machine = &mut accounts.candy_machine;
let collection_metadata_info = &accounts.collection_metadata;
let collection_metadata: Metadata =
Metadata::try_from(&collection_metadata_info.to_account_info())?;
if !cmp_pubkeys(&collection_metadata.mint, accounts.collection_mint.key) {
return err!(CandyError::MintMismatch);
}
assert_token_standard(token_standard)?;
if matches!(candy_machine.version, AccountVersion::V1) {
let collection_authority_record = accounts
.collection_authority_record
.as_ref()
.ok_or(CandyError::MissingCollectionAuthorityRecord)?;
let revoke_accounts = RevokeCollectionAuthorityHelperAccounts {
authority_pda: accounts.authority_pda.to_account_info(),
collection_authority_record: collection_authority_record.to_account_info(),
collection_metadata: accounts.collection_metadata.to_account_info(),
collection_mint: accounts.collection_mint.to_account_info(),
token_metadata_program: accounts.token_metadata_program.to_account_info(),
};
revoke_collection_authority_helper(
revoke_accounts,
candy_machine.key(),
*ctx.bumps.get("authority_pda").unwrap(),
collection_metadata.token_standard,
)?;
let delegate_accounts = ApproveMetadataDelegateHelperAccounts {
token_metadata_program: accounts.token_metadata_program.to_account_info(),
authority_pda: accounts.authority_pda.to_account_info(),
collection_metadata: accounts.collection_metadata.to_account_info(),
collection_mint: accounts.collection_mint.to_account_info(),
collection_update_authority: accounts.collection_update_authority.to_account_info(),
delegate_record: accounts.collection_delegate_record.to_account_info(),
payer: accounts.payer.to_account_info(),
system_program: accounts.system_program.to_account_info(),
sysvar_instructions: accounts.sysvar_instructions.to_account_info(),
authorization_rules_program: accounts
.authorization_rules_program
.to_owned()
.map(|authorization_rules_program| authorization_rules_program.to_account_info()),
authorization_rules: accounts
.authorization_rules
.to_owned()
.map(|authorization_rules| authorization_rules.to_account_info()),
};
approve_metadata_delegate(delegate_accounts)?;
candy_machine.version = AccountVersion::V2;
}
msg!(
"Changing token standard from {} to {}",
candy_machine.token_standard,
token_standard
);
candy_machine.token_standard = token_standard;
let required_length = candy_machine.data.get_space_for_candy()?;
let candy_machine_info = candy_machine.to_account_info();
if token_standard == TokenStandard::ProgrammableNonFungible as u8 {
if candy_machine_info.data_len() < (required_length + RULE_SET_LENGTH + 1) {
msg!("Allocating space to store the rule set");
resize_or_reallocate_account_raw(
&candy_machine_info,
&accounts.payer.to_account_info(),
&accounts.system_program.to_account_info(),
required_length + (1 + RULE_SET_LENGTH),
)?;
}
let mut account_data = candy_machine_info.data.borrow_mut();
if let Some(rule_set_info) = &accounts.rule_set {
let rule_set = rule_set_info.key();
account_data[required_length] = SET;
msg!("Storing rule set pubkey");
let index = required_length + 1;
let mut storage = &mut account_data[index..index + RULE_SET_LENGTH];
rule_set.serialize(&mut storage)?;
} else {
account_data[required_length] = UNSET;
let index = required_length + 1;
account_data[index..index + RULE_SET_LENGTH].fill(0);
msg!("Rule set cleared");
}
} else if required_length < candy_machine_info.data_len() {
let end_index = candy_machine_info.data_len();
let mut account_data = candy_machine_info.data.borrow_mut();
account_data[required_length..end_index].fill(0);
msg!("Remaining account bytes cleared");
}
Ok(())
}
#[derive(Accounts)]
pub struct SetTokenStandard<'info> {
#[account(mut, has_one = authority, has_one = collection_mint)]
candy_machine: Account<'info, CandyMachine>,
authority: Signer<'info>,
#[account(
mut,
seeds = [AUTHORITY_SEED.as_bytes(), candy_machine.to_account_info().key.as_ref()],
bump
)]
authority_pda: UncheckedAccount<'info>,
#[account(mut)]
payer: Signer<'info>,
#[account(owner = MPL_TOKEN_AUTH_RULES_PROGRAM)]
rule_set: Option<UncheckedAccount<'info>>,
#[account(mut)]
collection_delegate_record: UncheckedAccount<'info>,
collection_mint: UncheckedAccount<'info>,
#[account(mut)]
collection_metadata: UncheckedAccount<'info>,
#[account(mut)]
collection_authority_record: Option<UncheckedAccount<'info>>,
collection_update_authority: Signer<'info>,
#[account(address = mpl_token_metadata::ID)]
token_metadata_program: UncheckedAccount<'info>,
system_program: Program<'info, System>,
#[account(address = sysvar::instructions::id())]
sysvar_instructions: UncheckedAccount<'info>,
#[account(address = MPL_TOKEN_AUTH_RULES_PROGRAM)]
authorization_rules_program: Option<UncheckedAccount<'info>>,
#[account(owner = MPL_TOKEN_AUTH_RULES_PROGRAM)]
authorization_rules: Option<UncheckedAccount<'info>>,
}