Skip to main content

sss_token/instructions/
blacklist_remove.rs

1use anchor_lang::prelude::*;
2use anchor_spl::{
3    token_2022::{self, ThawAccount, Token2022},
4    token_interface::TokenAccount,
5};
6
7use crate::errors::SssError;
8use crate::events::BlacklistRemoved;
9use crate::state::*;
10use crate::utils::require_role;
11
12#[derive(Accounts)]
13pub struct BlacklistRemove<'info> {
14    #[account(mut)]
15    pub authority: Signer<'info>,
16
17    #[account(
18        mut,
19        seeds = [StablecoinConfig::SEED_PREFIX, config.mint.as_ref()],
20        bump = config.bump,
21    )]
22    pub config: Account<'info, StablecoinConfig>,
23
24    #[account(
25        seeds = [RoleRegistry::SEED_PREFIX, config.key().as_ref()],
26        bump = role_registry.bump,
27        constraint = role_registry.config == config.key() @ SssError::InvalidAuthority,
28    )]
29    pub role_registry: Account<'info, RoleRegistry>,
30
31    #[account(
32        mut,
33        close = authority,
34        seeds = [BlacklistEntry::SEED_PREFIX, config.key().as_ref(), blacklist_entry.blocked_address.as_ref()],
35        bump = blacklist_entry.bump,
36        constraint = blacklist_entry.config == config.key(),
37    )]
38    pub blacklist_entry: Account<'info, BlacklistEntry>,
39
40    /// CHECK: The Token-2022 mint account. Address validated against config, owner against Token-2022.
41    #[account(
42        address = config.mint,
43        constraint = mint.owner == &token_program.key() @ SssError::InvalidAuthority,
44    )]
45    pub mint: UncheckedAccount<'info>,
46
47    /// The target's token account to thaw — must belong to the blacklisted address
48    #[account(
49        mut,
50        token::mint = config.mint,
51        token::authority = blacklist_entry.blocked_address,
52        token::token_program = token_program,
53    )]
54    pub target_token_account: InterfaceAccount<'info, TokenAccount>,
55
56    pub token_program: Program<'info, Token2022>,
57}
58
59pub fn handler(ctx: Context<BlacklistRemove>) -> Result<()> {
60    let config = &ctx.accounts.config;
61
62    require!(
63        config.enable_permanent_delegate,
64        SssError::BlacklistNotEnabled
65    );
66
67    require_role(
68        &ctx.accounts.role_registry,
69        &ctx.accounts.authority.key(),
70        Role::Blacklister,
71    )?;
72
73    let clock = Clock::get()?;
74    let unblocked_address = ctx.accounts.blacklist_entry.blocked_address;
75
76    // Thaw the target's token account before closing blacklist entry
77    let mint_key = config.mint;
78    let signer_seeds: &[&[&[u8]]] = &[&[
79        StablecoinConfig::SEED_PREFIX,
80        mint_key.as_ref(),
81        &[config.bump],
82    ]];
83
84    token_2022::thaw_account(CpiContext::new_with_signer(
85        ctx.accounts.token_program.to_account_info(),
86        ThawAccount {
87            account: ctx.accounts.target_token_account.to_account_info(),
88            mint: ctx.accounts.mint.to_account_info(),
89            authority: ctx.accounts.config.to_account_info(),
90        },
91        signer_seeds,
92    ))?;
93
94    // Blacklist entry is closed via the `close` constraint above
95
96    emit!(BlacklistRemoved {
97        config: config.key(),
98        unblocked_address,
99        removed_by: ctx.accounts.authority.key(),
100        timestamp: clock.unix_timestamp,
101    });
102
103    let config = &mut ctx.accounts.config;
104    config.updated_at = clock.unix_timestamp;
105
106    Ok(())
107}