trident_fuzz/
ix_ops.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#![allow(dead_code)]

use crate::error::*;
use crate::fuzz_client::FuzzClient;
use crate::fuzz_deserialize::FuzzDeserialize;
use anchor_lang::solana_program::account_info::AccountInfo;
use anchor_lang::InstructionData;
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: InstructionData;
    /// The accounts to be passed as instruction accounts
    type IxAccounts;
    /// The structure to which the instruction accounts will be deserialized
    type IxSnapshot: FuzzDeserialize<'info>;

    /// Specify Program ID to which the Instruction corresponds. This is particularly helpful when using multiple
    /// programs in the workspace, to differentiate between possible program calls.
    fn get_program_id(&self) -> solana_sdk::pubkey::Pubkey;

    /// 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<Self::IxData, FuzzingError>;

    /// 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<Keypair>, Vec<AccountMeta>), 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_accounts(pre_ix_acc_infos)
    /// ```
    #[allow(unused_variables)]
    fn tx_error_handler(
        &self,
        e: FuzzClientErrorWithOrigin,
        ix_data: Self::IxData,
        pre_ix_acc_infos: &mut &'info [Option<AccountInfo<'info>>],
    ) -> Result<(), FuzzClientErrorWithOrigin> {
        Err(e)
    }

    /// A method implemented for each instruction variant.
    /// This method calls the corresponding `deserialize_option`, which is defined
    /// by deriving the `AccountsSnapshot` macro.
    /// No changes are needed for this function.
    fn deserialize_accounts(
        &self,
        accounts: &mut &'info [Option<AccountInfo<'info>>],
    ) -> Result<Self::IxSnapshot, FuzzingError> {
        Self::IxSnapshot::deserialize_option(&self.get_program_id(), accounts)
    }
}