token-acl 0.2.1

The Token ACL program
Documentation
use solana_cpi::invoke_signed;
use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
use solana_program_error::{ProgramError, ProgramResult};
use spl_token_2022::{extension::PodStateWithExtensions, instruction::AuthorityType, pod::PodMint};

use crate::{
    error::TokenAclError,
    state::{load_mint_config, MintConfig},
};

pub struct DeleteConfig<'a> {
    pub authority: &'a AccountInfo<'a>,
    pub receiver: &'a AccountInfo<'a>,
    pub mint: &'a AccountInfo<'a>,
    pub mint_config: &'a AccountInfo<'a>,
    pub token_program: &'a AccountInfo<'a>,
}

impl DeleteConfig<'_> {
    pub const DISCRIMINATOR: u8 = 3;

    pub fn process(&self, remaining_data: &[u8]) -> ProgramResult {
        if remaining_data.len() != 32 {
            return Err(ProgramError::InvalidInstructionData);
        }
        let new_freeze_authority =
            Pubkey::try_from(remaining_data).map_err(|_| ProgramError::InvalidInstructionData)?;

        // only set the freeze authority if the mint_config is still the freeze authority
        // this also ensures that the mint still exists and is initialized
        let mint_data = self.mint.data.borrow_mut();
        let mint = PodStateWithExtensions::<PodMint>::unpack(&mint_data);
        let set_freeze_authority = mint
            .and_then(|mint| {
                Ok(
                    mint.base.freeze_authority.unwrap_or(Pubkey::default())
                        == *self.mint_config.key,
                )
            })
            .unwrap_or(false);
        drop(mint_data);

        let bump_seed = {
            let data = &mut self.mint_config.data.borrow_mut();
            let config = load_mint_config(data)?;

            if config.freeze_authority != *self.authority.key {
                return Err(TokenAclError::InvalidAuthority.into());
            }

            if config.mint != *self.mint.key {
                return Err(TokenAclError::InvalidTokenMint.into());
            }

            [config.bump]
        };

        if set_freeze_authority {
            let seeds = [MintConfig::SEED_PREFIX, self.mint.key.as_ref(), &bump_seed];

            let ix = spl_token_2022::instruction::set_authority(
                self.token_program.key,
                self.mint.key,
                Some(&new_freeze_authority),
                AuthorityType::FreezeAccount,
                self.mint_config.key,
                &[],
            )?;
            invoke_signed(
                &ix,
                &[self.mint.clone(), self.mint_config.clone()],
                &[&seeds],
            )?;
        }

        **self.receiver.try_borrow_mut_lamports()? += self.mint_config.lamports();
        **self.mint_config.try_borrow_mut_lamports()? = 0;
        self.mint_config.resize(0)?;
        self.mint_config.assign(&Pubkey::default());

        Ok(())
    }
}

impl<'a> TryFrom<&'a [AccountInfo<'a>]> for DeleteConfig<'a> {
    type Error = ProgramError;

    fn try_from(accounts: &'a [AccountInfo<'a>]) -> Result<Self, Self::Error> {
        let [authority, receiver, mint, mint_config, token_program] = &accounts else {
            return Err(ProgramError::InvalidInstructionData);
        };

        if !authority.is_signer {
            return Err(TokenAclError::InvalidAuthority.into());
        }

        if !spl_token_2022::check_id(token_program.key) {
            return Err(TokenAclError::InvalidTokenProgram.into());
        }

        Ok(Self {
            authority,
            receiver,
            mint,
            mint_config,
            token_program,
        })
    }
}