Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🥅 fuzzer error handling #118

Merged
merged 8 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
18 changes: 13 additions & 5 deletions crates/client/derive/fuzz_test_executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ pub fn fuzz_test_executor(input: TokenStream) -> TokenStream {
quote! {
#enum_name::#variant_name (ix) => {
let (mut signers, metas) =
if let Ok(acc) = ix.get_accounts(client, &mut accounts.borrow_mut()) {
if let Ok(acc) = ix.get_accounts(client, &mut accounts.borrow_mut())
.map_err(|e| e.with_origin(Origin::Instruction(self.to_string()))) {
acc
} else {
return Ok(());
};
let mut snaphot = Snapshot::new(&metas, ix);
// this can return FuzzClientErrorWithOrigin
snaphot.capture_before(client).unwrap();
let data =
if let Ok(data) = ix.get_data(client, &mut accounts.borrow_mut()) {
if let Ok(data) = ix.get_data(client, &mut accounts.borrow_mut())
.map_err(|e| e.with_origin(Origin::Instruction(self.to_string()))) {
data
} else {
return Ok(());
Expand All @@ -38,10 +41,15 @@ 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);
let res = client.process_transaction(transaction)
.map_err(|e| e.with_origin(Origin::Instruction(self.to_string())));

// this can return FuzzClientErrorWithOrigin
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) {
let (acc_before, acc_after) = snaphot.get_snapshot()
.map_err(|e| e.with_origin(Origin::Instruction(self.to_string()))).unwrap(); // we want to panic if we cannot unwrap to cause a crash

if let Err(e) = ix.check(acc_before, acc_after, data).map_err(|e| e.with_origin(Origin::Instruction(self.to_string()))) {
eprintln!(
"Custom check after the {} instruction did not pass with the error message: {}",
self, e
Expand Down
32 changes: 8 additions & 24 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 Expand Up @@ -246,7 +230,7 @@ pub fn build_ix_fuzz_data<U: for<'a> Arbitrary<'a>, T: FuzzDataBuilder<U>, V: De
pub fn get_account_infos_option<'info>(
accounts: &'info mut [Option<Account>],
metas: &'info [AccountMeta],
) -> Result<Vec<Option<AccountInfo<'info>>>, Box<dyn Error + 'static>> {
) -> Result<Vec<Option<AccountInfo<'info>>>, FuzzingError> {
let iter = accounts.iter_mut().zip(metas);
let r = iter
.map(|(account, meta)| {
Expand Down
172 changes: 172 additions & 0 deletions crates/client/src/fuzzer/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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: {0}")]
ClientInitError(#[from] std::io::Error),
// Box for Error variant too Long warnings
#[error("Banks Client Error: {0}")]
BanksError(Box<BanksClientError>),
}

#[derive(Debug, Error)]
pub enum FuzzingError {
#[error("Custom fuzzing error: {0}\n")]
Custom(u32),
#[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 From<BanksClientError> for FuzzClientError {
fn from(value: BanksClientError) -> Self {
Self::BanksError(Box::new(value))
}
}

impl FuzzClientError {
pub 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 {
let mut error_with_origin = FuzzClientErrorWithOrigin::from(self);
error_with_origin.context = Some(context);
error_with_origin
}
}

impl FuzzingError {
pub fn with_origin(self, origin: Origin) -> FuzzingErrorWithOrigin {
let mut error_with_origin = FuzzingErrorWithOrigin::from(self);
error_with_origin.origin = Some(origin);
error_with_origin
}
pub fn with_context(self, context: Context) -> FuzzingErrorWithOrigin {
let mut error_with_origin = FuzzingErrorWithOrigin::from(self);
error_with_origin.context = Some(context);
error_with_origin
}
}

#[derive(Debug)]
pub enum Origin {
Instruction(String),
Account(Pubkey),
}

impl Display for Origin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Origin: {:#?}", self)
}
}

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

impl Display for Context {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Context: {:#?}", self)
}
}

#[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,
}
}
}
impl Display for FuzzClientErrorWithOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.client_error, f)?;
if let Some(o) = &self.origin {
Display::fmt(o, f)?;
}
if let Some(c) = &self.context {
Display::fmt(c, f)?;
}
Ok(())
}
}
impl Display for FuzzingErrorWithOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.fuzzing_error, f)?;
if let Some(o) = &self.origin {
Display::fmt(o, f)?;
}
if let Some(c) = &self.context {
Display::fmt(c, f)?;
}
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;
32 changes: 17 additions & 15 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 All @@ -28,10 +29,7 @@ impl ProgramTestClientBlocking {
// .enable_all()
// .build()
// .map_err(|_| FuzzClientError::ClientInitError)?;
let rt: tokio::runtime::Runtime = Builder::new_current_thread()
.enable_all()
.build()
.map_err(|_| FuzzClientError::ClientInitError)?;
let rt: tokio::runtime::Runtime = Builder::new_current_thread().enable_all().build()?;

let ctx = rt.block_on(program_test.start_with_context());
Ok(Self { ctx, rt })
Expand Down Expand Up @@ -137,32 +135,36 @@ 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| 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
Loading