Skip to main content

sss_token/instructions/
burn.rs

1use anchor_lang::prelude::*;
2use anchor_spl::{
3    token_2022::{self, Burn, Token2022},
4    token_interface::TokenAccount,
5};
6
7use crate::errors::SssError;
8use crate::events::TokensBurned;
9use crate::state::*;
10use crate::utils::require_not_paused;
11
12#[derive(Accounts)]
13pub struct BurnTokens<'info> {
14    #[account(mut)]
15    pub burner: 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    /// CHECK: The Token-2022 mint account. Address validated against config, owner against Token-2022.
25    #[account(
26        mut,
27        address = config.mint,
28        constraint = mint.owner == &token_program.key() @ SssError::InvalidAuthority,
29    )]
30    pub mint: UncheckedAccount<'info>,
31
32    /// The token account to burn from. Must belong to this mint.
33    /// If self-burn: token::authority must equal burner.
34    /// If authority burn: burner must be master_authority and permanent delegate must be enabled.
35    #[account(
36        mut,
37        token::mint = config.mint,
38        token::token_program = token_program,
39    )]
40    pub burn_token_account: InterfaceAccount<'info, TokenAccount>,
41
42    pub token_program: Program<'info, Token2022>,
43}
44
45pub fn handler(ctx: Context<BurnTokens>, amount: u64) -> Result<()> {
46    require!(amount > 0, SssError::BurnAmountZero);
47
48    let config = &ctx.accounts.config;
49    require_not_paused(config)?;
50
51    require!(
52        ctx.accounts.burn_token_account.amount >= amount,
53        SssError::InsufficientBalance
54    );
55
56    let clock = Clock::get()?;
57    let burner_key = ctx.accounts.burner.key();
58    let token_account_owner = ctx.accounts.burn_token_account.owner;
59
60    if burner_key == token_account_owner {
61        // Path 1: Self-burn — token holder burns their own tokens
62        token_2022::burn(
63            CpiContext::new(
64                ctx.accounts.token_program.to_account_info(),
65                Burn {
66                    mint: ctx.accounts.mint.to_account_info(),
67                    from: ctx.accounts.burn_token_account.to_account_info(),
68                    authority: ctx.accounts.burner.to_account_info(),
69                },
70            ),
71            amount,
72        )?;
73    } else if burner_key == config.master_authority {
74        // Path 2: Authority burn — master authority burns from any account via permanent delegate
75        require!(
76            config.enable_permanent_delegate,
77            SssError::FeatureNotEnabled
78        );
79
80        let mint_key = config.mint;
81        let signer_seeds: &[&[&[u8]]] = &[&[
82            StablecoinConfig::SEED_PREFIX,
83            mint_key.as_ref(),
84            &[config.bump],
85        ]];
86
87        token_2022::burn(
88            CpiContext::new_with_signer(
89                ctx.accounts.token_program.to_account_info(),
90                Burn {
91                    mint: ctx.accounts.mint.to_account_info(),
92                    from: ctx.accounts.burn_token_account.to_account_info(),
93                    authority: ctx.accounts.config.to_account_info(), // permanent delegate
94                },
95                signer_seeds,
96            ),
97            amount,
98        )?;
99    } else {
100        return Err(SssError::InvalidAuthority.into());
101    }
102
103    // Update config stats
104    let config = &mut ctx.accounts.config;
105    config.total_burned = config
106        .total_burned
107        .checked_add(amount)
108        .ok_or(SssError::Overflow)?;
109    config.updated_at = clock.unix_timestamp;
110
111    emit!(TokensBurned {
112        config: config.key(),
113        burner: ctx.accounts.burner.key(),
114        from: ctx.accounts.burn_token_account.key(),
115        amount,
116        total_burned: config.total_burned,
117        timestamp: clock.unix_timestamp,
118    });
119
120    Ok(())
121}