Skip to content

Commit

Permalink
🥅 WIP: error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
lukacan authored and lukacan committed Feb 14, 2024
1 parent b676b79 commit efde295
Show file tree
Hide file tree
Showing 11 changed files with 421 additions and 157 deletions.
1 change: 1 addition & 0 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ trdelnik-derive-displayix = { path = "./derive/display_ix" }
trdelnik-derive-fuzz-deserialize = { path = "./derive/fuzz_deserialize" }
trdelnik-derive-fuzz-test-executor = { path = "./derive/fuzz_test_executor" }
pathdiff = "0.2.1"
solana-banks-client = "1.16.19"
76 changes: 55 additions & 21 deletions crates/client/derive/fuzz_test_executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,36 @@ pub fn fuzz_test_executor(input: TokenStream) -> TokenStream {
let variant_name = &variant.ident;
quote! {
#enum_name::#variant_name (ix) => {

let (mut signers, metas) =
if let Ok(acc) = ix.get_accounts(client, &mut accounts.borrow_mut()) {
acc
} else {
return Ok(());
match ix.get_accounts(client, &mut accounts.borrow_mut())
.map_err(|e| FuzzingErrorWithOrigin::from(e).with_origin(Origin::Instruction(self.to_string()))){
Ok(acc)=>{acc},
Err(e)=>{
eprintln!("{}",e);
panic!()
}
};
let mut snaphot = Snapshot::new(&metas, ix);
snaphot.capture_before(client).unwrap();

let mut snapshot = Snapshot::new(&metas, ix);
match snapshot.capture_before(client) {
Ok(_)=>{},
Err(e)=>{
eprintln!("{}",e);
panic!()
},
};

let data =
if let Ok(data) = ix.get_data(client, &mut accounts.borrow_mut()) {
data
} else {
return Ok(());
match ix.get_data(client, &mut accounts.borrow_mut())
.map_err(|e| FuzzingErrorWithOrigin::from(e).with_origin(Origin::Instruction(self.to_string()))){
Ok(data)=>{data},
Err(e)=>{
eprintln!("{}",e);
panic!()
}
};

let ixx = Instruction {
program_id,
accounts: metas.clone(),
Expand All @@ -38,18 +54,36 @@ pub fn fuzz_test_executor(input: TokenStream) -> TokenStream {
let sig: Vec<&Keypair> = signers.iter().collect();
transaction.sign(&sig, client.get_last_blockhash());

let res = client.process_transaction(transaction);
snaphot.capture_after(client).unwrap();
let (acc_before, acc_after) = snaphot.get_snapshot().unwrap(); // we want to panic if we cannot unwrap to cause a crash
if let Err(e) = ix.check(acc_before, acc_after, data) {
eprintln!(
"Custom check after the {} instruction did not pass with the error message: {}",
self, e
);
eprintln!("Instruction data submitted to the instruction were:"); // TODO data does not implement Debug trait -> derive Debug trait on InitializeIx and automaticaly implement conversion from Initialize to InitializeIx
panic!("{}", e)
}

let res = client.process_transaction(transaction)
.map_err(|e| FuzzClientErrorWithOrigin::from(e).with_origin(Origin::Instruction(self.to_string())));


match snapshot.capture_after(client) {
Ok(_)=>{},
Err(e)=>{
eprintln!("{}",e);
panic!()
},
};
match snapshot.get_snapshot()
.map_err(|e| e.with_origin(Origin::Instruction(self.to_string()))){
Ok((acc_before, acc_after))=>{
match ix.check(acc_before, acc_after, data)
.map_err(|e| FuzzingErrorWithOrigin::from(e).with_origin(Origin::Instruction(self.to_string()))) {
Ok(_)=>{},
Err(e)=>{
eprintln!("{}",e);
panic!()
},
};
},
Err(e)=>{
eprintln!("{}",e);
panic!()

}
};
if res.is_err() {
return Ok(());
}
Expand Down
30 changes: 7 additions & 23 deletions crates/client/src/fuzzer/data_builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
use std::cell::RefCell;
use std::error::Error;
use std::fmt::Display;

use anchor_client::anchor_lang::solana_program::account_info::{Account as Acc, AccountInfo};
use anchor_client::anchor_lang::solana_program::hash::Hash;
use arbitrary::Arbitrary;
Expand All @@ -11,6 +7,11 @@ 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::error::Error;
use std::fmt::Display;

use crate::error::*;

pub struct FuzzData<T, U> {
pub pre_ixs: Vec<T>,
Expand Down Expand Up @@ -139,7 +140,7 @@ pub trait IxOps<'info> {
pre_ix: Self::IxSnapshot,
post_ix: Self::IxSnapshot,
ix_data: Self::IxData,
) -> Result<(), &'static str> {
) -> Result<(), FuzzingError> {
Ok(())
}
}
Expand Down Expand Up @@ -184,7 +185,7 @@ pub trait FuzzClient {
fn get_accounts(
&mut self,
metas: &[AccountMeta],
) -> Result<Vec<Option<Account>>, FuzzClientError>;
) -> Result<Vec<Option<Account>>, FuzzClientErrorWithOrigin>;

fn get_last_blockhash(&self) -> Hash;

Expand All @@ -194,23 +195,6 @@ pub trait FuzzClient {
) -> Result<(), FuzzClientError>;
}

#[derive(Debug)]
pub enum FuzzClientError {
CannotGetAccounts,
CannotProcessTransaction, // TODO add also custom error
ClientInitError,
}

#[derive(Debug)]
pub enum FuzzingError {
// TODO Add context with_account_name()
CannotGetAccounts,
CannotGetInstructionData,
CannotDeserializeAccount,
NotEnoughAccounts, // TODO add also custom error
AccountNotFound,
}

#[macro_export]
macro_rules! fuzz_trd {
($ix:ident: $ix_dty:ident , |$buf:ident: $dty:ident| $body:block) => {
Expand Down
115 changes: 115 additions & 0 deletions crates/client/src/fuzzer/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use solana_banks_client::BanksClientError;
use solana_sdk::pubkey::Pubkey;
use std::fmt::{Debug, Display};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum FuzzClientError {
#[error("Custom fuzzing error: {0}")]
Custom(u32),
#[error("Not able to initialize client")]
ClientInitError,
#[error("Obtained Banks Client Error: {0}")]
BanksError(#[from] BanksClientError),
}

#[derive(Debug, Error)]
pub enum FuzzingError {
#[error("Custom fuzzing error: {0}\n")]
Custom(u32),
#[error("Not able to deserialize account: {0}\n")]
DeserializeError(String),
#[error("Not Able To Obtain AccountInfos\n")]
NotAbleToObtainAccountInfos,
#[error("Balance Mismatch\n")]
BalanceMismatch,
#[error("Data Mismatch example message xyz\n")]
DataMismatch,
#[error("Unable to obtain Data\n")]
UnableToObtainData,
}
#[derive(Debug, Clone)]
pub enum Origin {
Instruction(String),
Account(Pubkey),
}

#[derive(Debug, Clone)]
pub enum Context {
Pre,
Post,
}

#[derive(Debug)]
pub struct FuzzClientErrorWithOrigin {
pub client_error: FuzzClientError,
pub origin: Option<Origin>,
pub context: Option<Context>,
}

#[derive(Debug)]
pub struct FuzzingErrorWithOrigin {
pub fuzzing_error: FuzzingError,
pub origin: Option<Origin>,
pub context: Option<Context>,
}

impl From<FuzzClientError> for FuzzClientErrorWithOrigin {
fn from(client_error: FuzzClientError) -> Self {
Self {
client_error,
origin: None,
context: None,
}
}
}

impl From<FuzzingError> for FuzzingErrorWithOrigin {
fn from(fuzzing_error: FuzzingError) -> Self {
Self {
fuzzing_error,
origin: None,
context: None,
}
}
}

// \x1b[93m ... \x1b[0m provides some text color in the terminal
// this can help to increase readability of the debug output
impl Display for FuzzClientErrorWithOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.client_error, f)?;
writeln!(f, "\x1b[93mOrigin:\x1b[0m {:#?}", &self.origin)?;
writeln!(f, "\x1b[93mContext:\x1b[0m {:#?}", &self.context)?;
Ok(())
}
}
impl Display for FuzzingErrorWithOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.fuzzing_error, f)?;
writeln!(f, "\x1b[93mOrigin:\x1b[0m {:#?}", &self.origin)?;
writeln!(f, "\x1b[93mContext:\x1b[0m {:#?}", &self.context)?;
Ok(())
}
}

impl FuzzClientErrorWithOrigin {
pub fn with_origin(mut self, origin: Origin) -> Self {
self.origin = Some(origin);
self
}
pub fn with_context(mut self, context: Context) -> Self {
self.context = Some(context);
self
}
}
impl FuzzingErrorWithOrigin {
pub fn with_origin(mut self, origin: Origin) -> Self {
self.origin = Some(origin);
self
}
pub fn with_context(mut self, context: Context) -> Self {
self.context = Some(context);
self
}
}
2 changes: 2 additions & 0 deletions crates/client/src/fuzzer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ pub mod snapshot;
pub mod snapshot_generator;

pub type AccountId = u8;

pub mod error;
28 changes: 17 additions & 11 deletions crates/client/src/fuzzer/program_test_client_blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use solana_sdk::{
use spl_token::state::Mint;
use tokio::runtime::Builder;

use crate::data_builder::{FuzzClient, FuzzClientError};
use crate::data_builder::FuzzClient;
use crate::error::*;

pub struct ProgramTestClientBlocking {
ctx: ProgramTestContext,
Expand Down Expand Up @@ -137,32 +138,37 @@ impl FuzzClient for ProgramTestClientBlocking {
}

fn get_account(&mut self, key: &Pubkey) -> Result<Option<Account>, FuzzClientError> {
self.rt
Ok(self
.rt
.block_on(self.ctx.banks_client.get_account_with_commitment(
*key,
solana_sdk::commitment_config::CommitmentLevel::Confirmed,
))
.map_err(|_| FuzzClientError::CannotGetAccounts)
))?)
}

fn get_accounts(
&mut self,
metas: &[AccountMeta],
) -> Result<Vec<Option<Account>>, FuzzClientError> {
let result: Vec<_> = metas.iter().map(|m| self.get_account(&m.pubkey)).collect();
) -> Result<Vec<Option<Account>>, FuzzClientErrorWithOrigin> {
let result: Vec<_> = metas
.iter()
.map(|m| {
self.get_account(&m.pubkey).map_err(|e| {
FuzzClientErrorWithOrigin::from(e).with_origin(Origin::Account(m.pubkey))
})
})
.collect();
result.into_iter().collect()
}

fn get_last_blockhash(&self) -> Hash {
self.ctx.last_blockhash
}

fn process_transaction(
&mut self,
transaction: impl Into<VersionedTransaction>,
) -> Result<(), FuzzClientError> {
self.rt
.block_on(self.ctx.banks_client.process_transaction(transaction))
.map_err(|_| FuzzClientError::CannotProcessTransaction)
Ok(self
.rt
.block_on(self.ctx.banks_client.process_transaction(transaction))?)
}
}
Loading

0 comments on commit efde295

Please sign in to comment.