Ackee-Blockchain / trident

Rust-based framework to Fuzz and Integration test Solana programs to help you ship secure code.
https://ackee.xyz/trident/docs/latest/
MIT License
205 stars 18 forks source link

Feat/custom error handler #145

Closed Ikrk closed 7 months ago

Ikrk commented 7 months ago

This PR introduces a way to override the transaction error handler and provide a custom implementation.

Context: The fuzzer generates a sequence of one or more instructions that are processed/executed sequentially. Originally, in case of failed transaction (instruction), the fuzzer skipped the remaining instructions in the sequence and continued with a new fuzzer iteration. This has been modified unintentionally during refactoring in a way, that a failed transaction did not skip the subsequent instructions. Therefore also sequences with instructions that all fail are completely executed.

With this PR, the default behavior was reverted so that the remaining instructions are skipped in case of transaction failure.

The default behavior can be overridden by providing a custom implementation of the tx_error_handler method of the IxOps trait. To continue to process remaining instructions in a sequence after a given instruction execution failed, it is possible to simply return an Ok(()) result such as:

fn tx_error_handler(
        &self,
        e: FuzzClientErrorWithOrigin,
        ix_data: Self::IxData,
        pre_ix_acc_infos: &'info mut [Option<AccountInfo<'info>>],
    ) -> Result<(), FuzzClientErrorWithOrigin> {
        Ok(())
    }

In order to ignore the transaction errors and continue with the ix sequence execution, it is necessary to provide this implementation of tx_error_handler method for each instruction. One global setting might be implemented in the future if it will be necessary/useful.

Besides that, this method can be used to inspect the kinds of transaction errors. As described in the trait's docs:

    /// 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)
    /// ```

If this PR will be merged, it would make sense to also extend the web docs.