use solana_cpi::{invoke, invoke_signed};
use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
use solana_program_error::{ProgramError, ProgramResult};
use solana_rent::Rent;
use solana_sysvar::Sysvar;
use spl_pod::{bytemuck::pod_from_bytes_mut, primitives::PodBool};
use spl_token_2022::{
extension::{
default_account_state::DefaultAccountState, BaseStateWithExtensions, PodStateWithExtensions,
},
instruction::AuthorityType,
pod::PodMint,
};
use crate::{error::TokenAclError, state::MintConfig};
pub struct CreateConfig<'a> {
pub payer: &'a AccountInfo<'a>,
pub authority: &'a AccountInfo<'a>,
pub mint: &'a AccountInfo<'a>,
pub mint_config: &'a AccountInfo<'a>,
pub system_program: &'a AccountInfo<'a>,
pub token_program: &'a AccountInfo<'a>,
pub config_bump: u8,
}
impl CreateConfig<'_> {
pub const DISCRIMINATOR: u8 = 0;
pub fn process(&self, remaining_data: &[u8]) -> ProgramResult {
if remaining_data.len() != 32 {
return Err(ProgramError::InvalidInstructionData);
}
let gating_program =
Pubkey::try_from(remaining_data).map_err(|_| ProgramError::InvalidInstructionData)?;
let mint_data = self.mint.data.borrow_mut();
let mint = PodStateWithExtensions::<PodMint>::unpack(&mint_data)?;
mint.get_extension::<DefaultAccountState>()
.map_err(|_| Into::<ProgramError>::into(TokenAclError::InvalidTokenMint))?;
let freeze_authority = mint
.base
.freeze_authority
.ok_or(Into::<ProgramError>::into(TokenAclError::InvalidTokenMint))?;
if freeze_authority != *self.authority.key {
return Err(TokenAclError::InvalidAuthority.into());
}
drop(mint_data);
let lamports = Rent::get()?.minimum_balance(MintConfig::LEN);
if self.mint_config.lamports() < lamports {
let diff = lamports - self.mint_config.lamports();
let ix = solana_system_interface::instruction::transfer(
self.payer.key,
self.mint_config.key,
diff,
);
invoke(&ix, &[self.payer.clone(), self.mint_config.clone()])?;
}
let bump_seed = [self.config_bump];
let seeds = [MintConfig::SEED_PREFIX, self.mint.key.as_ref(), &bump_seed];
let allocate_ix = solana_system_interface::instruction::allocate(
self.mint_config.key,
MintConfig::LEN as u64,
);
invoke_signed(
&allocate_ix,
&[self.payer.clone(), self.mint_config.clone()],
&[&seeds],
)?;
let assign_ix =
solana_system_interface::instruction::assign(self.mint_config.key, &crate::ID);
invoke_signed(
&assign_ix,
&[self.payer.clone(), self.mint_config.clone()],
&[&seeds],
)?;
let data = &mut self.mint_config.data.borrow_mut();
let config = pod_from_bytes_mut::<MintConfig>(data)?;
config.discriminator = MintConfig::DISCRIMINATOR;
config.mint = *self.mint.key;
config.freeze_authority = *self.authority.key;
config.gating_program = gating_program;
config.bump = self.config_bump;
config.enable_permissionless_freeze = PodBool::from_bool(false);
config.enable_permissionless_thaw = PodBool::from_bool(false);
let ix = spl_token_2022::instruction::set_authority(
self.token_program.key,
self.mint.key,
Some(self.mint_config.key),
AuthorityType::FreezeAccount,
self.authority.key,
&[],
)?;
invoke(&ix, &[self.mint.clone(), self.authority.clone()])?;
Ok(())
}
}
impl<'a> TryFrom<&'a [AccountInfo<'a>]> for CreateConfig<'a> {
type Error = ProgramError;
fn try_from(accounts: &'a [AccountInfo<'a>]) -> Result<Self, Self::Error> {
let [payer, authority, mint, mint_config, system_program, token_program] = &accounts else {
return Err(ProgramError::InvalidInstructionData);
};
if !authority.is_signer {
return Err(TokenAclError::InvalidAuthority.into());
}
let (expected_mint_config_pk, config_bump) =
Pubkey::find_program_address(&[MintConfig::SEED_PREFIX, mint.key.as_ref()], &crate::ID);
if *mint_config.key != expected_mint_config_pk {
return Err(TokenAclError::InvalidMintConfig.into());
}
if !solana_system_interface::program::check_id(system_program.key) {
return Err(TokenAclError::InvalidSystemProgram.into());
}
if !spl_token_2022::check_id(token_program.key) {
return Err(TokenAclError::InvalidTokenProgram.into());
}
if !spl_token_2022::check_id(mint.owner) {
return Err(TokenAclError::InvalidTokenProgram.into());
}
Ok(Self {
payer,
authority,
mint,
mint_config,
system_program,
token_program,
config_bump,
})
}
}