From cda4aa3f4441be2355510c8decdf69fa3d93e1e6 Mon Sep 17 00:00:00 2001 From: "Jean Marchand (Exotic Markets)" Date: Wed, 9 Oct 2024 11:55:40 +0700 Subject: [PATCH 1/5] chore: Add CSpell words (#72) --- .vscode/settings.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..553b864e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "cSpell.words": [ + "pdas", + "RUSTC", + "Sablier", + "pubkey", + "solana", + "lamports" + ] +} \ No newline at end of file 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 2/5] 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(()) } From 454a445dabeeea7e76c593c6519997c5706a2876 Mon Sep 17 00:00:00 2001 From: "Jean Marchand (Exotic Markets)" Date: Wed, 9 Oct 2024 12:44:17 +0700 Subject: [PATCH 3/5] thread: Fix min-space calculation (#75) --- programs/thread/src/instructions/thread_create.rs | 2 +- programs/thread/src/state/thread.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/programs/thread/src/instructions/thread_create.rs b/programs/thread/src/instructions/thread_create.rs index 7f7d5b20..c279437f 100644 --- a/programs/thread/src/instructions/thread_create.rs +++ b/programs/thread/src/instructions/thread_create.rs @@ -30,7 +30,7 @@ pub struct ThreadCreate<'info> { domain.as_ref().unwrap_or(&Vec::new()).as_slice() ], bump, - payer= payer, + payer = payer, space = Thread::min_space(&instructions)? )] pub thread: Account<'info, Thread>, diff --git a/programs/thread/src/state/thread.rs b/programs/thread/src/state/thread.rs index d2e5905e..dc81ac64 100644 --- a/programs/thread/src/state/thread.rs +++ b/programs/thread/src/state/thread.rs @@ -71,8 +71,12 @@ pub trait ThreadAccount { impl Thread { pub fn min_space(instructions: &[SerializableInstruction]) -> Result { - let ins_number = instructions.len(); let ins_space = instructions.try_to_vec()?.len(); + let max_ins_size = instructions + .iter() + .map(|ins| ins.try_to_vec().map(|v| v.len()).unwrap_or(0)) + .max() + .unwrap_or(0); Ok( 8 @@ -84,7 +88,7 @@ impl Thread { + u64::MIN_SPACE // fee + (4 + 32) // id + (4 + ins_space) // instructions - + (1 + ins_space / ins_number) // next_instruction + + (1 + max_ins_size) // next_instruction + bool::MIN_SPACE // paused + u64::MIN_SPACE // rate_limit + Trigger::MIN_SPACE, // trigger From e41712c4fc4c2b72054f4ed056f4c61a28329487 Mon Sep 17 00:00:00 2001 From: "Jean Marchand (Exotic Markets)" Date: Wed, 9 Oct 2024 14:38:47 +0700 Subject: [PATCH 4/5] thread: Fix execution time (#76) --- .../thread/src/instructions/thread_exec.rs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/programs/thread/src/instructions/thread_exec.rs b/programs/thread/src/instructions/thread_exec.rs index ed23a362..327a5175 100644 --- a/programs/thread/src/instructions/thread_exec.rs +++ b/programs/thread/src/instructions/thread_exec.rs @@ -204,5 +204,28 @@ pub fn handler(ctx: Context) -> Result<()> { fee.add_lamports(thread.fee)?; } + let exec_ctx = &mut thread.exec_context.unwrap(); + + // Update execution context for Cron and Periodic triggers + // This ensures the next execution is scheduled based on the actual execution time, + // rather than the theoretical scheduled time, preventing drift in long-running threads + match thread.trigger { + Trigger::Cron { skippable, .. } => { + if skippable { + exec_ctx.last_exec_at = clock.slot; + exec_ctx.trigger_context = TriggerContext::Cron { + started_at: clock.unix_timestamp, + }; + } + } + Trigger::Periodic { .. } => { + exec_ctx.last_exec_at = clock.slot; + exec_ctx.trigger_context = TriggerContext::Periodic { + started_at: clock.unix_timestamp, + }; + } + _ => (), + } + Ok(()) } From da0fccd6ff06e7e433d3e03b20dd7b6e69c69599 Mon Sep 17 00:00:00 2001 From: "Jean Marchand (Exotic Markets)" Date: Wed, 9 Oct 2024 15:06:35 +0700 Subject: [PATCH 5/5] plugin: Reduce simulation trials (#77) --- plugin/src/executors/state/executable_threads.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/executors/state/executable_threads.rs b/plugin/src/executors/state/executable_threads.rs index 34459a3d..65db0cab 100644 --- a/plugin/src/executors/state/executable_threads.rs +++ b/plugin/src/executors/state/executable_threads.rs @@ -10,7 +10,7 @@ use tokio::sync::RwLock; use crate::{executors::tx::ExecutableThreadMetadata, pool_position::PoolPosition}; /// Number of times to retry a thread simulation. -static MAX_THREAD_SIMULATION_FAILURES: u32 = 10; +static MAX_THREAD_SIMULATION_FAILURES: u32 = 5; /// Number of slots to wait before trying to execute a thread while not in the pool. static THREAD_TIMEOUT_WINDOW: u64 = 24;