Skip to main content

sss_token/instructions/
allowlist_remove.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::SssError;
4use crate::events::AllowlistRemoved;
5use crate::state::*;
6use crate::utils::require_role;
7
8#[derive(Accounts)]
9pub struct AllowlistRemove<'info> {
10    #[account(mut)]
11    pub authority: Signer<'info>,
12
13    #[account(
14        mut,
15        seeds = [StablecoinConfig::SEED_PREFIX, config.mint.as_ref()],
16        bump = config.bump,
17    )]
18    pub config: Account<'info, StablecoinConfig>,
19
20    #[account(
21        seeds = [RoleRegistry::SEED_PREFIX, config.key().as_ref()],
22        bump = role_registry.bump,
23        constraint = role_registry.config == config.key() @ SssError::InvalidAuthority,
24    )]
25    pub role_registry: Account<'info, RoleRegistry>,
26
27    /// CHECK: The address being removed from the allowlist.
28    pub address_to_remove: UncheckedAccount<'info>,
29
30    /// CHECK: PDA address is validated by seeds/bump. Data is validated and closed manually
31    /// in the handler so missing accounts can return AllowlistEntryNotFound.
32    #[account(
33        mut,
34        seeds = [AllowlistEntry::SEED_PREFIX, config.key().as_ref(), address_to_remove.key().as_ref()],
35        bump,
36    )]
37    pub allowlist_entry: UncheckedAccount<'info>,
38}
39
40pub fn handler(ctx: Context<AllowlistRemove>) -> Result<()> {
41    let config = &ctx.accounts.config;
42
43    require!(
44        config.enable_permanent_delegate,
45        SssError::BlacklistNotEnabled
46    );
47
48    require_role(
49        &ctx.accounts.role_registry,
50        &ctx.accounts.authority.key(),
51        Role::Blacklister,
52    )?;
53
54    let allowlist_entry_info = ctx.accounts.allowlist_entry.to_account_info();
55    require!(
56        allowlist_entry_info.owner == &crate::ID && !allowlist_entry_info.data_is_empty(),
57        SssError::AllowlistEntryNotFound
58    );
59
60    let clock = Clock::get()?;
61    let allowlist_entry = {
62        let data = allowlist_entry_info.try_borrow_data()?;
63        let mut data_slice: &[u8] = &data;
64        AllowlistEntry::try_deserialize(&mut data_slice)?
65    };
66    require!(
67        allowlist_entry.config == config.key(),
68        SssError::InvalidAuthority
69    );
70    let address = allowlist_entry.address;
71    let lamports = allowlist_entry_info.lamports();
72    **ctx
73        .accounts
74        .authority
75        .to_account_info()
76        .try_borrow_mut_lamports()? = ctx
77        .accounts
78        .authority
79        .to_account_info()
80        .lamports()
81        .checked_add(lamports)
82        .ok_or(SssError::Overflow)?;
83    **allowlist_entry_info.try_borrow_mut_lamports()? = 0;
84    allowlist_entry_info.assign(&anchor_lang::solana_program::system_program::ID);
85    allowlist_entry_info.realloc(0, false)?;
86
87    emit!(AllowlistRemoved {
88        config: config.key(),
89        address,
90        removed_by: ctx.accounts.authority.key(),
91        timestamp: clock.unix_timestamp,
92    });
93
94    let config = &mut ctx.accounts.config;
95    config.updated_at = clock.unix_timestamp;
96
97    Ok(())
98}