diff --git a/crates/client/src/commander.rs b/crates/client/src/commander.rs index 6fdbd5c7..1219fbcb 100644 --- a/crates/client/src/commander.rs +++ b/crates/client/src/commander.rs @@ -18,7 +18,7 @@ use tokio::{ use crate::constants::*; use tokio::io::AsyncBufReadExt; -use trident_fuzz::fuzzing_stats::FuzzingStatistics; +use trident_fuzz::fuzz_stats::FuzzingStatistics; #[derive(Error, Debug)] pub enum Error { diff --git a/crates/client/src/lib.rs b/crates/client/src/lib.rs index df933af7..68d2aded 100644 --- a/crates/client/src/lib.rs +++ b/crates/client/src/lib.rs @@ -34,10 +34,10 @@ pub mod fuzzing { pub use trident_derive_fuzz_deserialize::FuzzDeserialize; pub use trident_derive_fuzz_test_executor::FuzzTestExecutor; + /// trident macros pub use trident_fuzz::convert_entry; pub use trident_fuzz::fuzz_trident; pub use trident_fuzz::show_account; - /// trident macros pub use trident_fuzz::*; pub use solana_program_test::processor; @@ -47,10 +47,14 @@ pub mod fuzzing { pub use super::temp_clone::*; /// trident methods pub use trident_fuzz::accounts_storage::*; - pub use trident_fuzz::data_builder::build_ix_fuzz_data; - pub use trident_fuzz::data_builder::*; pub use trident_fuzz::error::*; - pub use trident_fuzz::fuzzing_stats::FuzzingStatistics; + pub use trident_fuzz::fuzz_client::FuzzClient; + pub use trident_fuzz::fuzz_data::build_ix_fuzz_data; + pub use trident_fuzz::fuzz_data::*; + pub use trident_fuzz::fuzz_deserialize::FuzzDeserialize; + pub use trident_fuzz::fuzz_stats::FuzzingStatistics; + pub use trident_fuzz::fuzz_test_executor::FuzzTestExecutor; + pub use trident_fuzz::ix_ops::IxOps; pub use trident_fuzz::program_test_client_blocking::ProgramTestClientBlocking; pub use trident_fuzz::snapshot::Snapshot; diff --git a/crates/fuzz/src/accounts_storage.rs b/crates/fuzz/src/accounts_storage.rs index 03e893f1..4e5145d6 100644 --- a/crates/fuzz/src/accounts_storage.rs +++ b/crates/fuzz/src/accounts_storage.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use solana_sdk::{pubkey::Pubkey, signature::Keypair}; -use crate::{data_builder::FuzzClient, AccountId}; +use crate::{fuzz_client::FuzzClient, AccountId}; pub struct PdaStore { pub pubkey: Pubkey, diff --git a/crates/fuzz/src/data_builder.rs b/crates/fuzz/src/data_builder.rs deleted file mode 100644 index d66caef8..00000000 --- a/crates/fuzz/src/data_builder.rs +++ /dev/null @@ -1,373 +0,0 @@ -#![allow(dead_code)] - -use anchor_lang::prelude::Rent; -use anchor_lang::solana_program::account_info::{Account as Acc, AccountInfo}; -use anchor_lang::solana_program::hash::Hash; -use arbitrary::Arbitrary; -use arbitrary::Unstructured; -use solana_sdk::account::{Account, AccountSharedData}; -use solana_sdk::instruction::AccountMeta; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::Keypair; -use solana_sdk::transaction::VersionedTransaction; -use std::cell::RefCell; -use std::collections::HashMap; -use std::error::Error; -use std::fmt::Display; - -use crate::error::*; - -pub struct FuzzData { - pub pre_ixs: Vec, - pub ixs: Vec, - pub post_ixs: Vec, - pub accounts: RefCell, -} - -pub struct FuzzDataIterator<'a, T> { - pre_ixs_iter: std::slice::Iter<'a, T>, - ixs_iter: std::slice::Iter<'a, T>, - post_ixs_iter: std::slice::Iter<'a, T>, -} - -impl FuzzData { - pub fn iter(&self) -> FuzzDataIterator<'_, T> { - FuzzDataIterator { - pre_ixs_iter: self.pre_ixs.iter(), - ixs_iter: self.ixs.iter(), - post_ixs_iter: self.post_ixs.iter(), - } - } -} - -impl<'a, T> Iterator for FuzzDataIterator<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - self.pre_ixs_iter - .next() - .or_else(|| self.ixs_iter.next()) - .or_else(|| self.post_ixs_iter.next()) - } -} - -impl FuzzData -where - T: FuzzTestExecutor + Display, -{ - pub fn run_with_runtime( - &self, - program_id: Pubkey, - client: &mut impl FuzzClient, - ) -> core::result::Result<(), Box> { - // solana_logger::setup_with_default("off"); - // #[cfg(fuzzing_debug)] - // solana_logger::setup_with_default( - // "solana_rbpf::vm=debug,\ - // solana_runtime::message_processor=debug,\ - // solana_runtime::system_instruction_processor=trace,\ - // solana_program_test=info,\ - // fuzz_target=info", - // ); - - #[cfg(fuzzing_debug)] - { - eprintln!("\x1b[34mInstructions sequence\x1b[0m:"); - for ix in self.iter() { - eprintln!("{}", ix); - } - eprintln!("------ End of Instructions sequence ------ "); - } - - let mut sent_txs: HashMap = HashMap::new(); - - for fuzz_ix in &mut self.iter() { - #[cfg(fuzzing_debug)] - eprintln!("\x1b[34mCurrently processing\x1b[0m: {}", fuzz_ix); - - if fuzz_ix - .run_fuzzer(program_id, &self.accounts, client, &mut sent_txs) - .is_err() - { - // for now skip following instructions in case of error and move to the next fuzz iteration - return Ok(()); - } - } - Ok(()) - } -} - -pub trait FuzzTestExecutor { - fn run_fuzzer( - &self, - program_id: Pubkey, - accounts: &RefCell, - client: &mut impl FuzzClient, - sent_txs: &mut HashMap, - ) -> core::result::Result<(), FuzzClientErrorWithOrigin>; -} - -#[allow(unused_variables)] -pub trait FuzzDataBuilder Arbitrary<'a>> { - /// The instruction(s) executed as first, can be used for initialization. - fn pre_ixs(u: &mut Unstructured) -> arbitrary::Result> { - Ok(vec![]) - } - - /// The main instructions for fuzzing. - fn ixs(u: &mut Unstructured) -> arbitrary::Result> { - let v = >::arbitrary(u)?; - // Return always a vector with at least one element, othewise return error. - if v.is_empty() { - return Err(arbitrary::Error::NotEnoughData); - } - Ok(v) - } - - /// The instuction(s) executed as last. - fn post_ixs(u: &mut Unstructured) -> arbitrary::Result> { - Ok(vec![]) - } -} - -/// 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. -pub trait IxOps<'info> { - /// The data to be passed as instruction data parameter - type IxData; - /// The accounts to be passed as instruction accounts - type IxAccounts; - /// The structure to which the instruction accounts will be deserialized - type IxSnapshot; - - /// Provides instruction data for the fuzzed instruction. - /// It is assumed that the instruction data will be based on the fuzzer input stored in the `self.data` variable. - /// However it is on the developer to decide and it can be also for example a hardcoded constant. - /// You should only avoid any non-deterministic random values to preserve reproducibility of the tests. - fn get_data( - &self, - client: &mut impl FuzzClient, - fuzz_accounts: &mut Self::IxAccounts, - ) -> Result; - - /// Provides accounts required for the fuzzed instruction. The method returns a tuple of signers and account metas. - fn get_accounts( - &self, - client: &mut impl FuzzClient, - fuzz_accounts: &mut Self::IxAccounts, - ) -> Result<(Vec, Vec), FuzzingError>; - - /// A method to implement custom invariants checks for a given instruction. This method is called after each - /// successfully executed instruction and by default does nothing. You can override this behavior by providing - /// your own implementation. You can access the snapshots of account states before and after the transaction for comparison. - /// - /// If you want to detect a crash, you have to return a `FuzzingError` (or alternativelly panic). - /// - /// If you want to perform checks also on a failed instruction execution, you can do so using the [`tx_error_handler`](trident_client::fuzzer::data_builder::IxOps::tx_error_handler) method. - #[allow(unused_variables)] - fn check( - &self, - pre_ix: Self::IxSnapshot, - post_ix: Self::IxSnapshot, - ix_data: Self::IxData, - ) -> Result<(), FuzzingError> { - Ok(()) - } - - /// A method to implement custom error handler for failed transactions. - /// - /// The fuzzer might generate a sequence of one or more instructions that are executed sequentially. - /// By default, if the execution of one of the instructions fails, the remaining instructions are skipped - /// and are not executed. This can be overriden by implementing this method and returning `Ok(())` - /// instead of propagating the error. - /// - /// You can also check the kind of the transaction error by inspecting the `e` parameter. - /// If you would like to detect a crash on a specific error, call `panic!()`. - /// - /// If your accounts are malformed and the fuzzed program is unable to deserialize it, the transaction - /// execution will fail. In that case also the deserialization of accounts snapshot before executing - /// the instruction would fail. You are provided with the raw account infos snapshots and you are free - /// to deserialize the accounts by yourself and therefore also handling potential errors. To deserialize - /// the `pre_ix_acc_infos` raw accounts to a snapshot structure, you can call: - /// - /// ```rust,ignore - /// self.deserialize_option(pre_ix_acc_infos) - /// ``` - #[allow(unused_variables)] - fn tx_error_handler( - &self, - e: FuzzClientErrorWithOrigin, - ix_data: Self::IxData, - pre_ix_acc_infos: &'info mut [Option>], - ) -> Result<(), FuzzClientErrorWithOrigin> { - Err(e) - } -} - -pub trait FuzzDeserialize<'info> { - type Ix; - // TODO return also remaining accounts - - fn deserialize_option( - &self, - _program_id: &anchor_lang::prelude::Pubkey, - accounts: &'info mut [Option>], - ) -> Result; -} - -/// A trait providing methods to read and write (manipulate) accounts -pub trait FuzzClient { - /// Create an empty account and add lamports to it - fn set_account(&mut self, lamports: u64) -> Keypair; - - /// Create or overwrite a custom account, subverting normal runtime checks. - fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData); - - /// Create an SPL token account - #[allow(clippy::too_many_arguments)] - fn set_token_account( - &mut self, - mint: Pubkey, - owner: Pubkey, - amount: u64, - delegate: Option, - is_native: Option, - delegated_amount: u64, - close_authority: Option, - ) -> Pubkey; - - /// Create an SPL mint account - fn set_mint_account( - &mut self, - decimals: u8, - owner: &Pubkey, - freeze_authority: Option, - ) -> Pubkey; - - /// Get the Keypair of the client's payer account - fn payer(&self) -> Keypair; - - /// Get the account at the given address - fn get_account(&mut self, key: &Pubkey) -> Result, FuzzClientError>; - - /// Get accounts based on the supplied meta information - fn get_accounts( - &mut self, - metas: &[AccountMeta], - ) -> Result>, FuzzClientErrorWithOrigin>; - - /// Get last blockhash - fn get_last_blockhash(&self) -> Hash; - - /// Get the cluster rent - fn get_rent(&mut self) -> Result; - - /// Send a transaction and return until the transaction has been finalized or rejected. - fn process_transaction( - &mut self, - transaction: impl Into, - ) -> Result<(), FuzzClientError>; -} - -#[macro_export] -macro_rules! fuzz_trident { - ($ix:ident: $ix_dty:ident , |$buf:ident: $dty:ident| $body:block) => { - fuzz(|$buf| { - let mut $buf: FuzzData<$ix_dty, _> = { - use arbitrary::Unstructured; - - let mut buf = Unstructured::new($buf); - if let Ok(fuzz_data) = build_ix_fuzz_data($dty {}, &mut buf) { - fuzz_data - } else { - return; - } - }; - $body - }); - }; -} -/// Prints the details of a given account in a pretty-printed format. -/// -/// This macro takes a single argument, which is an expression referring to the account -/// you want to print. The account data structure must implement or derive the [`Debug`] -/// trait for this macro to work, as it relies on `std::fmt::Debug` for formatting. -/// -/// # Examples -/// -/// ```rust,ignore -/// use trident_client::fuzzing::show_account; -/// -/// #[derive(Debug)] -/// #[account] -/// struct Escrow { -/// recipeint: Pubkey, -/// id: u32, -/// balance: f64, -/// name: String, -/// } -/// -/// fn check( -/// &self, -/// pre_ix: Self::IxSnapshot, -/// post_ix: Self::IxSnapshot, -/// ix_data: Self::IxData, -/// ) -> Result<(), FuzzingError> { -/// if let Some(escrow) = pre_ix.escrow{ -/// show_account!(escrow); -/// } -/// } -/// ``` -/// -/// # Requirements -/// -/// The `account` passed to `show_account!` must implement or derive the [`Debug`] trait. -/// Attempting to use this macro with a type that does not meet this requirement will -/// result in a compilation error. -#[macro_export] -macro_rules! show_account { - ($account:expr) => { - eprintln!("{:#?}", $account); - }; -} - -pub fn build_ix_fuzz_data Arbitrary<'a>, T: FuzzDataBuilder, V: Default>( - _data_builder: T, - u: &mut arbitrary::Unstructured, -) -> arbitrary::Result> { - Ok(FuzzData { - pre_ixs: T::pre_ixs(u)?, - ixs: T::ixs(u)?, - post_ixs: T::post_ixs(u)?, - accounts: RefCell::new(V::default()), - }) -} - -/// Creates `AccountInfo`s from `Accounts` and corresponding `AccountMeta` slices. -pub fn get_account_infos_option<'info>( - accounts: &'info mut [Option], - metas: &'info [AccountMeta], -) -> Result>>, FuzzingError> { - let iter = accounts.iter_mut().zip(metas); - let r = iter - .map(|(account, meta)| { - if let Some(account) = account { - let (lamports, data, owner, executable, rent_epoch) = account.get(); - Some(AccountInfo::new( - &meta.pubkey, - meta.is_signer, - meta.is_writable, - lamports, - data, - owner, - executable, - rent_epoch, - )) - } else { - None - } - }) - .collect(); - - Ok(r) -} diff --git a/crates/fuzz/src/fuzz_client.rs b/crates/fuzz/src/fuzz_client.rs new file mode 100644 index 00000000..6fe11334 --- /dev/null +++ b/crates/fuzz/src/fuzz_client.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] + +use anchor_lang::prelude::Rent; +use anchor_lang::solana_program::hash::Hash; + +use solana_sdk::account::{Account, AccountSharedData}; +use solana_sdk::instruction::AccountMeta; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::transaction::VersionedTransaction; + +use crate::error::*; + +/// A trait providing methods to read and write (manipulate) accounts +pub trait FuzzClient { + /// Create an empty account and add lamports to it + fn set_account(&mut self, lamports: u64) -> Keypair; + + /// Create or overwrite a custom account, subverting normal runtime checks. + fn set_account_custom(&mut self, address: &Pubkey, account: &AccountSharedData); + + /// Create an SPL token account + #[allow(clippy::too_many_arguments)] + fn set_token_account( + &mut self, + mint: Pubkey, + owner: Pubkey, + amount: u64, + delegate: Option, + is_native: Option, + delegated_amount: u64, + close_authority: Option, + ) -> Pubkey; + + /// Create an SPL mint account + fn set_mint_account( + &mut self, + decimals: u8, + owner: &Pubkey, + freeze_authority: Option, + ) -> Pubkey; + + /// Get the Keypair of the client's payer account + fn payer(&self) -> Keypair; + + /// Get the account at the given address + fn get_account(&mut self, key: &Pubkey) -> Result, FuzzClientError>; + + /// Get accounts based on the supplied meta information + fn get_accounts( + &mut self, + metas: &[AccountMeta], + ) -> Result>, FuzzClientErrorWithOrigin>; + + /// Get last blockhash + fn get_last_blockhash(&self) -> Hash; + + /// Get the cluster rent + fn get_rent(&mut self) -> Result; + + /// Send a transaction and return until the transaction has been finalized or rejected. + fn process_transaction( + &mut self, + transaction: impl Into, + ) -> Result<(), FuzzClientError>; +} diff --git a/crates/fuzz/src/fuzz_data.rs b/crates/fuzz/src/fuzz_data.rs new file mode 100644 index 00000000..4d67161e --- /dev/null +++ b/crates/fuzz/src/fuzz_data.rs @@ -0,0 +1,161 @@ +#![allow(dead_code)] + +use anchor_lang::solana_program::account_info::{Account as AccountTrait, AccountInfo}; +use anchor_lang::solana_program::hash::Hash; +use arbitrary::Arbitrary; +use arbitrary::Unstructured; +use solana_sdk::account::Account; +use solana_sdk::instruction::AccountMeta; +use solana_sdk::pubkey::Pubkey; +use std::cell::RefCell; +use std::collections::HashMap; +use std::error::Error; +use std::fmt::Display; + +use crate::error::*; +use crate::fuzz_client::FuzzClient; +use crate::fuzz_test_executor::FuzzTestExecutor; + +pub struct FuzzData { + pub pre_ixs: Vec, + pub ixs: Vec, + pub post_ixs: Vec, + pub accounts: RefCell, +} + +pub struct FuzzDataIterator<'a, T> { + pre_ixs_iter: std::slice::Iter<'a, T>, + ixs_iter: std::slice::Iter<'a, T>, + post_ixs_iter: std::slice::Iter<'a, T>, +} + +impl FuzzData { + pub fn iter(&self) -> FuzzDataIterator<'_, T> { + FuzzDataIterator { + pre_ixs_iter: self.pre_ixs.iter(), + ixs_iter: self.ixs.iter(), + post_ixs_iter: self.post_ixs.iter(), + } + } +} + +impl<'a, T> Iterator for FuzzDataIterator<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.pre_ixs_iter + .next() + .or_else(|| self.ixs_iter.next()) + .or_else(|| self.post_ixs_iter.next()) + } +} + +impl FuzzData +where + T: FuzzTestExecutor + Display, +{ + pub fn run_with_runtime( + &self, + program_id: Pubkey, + client: &mut impl FuzzClient, + ) -> core::result::Result<(), Box> { + // solana_logger::setup_with_default("off"); + // #[cfg(fuzzing_debug)] + // solana_logger::setup_with_default( + // "solana_rbpf::vm=debug,\ + // solana_runtime::message_processor=debug,\ + // solana_runtime::system_instruction_processor=trace,\ + // solana_program_test=info,\ + // fuzz_target=info", + // ); + + #[cfg(fuzzing_debug)] + { + eprintln!("\x1b[34mInstructions sequence\x1b[0m:"); + for ix in self.iter() { + eprintln!("{}", ix); + } + eprintln!("------ End of Instructions sequence ------ "); + } + + let mut sent_txs: HashMap = HashMap::new(); + + for fuzz_ix in &mut self.iter() { + #[cfg(fuzzing_debug)] + eprintln!("\x1b[34mCurrently processing\x1b[0m: {}", fuzz_ix); + + if fuzz_ix + .run_fuzzer(program_id, &self.accounts, client, &mut sent_txs) + .is_err() + { + // for now skip following instructions in case of error and move to the next fuzz iteration + return Ok(()); + } + } + Ok(()) + } +} + +#[allow(unused_variables)] +pub trait FuzzDataBuilder Arbitrary<'a>> { + /// The instruction(s) executed as first, can be used for initialization. + fn pre_ixs(u: &mut Unstructured) -> arbitrary::Result> { + Ok(vec![]) + } + + /// The main instructions for fuzzing. + fn ixs(u: &mut Unstructured) -> arbitrary::Result> { + let v = >::arbitrary(u)?; + // Return always a vector with at least one element, othewise return error. + if v.is_empty() { + return Err(arbitrary::Error::NotEnoughData); + } + Ok(v) + } + + /// The instuction(s) executed as last. + fn post_ixs(u: &mut Unstructured) -> arbitrary::Result> { + Ok(vec![]) + } +} + +pub fn build_ix_fuzz_data Arbitrary<'a>, T: FuzzDataBuilder, V: Default>( + _data_builder: T, + u: &mut arbitrary::Unstructured, +) -> arbitrary::Result> { + Ok(FuzzData { + pre_ixs: T::pre_ixs(u)?, + ixs: T::ixs(u)?, + post_ixs: T::post_ixs(u)?, + accounts: RefCell::new(V::default()), + }) +} + +/// Creates `AccountInfo`s from `Accounts` and corresponding `AccountMeta` slices. +pub fn get_account_infos_option<'info>( + accounts: &'info mut [Option], + metas: &'info [AccountMeta], +) -> Result>>, FuzzingError> { + let iter = accounts.iter_mut().zip(metas); + let r = iter + .map(|(account, meta)| { + if let Some(account) = account { + let (lamports, data, owner, executable, rent_epoch) = account.get(); + Some(AccountInfo::new( + &meta.pubkey, + meta.is_signer, + meta.is_writable, + lamports, + data, + owner, + executable, + rent_epoch, + )) + } else { + None + } + }) + .collect(); + + Ok(r) +} diff --git a/crates/fuzz/src/fuzz_deserialize.rs b/crates/fuzz/src/fuzz_deserialize.rs new file mode 100644 index 00000000..e38093a9 --- /dev/null +++ b/crates/fuzz/src/fuzz_deserialize.rs @@ -0,0 +1,16 @@ +#![allow(dead_code)] + +use anchor_lang::solana_program::account_info::AccountInfo; + +use crate::error::FuzzingError; + +pub trait FuzzDeserialize<'info> { + type Ix; + // TODO return also remaining accounts + + fn deserialize_option( + &self, + _program_id: &anchor_lang::prelude::Pubkey, + accounts: &'info mut [Option>], + ) -> Result; +} diff --git a/crates/fuzz/src/fuzzing_stats.rs b/crates/fuzz/src/fuzz_stats.rs similarity index 100% rename from crates/fuzz/src/fuzzing_stats.rs rename to crates/fuzz/src/fuzz_stats.rs diff --git a/crates/fuzz/src/fuzz_test_executor.rs b/crates/fuzz/src/fuzz_test_executor.rs new file mode 100644 index 00000000..41eca4af --- /dev/null +++ b/crates/fuzz/src/fuzz_test_executor.rs @@ -0,0 +1,21 @@ +#![allow(dead_code)] + +use anchor_lang::solana_program::hash::Hash; + +use solana_sdk::pubkey::Pubkey; + +use std::cell::RefCell; +use std::collections::HashMap; + +use crate::error::FuzzClientErrorWithOrigin; +use crate::fuzz_client::FuzzClient; + +pub trait FuzzTestExecutor { + fn run_fuzzer( + &self, + program_id: Pubkey, + accounts: &RefCell, + client: &mut impl FuzzClient, + sent_txs: &mut HashMap, + ) -> core::result::Result<(), FuzzClientErrorWithOrigin>; +} diff --git a/crates/fuzz/src/fuzz_trident.rs b/crates/fuzz/src/fuzz_trident.rs new file mode 100644 index 00000000..aab7e17d --- /dev/null +++ b/crates/fuzz/src/fuzz_trident.rs @@ -0,0 +1,62 @@ +#[macro_export] +macro_rules! fuzz_trident { + ($ix:ident: $ix_dty:ident , |$buf:ident: $dty:ident| $body:block) => { + fuzz(|$buf| { + let mut $buf: FuzzData<$ix_dty, _> = { + use arbitrary::Unstructured; + + let mut buf = Unstructured::new($buf); + if let Ok(fuzz_data) = build_ix_fuzz_data($dty {}, &mut buf) { + fuzz_data + } else { + return; + } + }; + $body + }); + }; +} + +/// Prints the details of a given account in a pretty-printed format. +/// +/// This macro takes a single argument, which is an expression referring to the account +/// you want to print. The account data structure must implement or derive the [`Debug`] +/// trait for this macro to work, as it relies on `std::fmt::Debug` for formatting. +/// +/// # Examples +/// +/// ```rust,ignore +/// use trident_client::fuzzing::show_account; +/// +/// #[derive(Debug)] +/// #[account] +/// struct Escrow { +/// recipeint: Pubkey, +/// id: u32, +/// balance: f64, +/// name: String, +/// } +/// +/// fn check( +/// &self, +/// pre_ix: Self::IxSnapshot, +/// post_ix: Self::IxSnapshot, +/// ix_data: Self::IxData, +/// ) -> Result<(), FuzzingError> { +/// if let Some(escrow) = pre_ix.escrow{ +/// show_account!(escrow); +/// } +/// } +/// ``` +/// +/// # Requirements +/// +/// The `account` passed to `show_account!` must implement or derive the [`Debug`] trait. +/// Attempting to use this macro with a type that does not meet this requirement will +/// result in a compilation error. +#[macro_export] +macro_rules! show_account { + ($account:expr) => { + eprintln!("{:#?}", $account); + }; +} diff --git a/crates/fuzz/src/ix_ops.rs b/crates/fuzz/src/ix_ops.rs new file mode 100644 index 00000000..a5081368 --- /dev/null +++ b/crates/fuzz/src/ix_ops.rs @@ -0,0 +1,81 @@ +#![allow(dead_code)] + +use crate::error::*; +use crate::fuzz_client::FuzzClient; +use anchor_lang::solana_program::account_info::AccountInfo; +use solana_sdk::instruction::AccountMeta; +use solana_sdk::signature::Keypair; + +/// 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. +pub trait IxOps<'info> { + /// The data to be passed as instruction data parameter + type IxData; + /// The accounts to be passed as instruction accounts + type IxAccounts; + /// The structure to which the instruction accounts will be deserialized + type IxSnapshot; + + /// Provides instruction data for the fuzzed instruction. + /// It is assumed that the instruction data will be based on the fuzzer input stored in the `self.data` variable. + /// However it is on the developer to decide and it can be also for example a hardcoded constant. + /// You should only avoid any non-deterministic random values to preserve reproducibility of the tests. + fn get_data( + &self, + client: &mut impl FuzzClient, + fuzz_accounts: &mut Self::IxAccounts, + ) -> Result; + + /// Provides accounts required for the fuzzed instruction. The method returns a tuple of signers and account metas. + fn get_accounts( + &self, + client: &mut impl FuzzClient, + fuzz_accounts: &mut Self::IxAccounts, + ) -> Result<(Vec, Vec), FuzzingError>; + + /// A method to implement custom invariants checks for a given instruction. This method is called after each + /// successfully executed instruction and by default does nothing. You can override this behavior by providing + /// your own implementation. You can access the snapshots of account states before and after the transaction for comparison. + /// + /// If you want to detect a crash, you have to return a `FuzzingError` (or alternativelly panic). + /// + /// If you want to perform checks also on a failed instruction execution, you can do so using the [`tx_error_handler`](trident_client::fuzzer::data_builder::IxOps::tx_error_handler) method. + #[allow(unused_variables)] + fn check( + &self, + pre_ix: Self::IxSnapshot, + post_ix: Self::IxSnapshot, + ix_data: Self::IxData, + ) -> Result<(), FuzzingError> { + Ok(()) + } + + /// A method to implement custom error handler for failed transactions. + /// + /// The fuzzer might generate a sequence of one or more instructions that are executed sequentially. + /// By default, if the execution of one of the instructions fails, the remaining instructions are skipped + /// and are not executed. This can be overriden by implementing this method and returning `Ok(())` + /// instead of propagating the error. + /// + /// You can also check the kind of the transaction error by inspecting the `e` parameter. + /// If you would like to detect a crash on a specific error, call `panic!()`. + /// + /// If your accounts are malformed and the fuzzed program is unable to deserialize it, the transaction + /// execution will fail. In that case also the deserialization of accounts snapshot before executing + /// the instruction would fail. You are provided with the raw account infos snapshots and you are free + /// to deserialize the accounts by yourself and therefore also handling potential errors. To deserialize + /// the `pre_ix_acc_infos` raw accounts to a snapshot structure, you can call: + /// + /// ```rust,ignore + /// self.deserialize_option(pre_ix_acc_infos) + /// ``` + #[allow(unused_variables)] + fn tx_error_handler( + &self, + e: FuzzClientErrorWithOrigin, + ix_data: Self::IxData, + pre_ix_acc_infos: &'info mut [Option>], + ) -> Result<(), FuzzClientErrorWithOrigin> { + Err(e) + } +} diff --git a/crates/fuzz/src/lib.rs b/crates/fuzz/src/lib.rs index 58831843..d12aaf10 100644 --- a/crates/fuzz/src/lib.rs +++ b/crates/fuzz/src/lib.rs @@ -1,7 +1,12 @@ pub mod accounts_storage; -pub mod data_builder; pub mod error; -pub mod fuzzing_stats; +pub mod fuzz_data; +pub mod fuzz_stats; pub mod program_test_client_blocking; pub mod snapshot; pub type AccountId = u8; +pub mod fuzz_client; +pub mod fuzz_deserialize; +pub mod fuzz_test_executor; +pub mod fuzz_trident; +pub mod ix_ops; diff --git a/crates/fuzz/src/program_test_client_blocking.rs b/crates/fuzz/src/program_test_client_blocking.rs index 146537f8..55bc7374 100644 --- a/crates/fuzz/src/program_test_client_blocking.rs +++ b/crates/fuzz/src/program_test_client_blocking.rs @@ -17,8 +17,8 @@ use solana_sdk::{ use spl_token::state::Mint; use tokio::runtime::Builder; -use crate::data_builder::FuzzClient; use crate::error::*; +use crate::fuzz_client::FuzzClient; pub type ProgramEntry = for<'info> fn( program_id: &Pubkey, diff --git a/crates/fuzz/src/snapshot.rs b/crates/fuzz/src/snapshot.rs index 1a1b77fd..647ed59a 100644 --- a/crates/fuzz/src/snapshot.rs +++ b/crates/fuzz/src/snapshot.rs @@ -4,7 +4,9 @@ use anchor_lang::solana_program::account_info::Account as Acc; use anchor_lang::solana_program::account_info::AccountInfo; use solana_sdk::{account::Account, instruction::AccountMeta}; -use crate::data_builder::{FuzzClient, FuzzDeserialize}; +use crate::fuzz_client::FuzzClient; +use crate::fuzz_deserialize::FuzzDeserialize; + use crate::error::*; pub struct Snapshot<'info, T> { before: Vec>,