Fuzz Instructions#
Trident defines the FuzzInstruction
enum, which contains all available Instructions within your program. Each enum variant corresponds to one instruction within your program. This enum is crucial for defining how different instructions behave during fuzz testing.
The enum variants additionally contain their corresponding structures for Accounts and Input arguments.
#[derive(Arbitrary, DisplayIx, FuzzTestExecutor)]
pub enum FuzzInstruction {
Initialize(Initialize),
Update(Update),
}
#[derive(Arbitrary, Debug)]
pub struct Initialize {
pub accounts: InitializeAccounts,
pub data: InitializeData,
}
#[derive(Arbitrary, Debug)]
pub struct Update {
pub accounts: UpdateAccounts,
pub data: UpdateData,
}
// ...
Instruction Behavior#
Each Instruction variant must define the IxOps
trait, which contains the following methods:
get_discriminator()
(automatically implemented)get_program_id()
(automatically implemented)get_data()
(required)get_accounts()
(required)check()
(optional)tx_error_handler()
(optional)
get_discriminator()
#
This method specifies the discriminator of the instruction.
Tip
- The IDL created by Anchor 0.29.0 and lower does not contain the discriminator. In that case, the discriminator is generated automatically.
- If you are experiencing errors with the wrong discriminator, you can specify it manually by hashing
global:<instruction_name>
with SHA256 and taking the first 8 bytes.
get_program_id()
#
This method specifies the program ID to which the Instruction corresponds.
get_data()
#
This method specifies what the Instruction Input Data should look like. You can use completely random data generated by the fuzzer, such as:
fn get_data(
&self,
client: &mut impl FuzzClient,
fuzz_accounts: &mut FuzzAccounts,
) -> Result<Vec<u8>, FuzzingError> {
let mut args: Vec<u8> = self.get_discriminator();
{
args.extend(borsh::to_vec(&self.data.input).unwrap());
}
Ok(args)
}
You can also use always constant values:
fn get_data(
&self,
client: &mut impl FuzzClient,
fuzz_accounts: &mut FuzzAccounts,
) -> Result<Vec<u8>, FuzzingError> {
let mut args: Vec<u8> = self.get_discriminator();
{
// The constant value 5 is used as an example
args.extend(borsh::to_vec(5).unwrap());
}
Ok(args)
}
Additionally, you can limit the range of the data generated using the Arbitrary
crate. Check Arbitrary Data.
get_accounts()
#
This method specifies the Accounts that will be used for the corresponding Instruction. To use multiple combinations of accounts, Trident utilizes the AccountStorage, where accounts are stored across the fuzzing process.
There are three main functions to use within the get_accounts()
:
For additional methods, check Account Storage Methods.
get_or_create_account()
#
Retrieves a record from AccountsStorage based on the entered account_id
. If no record exists for the account_id
, a new empty account is created.
Tip
- This function is particularly useful for instructions that initialize accounts.
Example:
get()
#
Retrieves a record from AccountsStorage based on the entered account_id
. If no record exists for the account_id
, a random public key is returned.
Tip
- This function is particularly useful for instructions other than those that perform initialization.
Example:
set_account_custom()
#
If the previous two functions are insufficient, you can use set_account_custom()
to manually set an account with data you select.
Tip
Example:
let author = fuzz_accounts.author_hello_world.get_or_create_account(
self.accounts.author,
client,
500 * LAMPORTS_PER_SOL,
);
client.set_account_custom(
&author.pubkey(),
&AccountSharedData::create(
10 * LAMPORTS_PER_SOL,
vec![1, 2, 3, 4, 5, 6, 7, 8, 9],
solana_sdk::system_program::ID,
false,
0,
),
);
check()
#
This method provides an Invariant Check for the corresponding Instruction. Check Invariant Checks.
tx_error_handler()
#
This method provides a Tx Error Handler for the corresponding Instruction. Check Error Handler.
Example#
Tip
Consider checking the Examples section for more tips.