diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f4dbe77..e4a07914 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,13 @@ incremented upon a breaking change and the patch version will be incremented for - Trident SVM + AFL (see the PR for more details) ([234](https://github.com/Ackee-Blockchain/trident/pull/234)) **Removed** + - removed fuzz_iteration from test_fuzz.rs ([243](https://github.com/Ackee-Blockchain/trident/pull/243)) +**Changed** + +- errors are simplified and transaction error contains only transaction error ([244](https://github.com/Ackee-Blockchain/trident/pull/244)) + ## [0.8.1] - 2024-11-14 **Removed** diff --git a/crates/fuzz/src/error.rs b/crates/fuzz/src/error.rs index dd033a08..7a1eab98 100644 --- a/crates/fuzz/src/error.rs +++ b/crates/fuzz/src/error.rs @@ -6,12 +6,10 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum FuzzClientError { - #[error("Custom fuzzing error: {0}")] + #[error("Custom fuzz client error: {0}")] Custom(u32), - #[error("Not able to initialize client: {0}")] - ClientInitError(#[from] TransactionError), #[error("Transaction failed: {0}")] - TransactionFailed(TransactionError), + TransactionFailed(#[from] TransactionError), } #[derive(Debug, Error)] @@ -20,31 +18,15 @@ pub enum FuzzingError { Custom(u32), #[error("Fuzzing error with Custom Message: {0}\n")] CustomMessage(String), - #[error("Not able to deserialize account: {0}\n")] - CannotDeserializeAccount(String), - #[error("Optional Account not provided: {0}\n")] - OptionalAccountNotProvided(String), - #[error("Not enough Accounts: {0}\n")] - NotEnoughAccounts(String), - #[error("Account not Found: {0}\n")] - AccountNotFound(String), - #[error("Not Able To Obtain AccountInfos\n")] - NotAbleToObtainAccountInfos, - #[error("Balance Mismatch\n")] - BalanceMismatch, - #[error("Data Mismatch\n")] - DataMismatch, - #[error("Unable to obtain Data\n")] - UnableToObtainData, } impl FuzzClientError { - pub fn with_origin(self, origin: Origin) -> FuzzClientErrorWithOrigin { + pub(crate) fn with_origin(self, origin: Origin) -> FuzzClientErrorWithOrigin { let mut error_with_origin = FuzzClientErrorWithOrigin::from(self); error_with_origin.origin = Some(origin); error_with_origin } - pub fn with_context(self, context: Context) -> FuzzClientErrorWithOrigin { + pub(crate) fn with_context(self, context: Context) -> FuzzClientErrorWithOrigin { let mut error_with_origin = FuzzClientErrorWithOrigin::from(self); error_with_origin.context = Some(context); error_with_origin diff --git a/crates/fuzz/src/fuzz_client.rs b/crates/fuzz/src/fuzz_client.rs index 25542256..f668e451 100644 --- a/crates/fuzz/src/fuzz_client.rs +++ b/crates/fuzz/src/fuzz_client.rs @@ -1,12 +1,13 @@ #![allow(dead_code)] -use crate::error::*; use solana_sdk::account::AccountSharedData; use solana_sdk::hash::Hash; use solana_sdk::instruction::Instruction; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::sysvar::Sysvar; +use solana_sdk::transaction::TransactionError; + use trident_config::Config; use trident_svm::utils::ProgramEntrypoint; @@ -23,7 +24,7 @@ pub trait FuzzClient { fn warp_to_slot(&mut self, warp_slot: u64); /// Forward in time by the desired number of seconds - fn forward_in_time(&mut self, seconds: i64) -> Result<(), FuzzClientError>; + fn forward_in_time(&mut self, seconds: i64); /// Create or overwrite a custom account, subverting normal runtime checks. fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData); @@ -41,7 +42,7 @@ pub trait FuzzClient { fn process_instructions( &mut self, _instructions: &[Instruction], - ) -> Result<(), FuzzClientError>; + ) -> Result<(), TransactionError>; // Clear Temp account created during fuzzing iteration fn clear_accounts(&mut self); diff --git a/crates/fuzz/src/fuzz_client_impl.rs b/crates/fuzz/src/fuzz_client_impl.rs index 085b4aa2..4a4be8a6 100644 --- a/crates/fuzz/src/fuzz_client_impl.rs +++ b/crates/fuzz/src/fuzz_client_impl.rs @@ -14,6 +14,7 @@ use trident_svm::utils::SBFTargets; use trident_svm::utils::TridentAccountSharedData; use crate::fuzz_client::FuzzClient; +use solana_sdk::transaction::TransactionError; impl FuzzClient for TridentSVM<'_> { fn new_client(programs: &[ProgramEntrypoint], config: &Config) -> Self { @@ -61,12 +62,11 @@ impl FuzzClient for TridentSVM<'_> { self.set_sysvar(&clock); } - fn forward_in_time(&mut self, seconds: i64) -> Result<(), crate::error::FuzzClientError> { + fn forward_in_time(&mut self, seconds: i64) { let mut clock = self.get_sysvar::(); clock.unix_timestamp = clock.unix_timestamp.saturating_add(seconds); self.set_sysvar(&clock); - Ok(()) } fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData) { @@ -88,12 +88,7 @@ impl FuzzClient for TridentSVM<'_> { fn process_instructions( &mut self, instructions: &[Instruction], - ) -> Result<(), crate::error::FuzzClientError> { - // Currently Trident supports only one instruction in a transaction - if instructions.len() != 1 { - return Err(crate::error::FuzzClientError::Custom(55000)); - } - + ) -> Result<(), TransactionError> { // there should be at least 1 RW fee-payer account. // But we do not pay for TX currently so has to be manually updated // tx.message.header.num_required_signatures = 1; diff --git a/crates/fuzz/src/ix_ops.rs b/crates/fuzz/src/ix_ops.rs index 47056078..8f10c5a4 100644 --- a/crates/fuzz/src/ix_ops.rs +++ b/crates/fuzz/src/ix_ops.rs @@ -5,6 +5,7 @@ use crate::fuzz_client::FuzzClient; use crate::snapshot::SnapshotAccount; use solana_sdk::instruction::AccountMeta; use solana_sdk::signature::Keypair; +use solana_sdk::transaction::TransactionError; /// A trait providing methods to prepare data and accounts for the fuzzed instructions and allowing /// users to implement custom invariants checks and transactions error handling. @@ -74,10 +75,10 @@ pub trait IxOps { #[allow(unused_variables)] fn tx_error_handler( &self, - e: FuzzClientErrorWithOrigin, + e: TransactionError, ix_data: Vec, - pre_ix_acc_infos: &[SnapshotAccount], - ) -> Result<(), FuzzClientErrorWithOrigin> { + pre_ix_accounts: &[SnapshotAccount], + ) -> Result<(), TransactionError> { Err(e) } } diff --git a/crates/fuzz/src/lib.rs b/crates/fuzz/src/lib.rs index 56f5c9b2..e3cb2269 100644 --- a/crates/fuzz/src/lib.rs +++ b/crates/fuzz/src/lib.rs @@ -27,6 +27,7 @@ pub mod fuzzing { pub use solana_sdk::signer::keypair::Keypair; pub use solana_sdk::signer::Signer; pub use solana_sdk::transaction::Transaction; + pub use solana_sdk::transaction::TransactionError; pub use afl::fuzz as fuzz_afl; pub use arbitrary; diff --git a/crates/fuzz/src/transaction_executor.rs b/crates/fuzz/src/transaction_executor.rs index 820b350b..7bf41348 100644 --- a/crates/fuzz/src/transaction_executor.rs +++ b/crates/fuzz/src/transaction_executor.rs @@ -2,13 +2,13 @@ use std::cell::RefCell; use solana_sdk::instruction::Instruction; -use crate::{ - error::{FuzzClientErrorWithOrigin, Origin}, - fuzz_client::FuzzClient, - fuzz_stats::FuzzingStatistics, - ix_ops::IxOps, - snapshot::Snapshot, -}; +use crate::error::FuzzClientError; +use crate::error::FuzzClientErrorWithOrigin; +use crate::error::Origin; +use crate::fuzz_client::FuzzClient; +use crate::fuzz_stats::FuzzingStatistics; +use crate::ix_ops::IxOps; +use crate::snapshot::Snapshot; use trident_config::Config; @@ -25,45 +25,60 @@ impl TransactionExecutor { where I: IxOps, { + // Obtain the program id let program_id = ix.get_program_id(); + // Obtain the instruction data let data = ix .get_data(client, &mut accounts.borrow_mut()) .map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned()))) .expect("Data calculation expect"); + // Obtain the account metas and signers let (_signers, account_metas) = ix .get_accounts(client, &mut accounts.borrow_mut()) .map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned()))) .expect("Accounts calculation expect"); + // Initializes the snapshot from the account metas let mut snapshot = Snapshot::new(&account_metas); + // Capture the accounts before the instruction is executed snapshot.capture_before(client).unwrap(); + // Create the instruction to be executed let ixx = Instruction { program_id, accounts: account_metas, data: data.clone(), }; + // If stats are enabled, log the invocation of the instruction if config.get_fuzzing_with_stats() { let mut stats_logger = FuzzingStatistics::new(); stats_logger.increase_invoked(instruction_name.to_owned()); - let tx_result = client - .process_instructions(&[ixx]) - .map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned()))); + // Execute the instruction + let tx_result = client.process_instructions(&[ixx]); + + // Check the result of the instruction execution match tx_result { Ok(_) => { + // Log the successful execution of the instruction stats_logger.increase_successful(instruction_name.to_owned()); + // Capture the accounts after the instruction is executed snapshot.capture_after(client).unwrap(); + + // Get the snapshot of the accounts before and after the instruction execution let (acc_before, acc_after) = snapshot.get_snapshot(); + + // Let the user perform custom checks on the accounts if let Err(e) = ix.check(acc_before, acc_after, data).map_err(|e| { e.with_origin(Origin::Instruction(instruction_name.to_owned())) }) { + // Log the failure of the custom check stats_logger.increase_failed_check(instruction_name.to_owned()); stats_logger.output_serialized(); @@ -73,22 +88,30 @@ impl TransactionExecutor { stats_logger.output_serialized(); } Err(e) => { + // Log the failure of the instruction execution stats_logger.increase_failed(instruction_name.to_owned()); stats_logger.output_serialized(); + // Let use use transaction error handler to handle the error let raw_accounts = snapshot.get_before(); - ix.tx_error_handler(e, data, raw_accounts)? + ix.tx_error_handler(e, data, raw_accounts).map_err(|e| { + FuzzClientError::from(e) + .with_origin(Origin::Instruction(instruction_name.to_owned())) + })? } } } else { - let tx_result = client - .process_instructions(&[ixx]) - .map_err(|e| e.with_origin(Origin::Instruction(instruction_name.to_owned()))); + // If stats are not enabled, execute the instruction directly + let tx_result = client.process_instructions(&[ixx]); match tx_result { Ok(_) => { + // Capture the accounts after the instruction is executed snapshot.capture_after(client).unwrap(); + + // Get the snapshot of the accounts before and after the instruction execution let (acc_before, acc_after) = snapshot.get_snapshot(); + // Let the user perform custom checks on the accounts if let Err(e) = ix.check(acc_before, acc_after, data).map_err(|e| { e.with_origin(Origin::Instruction(instruction_name.to_owned())) }) { @@ -97,8 +120,12 @@ impl TransactionExecutor { } } Err(e) => { + // Let use use transaction error handler to handle the error let raw_accounts = snapshot.get_before(); - ix.tx_error_handler(e, data, raw_accounts)? + ix.tx_error_handler(e, data, raw_accounts).map_err(|e| { + FuzzClientError::from(e) + .with_origin(Origin::Instruction(instruction_name.to_owned())) + })? } } } diff --git a/documentation/docs/features/error-handlers.md b/documentation/docs/features/error-handlers.md index f1a8c9a0..0b3d738d 100644 --- a/documentation/docs/features/error-handlers.md +++ b/documentation/docs/features/error-handlers.md @@ -1,38 +1,38 @@ # Error Handler -Trident allows you to specify custom error handler for each Instruction. +Trident allows you to specify a custom error handler for each instruction. -This can be particularly helpful: +This can be particularly helpful in the following scenarios: -- If Transaction returns Error, you can specify to omit this error and continue with the fuzzing instruction. -- Using the `tx_error_handler` you can check if the error returned is desired based on the Accounts and Input data that were used. +- If a transaction returns an error, you can choose to omit this error and continue the fuzzing process. +- Using the `tx_error_handler`, you can check if the returned error is desired based on the accounts and input data that were used. !!! tip - The default behavior of the function is that the error is returned. + By default, transaction errors are propagated, meaning that if the transaction fails, the fuzzing iteration is stopped, and a new fuzzing iteration is started. ```rust -/// default implementation +/// Default implementation fn tx_error_handler( &self, - e: FuzzClientErrorWithOrigin, + e: TransactionError, ix_data: Vec, pre_ix_acc_infos: &[SnapshotAccount], -) -> Result<(), FuzzClientErrorWithOrigin> { +) -> Result<(), TransactionError> { Err(e) } ``` -To omit the Error and continue with the next Instruction in the iteration, you can do +To omit the error and continue with the next instruction in the iteration, you can use the following implementation: ```rust -/// custom implementation +/// Custom implementation fn tx_error_handler( &self, - e: FuzzClientErrorWithOrigin, + e: TransactionError, ix_data: Vec, pre_ix_acc_infos: &[SnapshotAccount], -) -> Result<(), FuzzClientErrorWithOrigin> { +) -> Result<(), TransactionError> { Ok(()) } ``` diff --git a/examples/common_issues/arbitrary-limit-inputs-5/trident-tests/fuzz_0/fuzz_instructions.rs b/examples/common_issues/arbitrary-limit-inputs-5/trident-tests/fuzz_0/fuzz_instructions.rs index ee97dcc9..957bab6b 100644 --- a/examples/common_issues/arbitrary-limit-inputs-5/trident-tests/fuzz_0/fuzz_instructions.rs +++ b/examples/common_issues/arbitrary-limit-inputs-5/trident-tests/fuzz_0/fuzz_instructions.rs @@ -378,7 +378,7 @@ impl IxOps for WithdrawUnlocked { if escrow.recipient == recipient { if recipient_token_account_pre.amount == recipient_token_account_post.amount { // Recipient was not able to withdraw - return Err(FuzzingError::BalanceMismatch); + return Err(FuzzingError::Custom(55)); } else if recipient_token_account_pre.amount + escrow.amount != recipient_token_account_post.amount {