sablier_thread_program/instructions/
thread_delete.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use {
    crate::{constants::SEED_THREAD, errors::SablierError, state::*},
    anchor_lang::{prelude::*, solana_program::system_program},
};

/// Accounts required by the `thread_delete` instruction.
#[derive(Accounts)]
pub struct ThreadDelete<'info> {
    /// The authority (owner) of the thread.
    pub authority: Signer<'info>,

    /// The address to return the data rent lamports to.
    #[account(mut)]
    pub close_to: SystemAccount<'info>,

    /// The thread to be deleted.
    /// CHECK: Validation checks are performed during instruction processing.
    #[account(mut)]
    pub thread: UncheckedAccount<'info>,
}

pub fn handler(ctx: Context<ThreadDelete>) -> Result<()> {
    let thread = &ctx.accounts.thread;
    let close_to = &ctx.accounts.close_to;

    // We want this instruction not to fail if the thread is already deleted or inexistent.
    // As such, all checks are done in the code that than in anchor (see commented code above)
    // First, must try to deserialize the thread.

    // Get either V1 or V2 thread - If the provided thread does not exist, print an error message and return Ok.
    let thread = match Thread::try_deserialize_unchecked(&mut thread.data.borrow_mut().as_ref()) {
        Ok(t) => t,
        Err(_) => {
            msg!("Not a thread or account does not exist");
            return Ok(());
        }
    };

    // Preliminary checks
    {
        // Verify the authority
        let authority_key = ctx.accounts.authority.key;
        let thread_key = ctx.accounts.thread.key;

        require!(
            thread.authority.eq(authority_key) || authority_key.eq(thread_key),
            SablierError::InvalidThreadAuthority
        );

        // Verify the account provided
        let thread_account = &ctx.accounts.thread;
        {
            // Verify the account is initialized
            require!(
                thread_account.owner != &system_program::ID && thread_account.lamports() > 0,
                SablierError::InvalidThreadAccount
            );

            // Verify the account is owned by the program
            require!(
                thread_account.owner == &crate::ID,
                SablierError::InvalidThreadAccount
            );

            // Verify the seed derivation
            let default_vec = Vec::new();
            let thread_bump = thread.bump.to_le_bytes();
            let seed = [
                SEED_THREAD,
                thread.authority.as_ref(),
                thread.id.as_slice(),
                thread.domain.as_ref().unwrap_or(&default_vec).as_slice(),
                thread_bump.as_ref(),
            ];
            let expected_thread_key = Pubkey::create_program_address(&seed, &crate::ID)
                .map_err(|_| SablierError::InvalidThreadAccount)?;
            require!(
                expected_thread_key == *thread_key,
                SablierError::InvalidThreadAccount
            );
        }
    }

    // Transfer lamports out (implicit close)
    {
        let thread_account = &ctx.accounts.thread;
        let thread_lamports = thread_account.get_lamports();
        thread_account.sub_lamports(thread_lamports)?;
        close_to.add_lamports(thread_lamports)?;
    }
    Ok(())
}