Skip to content

Commit

Permalink
thread: Update thread delete validation (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aursen authored Oct 9, 2024
1 parent cda4aa3 commit edf8b37
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 20 deletions.
8 changes: 8 additions & 0 deletions programs/thread/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,12 @@ pub enum SablierError {
/// Thrown if the user attempts to withdraw SOL that would put a thread below it's minimum rent threshold.
#[msg("Withdrawing this amount would leave the thread with less than the minimum required SOL for rent exemption")]
WithdrawalTooLarge,

/// Thrown if the provided authority does not match the thread's authority.
#[msg("The provided authority does not match the thread's authority")]
InvalidThreadAuthority,

/// Thrown if the provided account is not a valid Thread account.
#[msg("The provided thread account is not a valid Thread account")]
InvalidThreadAccount,
}
90 changes: 70 additions & 20 deletions programs/thread/src/instructions/thread_delete.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,92 @@
use {
crate::{constants::*, state::*},
anchor_lang::prelude::*,
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.
#[account(
constraint = authority.key().eq(&thread.authority) || authority.key().eq(&thread.key())
)]
pub authority: Signer<'info>,

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

/// The thread to be delete.
#[account(
mut,
seeds = [
SEED_THREAD,
thread.authority.as_ref(),
thread.id.as_slice(),
thread.domain.as_ref().unwrap_or(&Vec::new()).as_slice()
],
bump = thread.bump,
)]
pub thread: Account<'info, Thread>,
/// 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;

let thread_lamports = thread.get_lamports();
thread.sub_lamports(thread_lamports)?;
close_to.add_lamports(thread_lamports)?;
// 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(())
}

0 comments on commit edf8b37

Please sign in to comment.