Fuzz Instructions#
Trident defines FuzzInstruction
enum containing all available Instructions within your program.
The enum variants additionally contains their corresponding structures for Accounts and Input arguments.
#[derive(Arbitrary, DisplayIx, FuzzTestExecutor, FuzzDeserialize)]
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 has to define IxOps
trait containing the following methods:
get_program_id()
(automatically implemented)get_data()
(required)get_accounts()
(required)check()
(optional)tx_error_handler()
(optional)deserialize_accounts()
(automatically implemented)
get_program_id()
#
This method specifies program ID to which the Instruction corresponds.
In case you have only one program in the Anchor Workspace it is not really important. The importance occurs when you have multiple programs in the Workspace and you want to call Instructions of every Program. In that case each Instruction Variant corresponds to its program by the Program ID.
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<Self::IxData, FuzzingError> {
let data = hello_world::instruction::InitializeFn {
input: self.data.input,
};
Ok(data)
}
You can also use always constant values
fn get_data(
&self,
_client: &mut impl FuzzClient,
_fuzz_accounts: &mut FuzzAccounts,
) -> Result<Self::IxData, FuzzingError> {
let data = hello_world::instruction::InitializeFn {
input: 5,
};
Ok(data)
}
Or you can customize the Data using the Arbitrary crate. Check Arbitrary Data.
Custom Data Types#
If you use Custom Types as Instruction data arguments, you may encounter a problem that the Custom Type does not implement
Derive Debug and Arbitrary traits inside the Fuzz Test#
You can redefine the custom type within the fuzz_instructions.rs
file, along with all the necessary traits.
// Redefine the Custom Type inside the fuzz_instructions.rs,
// but this time with all of the required traits.
#[derive(Arbitrary,Debug, Clone, Copy)]
pub enum CustomEnumInputFuzz {
InputVariant1,
InputVariant2,
InputVariant3,
}
Then, you would also need to implement the std::convert::From<T>
trait to enable conversion between the newly defined Custom Type and the Custom Type used within your program.
Tip
Consider checking the Examples section for more tips.
get_accounts()
#
This method specifies how the Accounts for the corresponding Instruction should be resolved. You can use accounts stored within the FuzzAccounts Account Storages, or you can define custom Account using the client.
There are two main functions to use within the get_accounts()
:
get_or_create_account()
#
Insert a new record into AccountsStorage based on the account_id
. If a record with the entered account_id
already exists, it is returned, and no insertion is performed.
Tip
- This function is particularly useful for instructions that initialize accounts.
Example:
let hello_world_account = fuzz_accounts.hello_world_account.get_or_create_account(
self.accounts.hello_world_account,
client,
&[b"hello_world_seed"],
&hello_world::ID,
);
hello_world
's AccountsStorage if a record for the entered account_id
exists. If not, the function will create a new record corresponding to the PDA derived from the provided seeds and return the PDA. 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:
let hello_world_account = fuzz_accounts
.hello_world_account
.get(self.accounts.hello_world_account);
hello_world
's AccountsStorage if a record for the entered account_id
exists. If not, the function will return a random public key. set_custom()
#
If the previous two functions are insufficient, you can use set_custom()
to manually set an account with data you select. This function accepts account_id
, which specifies the record in the corresponding AccountsStorage; client
, which handles the insertion of AccountSharedData; the account's address
(particularly helpful if a predefined address is needed); and AccountSharedData
, which you can configure as needed.
Tip
Example:
let address = Keypair::new();
fuzz_accounts.hello_world_account.set_custom(
self.accounts.hello_world_account,
client,
address.pubkey(),
AccountSharedData::new(10 * LAMPORTS_PER_SOL, 0, &solana_sdk::system_program::ID),
);
set_account
function will insert the specified AccountSharedData
into the client and create a record in the corresponding AccountsStorage based on the entered account_id
. check()
#
This method provides Invariant Check for the corresponding Instruction. Check Invariant Checks.
tx_error_handler()
#
This method provides Tx Error Handler for the corresponding Instruction. Check Error Handler.
Example#
Tip
Consider checking the Examples section for more tips.