From edf8b374f19b8d6e6b8232452674ebdc54cffb6d Mon Sep 17 00:00:00 2001 From: "Jean Marchand (Exotic Markets)" Date: Wed, 9 Oct 2024 12:34:39 +0700 Subject: [PATCH] thread: Update thread delete validation (#73) --- programs/thread/src/errors.rs | 8 ++ .../thread/src/instructions/thread_delete.rs | 90 ++++++++++++++----- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/programs/thread/src/errors.rs b/programs/thread/src/errors.rs index 81386970..a7b951af 100644 --- a/programs/thread/src/errors.rs +++ b/programs/thread/src/errors.rs @@ -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, } diff --git a/programs/thread/src/instructions/thread_delete.rs b/programs/thread/src/instructions/thread_delete.rs index 4f468a75..71fa49ba 100644 --- a/programs/thread/src/instructions/thread_delete.rs +++ b/programs/thread/src/instructions/thread_delete.rs @@ -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) -> 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(()) }