\ No newline at end of file
diff --git a/dev/search/search_index.json b/dev/search/search_index.json
index a252f0a3..acd599c8 100644
--- a/dev/search/search_index.json
+++ b/dev/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"Trident
Rust-based Fuzzing framework for Solana programs to help you ship secure code.
Getting Started
Install the Trident Fuzz Testing Framework
Installation
Start Fuzzing
Focus on security and start fuzzing immediately
Start Fuzzing
Check the GitHub for unreleased features
Check our GitHub repository to see the unreleased features
Trident Repository
Trident by Examples
Try the Fuzzing Examples
Trident Examples
"},{"location":"#what-is-fuzzing","title":"What is Fuzzing ?","text":"
\"Fuzz testing is an automated technique that provides generated random, invalid, or unexpected input data to your program. This helps discover unknown bugs and vulnerabilities, potentially preventing zero-day exploits.\"
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning (SemVer).
Note: Version 0 of Semantic Versioning is handled differently from version 1 and above. The minor version will be incremented upon a breaking change and the patch version will be incremented for features.
impr/ allow to derive AccountsSnapshots for empty Account Context (209)
impr/ fuzz flags are read at start of fuzzing session from Config instead of env variable and transaction dispatch was added to increase FuzzTestExecutor readability (204)
impr/ allow various instructions to be generated in case of multiple programs in the Anchor workspace (200)
feat/ option to add account into Fuzz Test environment with base64 data (197)
impr/ instead of parsing source code and creating our IDL, read anchor IDL (198)
Removed
del/remove integration tests supported by Trident, this feature adds more unnecessary overhead compared to its value (196)
Upgrade Solana (~1.10) and Anchor framework (~0.25) versions
Added
Custom Solana RPC error reporter. If the Solana RPC error is thrown, the error code, message and data (logs) are reported to the output.
Custom imports in the .program_client. User is able to import custom types and structures into program client. The import part of the code would not be re-generated.
Trident is now configurable. This requires Trident.toml file to exist in the project's root directory - without this file the execution will fail. To solve this re-run trident init or just create an empty Trident.toml file in the project's root directory.
Run Honggfuzz debug on the specified Fuzz Target (i.e. the Fuzz Template, for example fuzz_0), with specified crash file, to see where the crash file found an issue.
The debug output is at current development stage really verbose and contains lldb parts. We are working on improving this experience. In the picture below you can see an example of provided debug output.
Series of Transaction Logs
Structures of data send within the Instructions
Panic or Crash, based on if the Fuzzing panicked within the Solana Program or Invariant Check failed.
Calls anchor clean and cleans targets created by the underlying Honggfuzz. Crashfiles and Fuzzing Inputs are preserved.
"},{"location":"examples/examples/","title":"Trident by Examples","text":"
Hello World!
Hello World example with Trident.
Hello World!
Possible vulnerabilities and bugs
Check the possible attack vectors and bugs that can be detected using Trident.
Unchecked Arithmetic
Incorrect Instruction Sequence
Unauthorized Access
Incorrect Integer Arithmetic
Customize with Arbitrary
You can use Arbitrary crate to your advantage and limit or customize the data that are sent to the instructions.
Custom Data Types
Limiting Instructions Inputs
Cross-Program Invocation
Trident supports Cross-Program Invocation, you can fuzz your programs and create NFTs at the same time.
Simple CPI
CPI with Metaplex Metadata Program
"},{"location":"faq/faq/","title":"FAQ","text":""},{"location":"faq/faq/#is-trident-supported-only-with-anchor","title":"Is Trident supported only with Anchor ?","text":"
Currently yes, Trident under the hood obtains data from the IDL generated by Anchor and it has to have access to the AccountsSnapshots derived for each Instruction Context.
"},{"location":"faq/faq/#i-created-the-fuzz-test-what-should-i-do-next","title":"I created the Fuzz Test what should I do next ?","text":"
Start here Writing Fuzz Tests. For additional features check Features. If you are not sure about anything check Get Help
"},{"location":"faq/faq/#my-program-instruction-contains-custom-type-such-as-struct-or-enum-on-its-input-but-it-does-not-derive-arbitrary","title":"My program Instruction contains custom type such as Struct or Enum on its input, but it does not derive Arbitrary.","text":"
In this case you need to specify same type in the Fuzz Test (with the same fields). And implement From Trait to convert to your type. Check Custom Data Types or Examples of Arbitrary.
"},{"location":"faq/faq/#i-would-like-to-report-issue-with-trident-what-should-i-do","title":"I would like to report Issue with Trident, what should I do ?","text":"
Write Issue Issues
"},{"location":"faq/faq/#is-trident-deployed-on-mainnet-devnet-testenet","title":"Is Trident deployed on Mainnet / Devnet / Testenet ?","text":"
No, Trident is Fuzz Testing Framework, not Solana Program.
"},{"location":"faq/faq/#what-type-of-fuzzer-trident-is","title":"What type of Fuzzer Trident is ?","text":"
Currently, we refer to it as \"coverage guided gray box fuzzer\".
Trident allows developers to generate random accounts for fuzzing.
However, the Accounts are not completely random, and neither are the Account addresses.
Instead, Trident generates random AccountIDs which are indexes to Account Storages. Each unique Account contained within the Anchor generated IDL has its own AccountStorage. The FuzzAccounts containing the Accounts Storages is global to all Instructions to use.
Note
Details:
Always generating only random accounts would in most cases lead to a situation where the fuzzer would be stuck because the accounts would be almost every time rejected by your Anchor program. Therefore it is necessary to specify, what accounts should be used and also limit the number of newly created accounts to reduce the space complexity.
Trident allows you to customize Instruction Data to provide structure.
For example your Initialize Instruction expects two arguments start_at and end_at you know that in order for the Instruction to make sense, it is required that the start_at < end_at. Moreover, there should be significant difference between these two. This can be utilized with the Arbitrary crate.
#[derive(Arbitrary, Debug)]\npub struct InitVestingData {\n pub recipient: AccountId,\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(1..=1_000_000)\n )]\n pub amount: u64,\n // we want start_at smaller than end_at\n // and for testing purposes we can run tests with times from the past\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(0..=1_000_000)\n )]\n pub start_at: u64,\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(1_001_001..=1_050_000)\n )]\n pub end_at: u64,\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(1..=1000)\n )]\n pub interval: u64,\n}\n
"},{"location":"features/fuzz-instructions/#get-program-id","title":"Get Program ID","text":"
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.
Or you can customize the Data using the Arbitrary crate. Check Arbitrary Data.
"},{"location":"features/fuzz-instructions/#custom-data-types","title":"Custom Data Types","text":"
If you use Custom Types as Instruction data arguments, you may encounter a problem that the Custom Type does not implement
Debug trait
Arbitrary trait
"},{"location":"features/fuzz-instructions/#derive-debug-and-arbitrary-traits-inside-the-fuzz-test","title":"Derive Debug and Arbitrary traits inside the Fuzz Test","text":"
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,\n// but this time with all of the required traits.\n#[derive(Arbitrary,Debug, Clone, Copy)]\npub enum CustomEnumInputFuzz {\n InputVariant1,\n InputVariant2,\n InputVariant3,\n}\n
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.
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.
Important
Source Code below
Take the author from the FuzzAccounts Account Storage author on self.accounts.author index. If Account on that index does not exist yet, it will be created and returned. In case it is already created, the corresponding Account will be returned.
hello_world_account Account Storage is of type PdaStore, in this case get_or_create_account function do the same as for the authority, but you need to specify seeds to derive its PDA.
Next, you need to specify signers if there should be any.
Lastly, specify the Account Metas of the corresponding Instruction.
for example <program_name>::accounts::<context_name> {}.to_account_metas(None);
"},{"location":"features/fuzz-instructions/#create-an-arbitrary-account","title":"Create an arbitrary account","text":"
The AccountsStorage<T> type provides an implementation of the get_or_create_account method that helps you create new or read already existing accounts. There are different implementations for different types of storage (Keypair, TokenStore, MintStore, PdaStore) to simplify the creation of new accounts.
However, there are cases when the provided implementation is not sufficient and it is necessary to create an account manually. These cases can be (but are not limited to) for example:
you need to create a new account with a predefined address
you need to create a new account that is not owned by the system program
you need to create and initialize a new PDA account
your program expects an account to be initialized in a previous instruction
In that case, you can use the storage method of the AccountsStorage<T> struct that exposes the underlying HashMap<AccountId, T> and you can add new accounts directly to it.
It is possible to create and store any kind of account. For example:
to add an account that uses the #[account(zero)] anchor constraint (must be rent exempt, owned by your program, with empty data):
let state = fuzz_accounts\n .state\n // gets the storage of all `state` account variants\n .storage()\n // returns the Keypair of the `state` account with\n // the given `AccountId` if it has been added previously\n .entry(self.accounts.state)\n .or_insert_with(|| {\n let space = State::SIZE;\n let rent_exempt_lamports = client.get_rent().unwrap()\n .minimum_balance(space);\n let keypair = Keypair::new();\n let account = AccountSharedData::new_data_with_space::<[u8; 0]>(\n rent_exempt_lamports,\n &[],\n space,\n &my_program::id(),\n ).unwrap();\n // insert the custom account also into the client\n client.set_account_custom(&keypair.pubkey(), &account);\n keypair\n });\n
to add a new system-owned account with a specific PDA (address):
let rent_exempt_for_token_acc = client\n .get_rent()\n .unwrap()\n .minimum_balance(anchor_spl::token::spl_token::state::Account::LEN);\n\nlet my_pda = fuzz_accounts\n .my_pda\n // gets the storage of all `my_pda` account variants\n .storage()\n // returns the PdaStore struct of the `my_pda` account with\n // the given `AccountId` if it has been added previously\n .entry(self.accounts.my_pda)\n .or_insert_with(|| {\n let seeds = &[b\"some-seeds\"];\n let pda = Pubkey::find_program_address(seeds, &my_program::id()).0;\n let account = AccountSharedData::new_data_with_space::<[u8; 0]>(\n rent_exempt_for_token_acc,\n &[],\n 0,\n &SYSTEM_PROGRAM_ID,\n ).unwrap();\n // insert the custom account also into the client\n client.set_account_custom(&pda, &account);\n let vec_of_seeds: Vec<Vec<u8>> = seeds.iter().map(|&seed| seed.to_vec())\n .collect();\n PdaStore {\n pubkey: pda,\n seeds: vec_of_seeds,\n }\n }).pubkey();\n
Number of invocations of each instruction during the fuzzing session.
Number of successful invocations of each instruction during the fuzzing session.
Number of failed invariants checks for each instruction during the fuzzing session.
Note
Keep in mind that the number of fuzz iterations does not directly correspond to the total number of invocations. In one fuzz iteration, the fuzzer might be unable to deserialize fuzz data into instructions, causing the entire iteration to be skipped.
On the other hand this is expected behavior as the underlying data are randomly (with coverage feedback) generated, so the Honggfuzz will not necessarily find appropriate data each iteration.
Tip
Consider checking the Examples section for more tips.
In case of multiple programs within the Anchor Workspace. Make sure that all of the programs you would like to call Cross Program Invocation to are included in the initial state of the Fuzz Test Environment.
Important
Source code below:
fuzzing_program_callee is included in the ProgramTestClientBlocking
fuzzing_program_caller is included in the ProgramTestClientBlocking
Trident allows you to specify Custom Instruction Sequences you would like to execute.
Possible Instruction sequences are split into 3 parts
pre-Instructions
Instructions
post-Instructions
For example if you program always needs to start with some kind of Initialization instruction, you can specify this Initialize Instruction in pre_ixs as shown in the source code below.
Tip
returning Ok(vec![]) will result in None Instructions executed in the corresponding part.
// test_fuzz.rs\n\n// do not forget to include the required structures\nuse fuzz_instructions::InitVesting;\nuse fuzz_instructions::WithdrawUnlocked;\n\nimpl FuzzDataBuilder<FuzzInstruction> for MyFuzzData {\n fn pre_ixs(\n u: &mut arbitrary::Unstructured\n ) -> arbitrary::Result<Vec<FuzzInstruction>> {\n let init_ix =\n FuzzInstruction::InitVesting(InitVesting::arbitrary(u)?);\n\n Ok(vec![init_ix])\n }\n fn ixs(\n u: &mut arbitrary::Unstructured\n ) -> arbitrary::Result<Vec<FuzzInstruction>> {\n let withdraw_ix =\n FuzzInstruction::WithdrawUnlocked(WithdrawUnlocked::arbitrary(u)?);\n\n Ok(vec![withdraw_ix])\n }\n fn post_ixs(\n _u: &mut arbitrary::Unstructured\n ) -> arbitrary::Result<Vec<FuzzInstruction>> {\n Ok(vec![])\n }\n}\n
Tip
Consider checking the Examples section for more tips.
Trident allows you to (optionally) specify Invariant Checks for each Instruction.
The Invariant Check will be called after the Instruction was successfully invoked. Within the Invariant Check you can compare the contents of Accounts before and after the Instruction was called.
Important
Returning error in the Invariant Check is considered as detected undesired behavior (i.e. issue/crash detected).
fn check(\n &self,\n _pre_ix: Self::IxSnapshot,\n post_ix: Self::IxSnapshot,\n _ix_data: Self::IxData,\n) -> Result<(), FuzzingError> {\n if let Some(hello_world_account) = post_ix.hello_world_account {\n if hello_world_account.input == 253 {\n return Err(FuzzingError::Custom(1));\n }\n }\n Ok(())\n}\n
Tip
Consider checking the Examples section for more tips.
"},{"location":"features/lifecycle/","title":"Fuzz Test Lifecycle","text":"
In the sequence diagram below you can see a simplified fuzz test lifecycle.
Some diagram states are labeled with emojis:
\u26a1 Mandatory methods that must be implemented by the user.
\ud83d\udc64 Optional methods that can be implemented by the user.
The maximal number of iterations is reached (if specified).
A crash was detected and the exit_upon_crash parameter was set.
User interrupted the test manually (for example by hitting CTRL+C).
In each iteration, the fuzzer generates a sequence of random instructions to execute.
User can optionally customize how the instructions are generated and can specify the instructions that should be executed at the beginning (pre_ixs), in the middle (ixs) and at the end (post_ixs) of each iteration. This can be useful for example if your program needs an initialization or you want to fuzz some specific program state.
For each instruction:
User defined mandatory method get_accounts() is called to collect necessary instruction accounts.
User defined mandatory method get_data() is called to collect instruction data.
A snapshot of all instruction accounts before the instruction execution is saved.
The instruction is executed.
A snapshot of all instruction accounts after the instruction execution is saved.
User defined optional method check() is called to check accounts data and evaluate invariants.
fuzzer_iterations = 0fuzzer_iterations = 0fuzzer_iterations <\u00a0max_iterationsfuzzer_iterations <...donedonecreate pre-instruction\u00a0accounts snapshotscreate pre-instruction...execute instructionexecute instructioncreate post-instruction\u00a0accounts snapshotscreate post-instruction...check invariants \ud83d\udc64check invariants \ud83d\udc64fuzzer_iterations++fuzzer_iterations++Generate instructionspre_ixs \ud83d\udc64pre_ixs \ud83d\udc64ixs \ud83d\udc64ixs \ud83d\udc64post_ixs \ud83d\udc64post_ixs \ud83d\udc64endendfor ix in instructionsfor ix in instructionsget instruction accounts \u26a1get instruction accounts \u26a1get instruction data \u26a1get instruction data \u26a1next ixnext ixText is not SVG - cannot display"},{"location":"features/limitations/","title":"Current limitations","text":"
This section summarizes some known limitations in the current development stage. Further development will be focused on resolving these limitations.
Remaining accounts in check methods are not supported.
Trident allows you to specify genesis programs (SBF targets) that will be included in the fuzzing environment in order to perform all desired Cross-Program-Invocations.
Including these types of programs will lead to performance decline of Trident.
Trident allwos you to specify genesis accounts to inlcude in the fuzzing environment. In most cases it is helpful to dump some accounts from mainnet and use then during testing.
Allow processing of duplicate transactions. Setting to true might speed up fuzzing but can cause false positive crashes (default: false)
[fuzz]\n# Allow processing of duplicate transactions.\n# Setting to true might speed up fuzzing but can cause\n# false positive crashes (default: false)\nallow_duplicate_txs = false\n
Trident will show statistics after the fuzzing session. This option forces use of honggfuzz parameter keep_output as true in order to be able to catch fuzzer stdout. (default: false)
[fuzz]\n# Trident will show statistics after the fuzzing session.\n# This option forces use of honggfuzz parameter\n# `keep_output` as true in order to be able to catch fuzzer stdout.\n# (default: false)\nfuzzing_with_stats = true\n
Save all test-cases (not only the unique ones) by appending the current time-stamp to the filenames (default: false)
[honggfuzz]\n# Save all test-cases\n# (not only the unique ones) by appending the current\n# time-stamp to the filename (default: false)\nsave_all = false\n
Tip
Consider checking the Examples section for more tips.
If you have Trident already initialized, you can add new fuzz test using trident fuzz add.
"},{"location":"writing-fuzz-test/writing-fuzz-test/#fill-the-fuzz-test-template","title":"Fill the Fuzz test Template","text":""},{"location":"writing-fuzz-test/writing-fuzz-test/#derive-accountssnapshots","title":"Derive AccountsSnapshots","text":"
For every Account Context specified in the Anchor project derive AccountsSnapshots such as:
Define AccountsStorage type for each Account you would like to use.
Important
Keep in mind:
You do not need to specify every AccountStorage, some accounts do not necessarily need to be stored in their corresponding storage.
For example System Program does not need to be stored, rather can be used from the solana_sdk.
If you are about to Initialize Mint or Token Account in your Solana Program.
use Keypair or PdaStore (not MintStore or TokenStore).
If you are going to initialize Associated Token Account in your Solana Program.
use PdaStore.
You can rename FuzzAccounts fields to whatever you want. The default names were generated based on the Program's IDL.
#[doc = r\" Use AccountsStorage<T> where T can be one of:\"]\n#[doc = r\" Keypair, PdaStore, TokenStore, MintStore, ProgramStore\"]\n#[derive(Default)]\npub struct FuzzAccounts {\n author: AccountsStorage<Keypair>,\n hello_world_account: AccountsStorage<PdaStore>,\n // No need to fuzz system_program\n // system_program: AccountsStorage<todo!()>,\n}\n
Tip
For more details about the AccountsStorage check AccountsStorage.
Each Instruction in the Fuzz Test has to have defined the following functions:
get_program_id()
Specifies to which program the Instruction belongs. This function is automatically defined and should not need any updates. The importance is such that if you have multiple programs in your workspace, Trident can generate Instruction Sequences of Instruction corresponding to different programs.
get_data()
Specifies what Instruction inputs are send to the Program Instructions.
get_accounts()
Specifies what Accounts are send to the Program Instructions.
Tip
For more info about how to write these functions, check the Fuzz Instructions.
For the examples how to write these functions, check the Examples.
To debug your program using Honggfuzz with values from a crash file:
# fuzzer will run the <TARGET_NAME> with the specified <CRASH_FILE_PATH>\ntrident fuzz debug-hfuzz <TARGET_NAME> <CRASH_FILE_PATH>\n
Tip
By default, the crashfiles are stored in the
trident-tests/fuzz_tests/fuzzing/honggfuzz/hfuzz_workspace/<FUZZ_TARGET> for Hongfuzz and
Tip
For more info about the fuzzing outputs chech the Commands
"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Home","text":"Trident
Rust-based Fuzzing framework for Solana programs to help you ship secure code.
Getting Started
Install the Trident Fuzz Testing Framework
Installation
Start Fuzzing
Focus on security and start fuzzing immediately
Start Fuzzing
Check the GitHub for unreleased features
Check our GitHub repository to see the unreleased features
Trident Repository
Trident by Examples
Try the Fuzzing Examples
Trident Examples
"},{"location":"#what-is-fuzzing","title":"What is Fuzzing ?","text":"
\"Fuzz testing is an automated technique that provides generated random, invalid, or unexpected input data to your program. This helps discover unknown bugs and vulnerabilities, potentially preventing zero-day exploits.\"
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning (SemVer).
Note: Version 0 of Semantic Versioning is handled differently from version 1 and above. The minor version will be incremented upon a breaking change and the patch version will be incremented for features.
impr/ allow to derive AccountsSnapshots for empty Account Context (209)
impr/ fuzz flags are read at start of fuzzing session from Config instead of env variable and transaction dispatch was added to increase FuzzTestExecutor readability (204)
impr/ allow various instructions to be generated in case of multiple programs in the Anchor workspace (200)
feat/ option to add account into Fuzz Test environment with base64 data (197)
impr/ instead of parsing source code and creating our IDL, read anchor IDL (198)
Removed
del/remove integration tests supported by Trident, this feature adds more unnecessary overhead compared to its value (196)
Upgrade Solana (~1.10) and Anchor framework (~0.25) versions
Added
Custom Solana RPC error reporter. If the Solana RPC error is thrown, the error code, message and data (logs) are reported to the output.
Custom imports in the .program_client. User is able to import custom types and structures into program client. The import part of the code would not be re-generated.
Trident is now configurable. This requires Trident.toml file to exist in the project's root directory - without this file the execution will fail. To solve this re-run trident init or just create an empty Trident.toml file in the project's root directory.
Run Honggfuzz debug on the specified Fuzz Target (i.e. the Fuzz Template, for example fuzz_0), with specified crash file, to see where the crash file found an issue.
The debug output is at current development stage really verbose and contains lldb parts. We are working on improving this experience. In the picture below you can see an example of provided debug output.
Series of Transaction Logs
Structures of data send within the Instructions
Panic or Crash, based on if the Fuzzing panicked within the Solana Program or Invariant Check failed.
Calls anchor clean and cleans targets created by the underlying Honggfuzz. Crashfiles and Fuzzing Inputs are preserved.
"},{"location":"examples/examples/","title":"Trident by Examples","text":"
Hello World!
Hello World example with Trident.
Hello World!
Possible vulnerabilities and bugs
Check the possible attack vectors and bugs that can be detected using Trident.
Unchecked Arithmetic
Incorrect Instruction Sequence
Unauthorized Access
Incorrect Integer Arithmetic
Customize with Arbitrary
You can use Arbitrary crate to your advantage and limit or customize the data that are sent to the instructions.
Custom Data Types
Limiting Instructions Inputs
Cross-Program Invocation
Trident supports Cross-Program Invocation, you can fuzz your programs and create NFTs at the same time.
Simple CPI
CPI with Metaplex Metadata Program
"},{"location":"faq/faq/","title":"FAQ","text":""},{"location":"faq/faq/#is-trident-supported-only-with-anchor","title":"Is Trident supported only with Anchor ?","text":"
Currently yes, Trident under the hood obtains data from the IDL generated by Anchor and it has to have access to the AccountsSnapshots derived for each Instruction Context.
"},{"location":"faq/faq/#i-created-the-fuzz-test-what-should-i-do-next","title":"I created the Fuzz Test what should I do next ?","text":"
Start here Writing Fuzz Tests. For additional features check Features. If you are not sure about anything check Get Help
"},{"location":"faq/faq/#my-program-instruction-contains-custom-type-such-as-struct-or-enum-on-its-input-but-it-does-not-derive-arbitrary","title":"My program Instruction contains custom type such as Struct or Enum on its input, but it does not derive Arbitrary.","text":"
In this case you need to specify same type in the Fuzz Test (with the same fields). And implement From Trait to convert to your type. Check Custom Data Types or Examples of Arbitrary.
"},{"location":"faq/faq/#i-would-like-to-report-issue-with-trident-what-should-i-do","title":"I would like to report Issue with Trident, what should I do ?","text":"
Write Issue Issues
"},{"location":"faq/faq/#is-trident-deployed-on-mainnet-devnet-testenet","title":"Is Trident deployed on Mainnet / Devnet / Testenet ?","text":"
No, Trident is Fuzz Testing Framework, not Solana Program.
"},{"location":"faq/faq/#what-type-of-fuzzer-trident-is","title":"What type of Fuzzer Trident is ?","text":"
Currently, we refer to it as \"coverage guided gray box fuzzer\".
Trident allows developers to generate random accounts for fuzzing.
However, the Accounts are not completely random, and neither are the Account addresses.
Instead, Trident generates random AccountIDs which are indexes to Account Storages. Each unique Account contained within the Anchor generated IDL has its own AccountStorage. The FuzzAccounts containing the Accounts Storages is global to all Instructions to use.
Note
Details:
Always generating only random accounts would in most cases lead to a situation where the fuzzer would be stuck because the accounts would be almost every time rejected by your Anchor program. Therefore it is necessary to specify, what accounts should be used and also limit the number of newly created accounts to reduce the space complexity.
Trident allows you to customize Instruction Data to provide structure.
For example your Initialize Instruction expects two arguments start_at and end_at you know that in order for the Instruction to make sense, it is required that the start_at < end_at. Moreover, there should be significant difference between these two. This can be utilized with the Arbitrary crate.
#[derive(Arbitrary, Debug)]\npub struct InitVestingData {\n pub recipient: AccountId,\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(1..=1_000_000)\n )]\n pub amount: u64,\n // we want start_at smaller than end_at\n // and for testing purposes we can run tests with times from the past\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(0..=1_000_000)\n )]\n pub start_at: u64,\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(1_001_001..=1_050_000)\n )]\n pub end_at: u64,\n #[arbitrary(\n with = |u: &mut arbitrary::Unstructured| u.int_in_range(1..=1000)\n )]\n pub interval: u64,\n}\n
"},{"location":"features/fuzz-instructions/#get-program-id","title":"Get Program ID","text":"
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.
Or you can customize the Data using the Arbitrary crate. Check Arbitrary Data.
"},{"location":"features/fuzz-instructions/#custom-data-types","title":"Custom Data Types","text":"
If you use Custom Types as Instruction data arguments, you may encounter a problem that the Custom Type does not implement
Debug trait
Arbitrary trait
"},{"location":"features/fuzz-instructions/#derive-debug-and-arbitrary-traits-inside-the-fuzz-test","title":"Derive Debug and Arbitrary traits inside the Fuzz Test","text":"
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,\n// but this time with all of the required traits.\n#[derive(Arbitrary,Debug, Clone, Copy)]\npub enum CustomEnumInputFuzz {\n InputVariant1,\n InputVariant2,\n InputVariant3,\n}\n
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.
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.
Important
Source Code below
Take the author from the FuzzAccounts Account Storage author on self.accounts.author index. If Account on that index does not exist yet, it will be created and returned. In case it is already created, the corresponding Account will be returned.
hello_world_account Account Storage is of type PdaStore, in this case get_or_create_account function do the same as for the authority, but you need to specify seeds to derive its PDA.
Next, you need to specify signers if there should be any.
Lastly, specify the Account Metas of the corresponding Instruction.
for example <program_name>::accounts::<context_name> {}.to_account_metas(None);
"},{"location":"features/fuzz-instructions/#create-an-arbitrary-account","title":"Create an arbitrary account","text":"
The AccountsStorage<T> type provides an implementation of the get_or_create_account method that helps you create new or read already existing accounts. There are different implementations for different types of storage (Keypair, TokenStore, MintStore, PdaStore) to simplify the creation of new accounts.
However, there are cases when the provided implementation is not sufficient and it is necessary to create an account manually. These cases can be (but are not limited to) for example:
you need to create a new account with a predefined address
you need to create a new account that is not owned by the system program
you need to create and initialize a new PDA account
your program expects an account to be initialized in a previous instruction
In that case, you can use the storage method of the AccountsStorage<T> struct that exposes the underlying HashMap<AccountId, T> and you can add new accounts directly to it.
It is possible to create and store any kind of account. For example:
to add an account that uses the #[account(zero)] anchor constraint (must be rent exempt, owned by your program, with empty data):
let state = fuzz_accounts\n .state\n // gets the storage of all `state` account variants\n .storage()\n // returns the Keypair of the `state` account with\n // the given `AccountId` if it has been added previously\n .entry(self.accounts.state)\n .or_insert_with(|| {\n let space = State::SIZE;\n let rent_exempt_lamports = client.get_rent().unwrap()\n .minimum_balance(space);\n let keypair = Keypair::new();\n let account = AccountSharedData::new_data_with_space::<[u8; 0]>(\n rent_exempt_lamports,\n &[],\n space,\n &my_program::id(),\n ).unwrap();\n // insert the custom account also into the client\n client.set_account_custom(&keypair.pubkey(), &account);\n keypair\n });\n
to add a new system-owned account with a specific PDA (address):
let rent_exempt_for_token_acc = client\n .get_rent()\n .unwrap()\n .minimum_balance(anchor_spl::token::spl_token::state::Account::LEN);\n\nlet my_pda = fuzz_accounts\n .my_pda\n // gets the storage of all `my_pda` account variants\n .storage()\n // returns the PdaStore struct of the `my_pda` account with\n // the given `AccountId` if it has been added previously\n .entry(self.accounts.my_pda)\n .or_insert_with(|| {\n let seeds = &[b\"some-seeds\"];\n let pda = Pubkey::find_program_address(seeds, &my_program::id()).0;\n let account = AccountSharedData::new_data_with_space::<[u8; 0]>(\n rent_exempt_for_token_acc,\n &[],\n 0,\n &SYSTEM_PROGRAM_ID,\n ).unwrap();\n // insert the custom account also into the client\n client.set_account_custom(&pda, &account);\n let vec_of_seeds: Vec<Vec<u8>> = seeds.iter().map(|&seed| seed.to_vec())\n .collect();\n PdaStore {\n pubkey: pda,\n seeds: vec_of_seeds,\n }\n }).pubkey();\n
Number of invocations of each instruction during the fuzzing session.
Number of successful invocations of each instruction during the fuzzing session.
Number of failed invariants checks for each instruction during the fuzzing session.
Note
Keep in mind that the number of fuzz iterations does not directly correspond to the total number of invocations. In one fuzz iteration, the fuzzer might be unable to deserialize fuzz data into instructions, causing the entire iteration to be skipped.
On the other hand this is expected behavior as the underlying data are randomly (with coverage feedback) generated, so the Honggfuzz will not necessarily find appropriate data each iteration.
Tip
Consider checking the Examples section for more tips.
In case of multiple programs within the Anchor Workspace. Make sure that all of the programs you would like to call Cross Program Invocation to are included in the initial state of the Fuzz Test Environment.
Important
Source code below:
fuzzing_program_callee is included in the ProgramTestClientBlocking
fuzzing_program_caller is included in the ProgramTestClientBlocking
Trident allows you to specify Custom Instruction Sequences you would like to execute.
Possible Instruction sequences are split into 3 parts
pre-Instructions
Instructions
post-Instructions
For example if you program always needs to start with some kind of Initialization instruction, you can specify this Initialize Instruction in pre_ixs as shown in the source code below.
Tip
returning Ok(vec![]) will result in None Instructions executed in the corresponding part.
// test_fuzz.rs\n\n// do not forget to include the required structures\nuse fuzz_instructions::InitVesting;\nuse fuzz_instructions::WithdrawUnlocked;\n\nimpl FuzzDataBuilder<FuzzInstruction> for MyFuzzData {\n fn pre_ixs(\n u: &mut arbitrary::Unstructured\n ) -> arbitrary::Result<Vec<FuzzInstruction>> {\n let init_ix =\n FuzzInstruction::InitVesting(InitVesting::arbitrary(u)?);\n\n Ok(vec![init_ix])\n }\n fn ixs(\n u: &mut arbitrary::Unstructured\n ) -> arbitrary::Result<Vec<FuzzInstruction>> {\n let withdraw_ix =\n FuzzInstruction::WithdrawUnlocked(WithdrawUnlocked::arbitrary(u)?);\n\n Ok(vec![withdraw_ix])\n }\n fn post_ixs(\n _u: &mut arbitrary::Unstructured\n ) -> arbitrary::Result<Vec<FuzzInstruction>> {\n Ok(vec![])\n }\n}\n
Tip
Consider checking the Examples section for more tips.
Trident allows you to (optionally) specify Invariant Checks for each Instruction.
The Invariant Check will be called after the Instruction was successfully invoked. Within the Invariant Check you can compare the contents of Accounts before and after the Instruction was called.
Important
Returning error in the Invariant Check is considered as detected undesired behavior (i.e. issue/crash detected).
fn check(\n &self,\n _pre_ix: Self::IxSnapshot,\n post_ix: Self::IxSnapshot,\n _ix_data: Self::IxData,\n) -> Result<(), FuzzingError> {\n if let Some(hello_world_account) = post_ix.hello_world_account {\n if hello_world_account.input == 253 {\n return Err(FuzzingError::Custom(1));\n }\n }\n Ok(())\n}\n
Tip
Consider checking the Examples section for more tips.
"},{"location":"features/lifecycle/","title":"Fuzz Test Lifecycle","text":"
In the sequence diagram below you can see a simplified fuzz test lifecycle.
Some diagram states are labeled with emojis:
\u26a1 Mandatory methods that must be implemented by the user.
\ud83d\udc64 Optional methods that can be implemented by the user.
The maximal number of iterations is reached (if specified).
A crash was detected and the exit_upon_crash parameter was set.
User interrupted the test manually (for example by hitting CTRL+C).
In each iteration, the fuzzer generates a sequence of random instructions to execute.
User can optionally customize how the instructions are generated and can specify the instructions that should be executed at the beginning (pre_ixs), in the middle (ixs) and at the end (post_ixs) of each iteration. This can be useful for example if your program needs an initialization or you want to fuzz some specific program state.
For each instruction:
User defined mandatory method get_accounts() is called to collect necessary instruction accounts.
User defined mandatory method get_data() is called to collect instruction data.
A snapshot of all instruction accounts before the instruction execution is saved.
The instruction is executed.
A snapshot of all instruction accounts after the instruction execution is saved.
User defined optional method check() is called to check accounts data and evaluate invariants.
fuzzer_iterations = 0fuzzer_iterations = 0fuzzer_iterations <\u00a0max_iterationsfuzzer_iterations <...donedonecreate pre-instruction\u00a0accounts snapshotscreate pre-instruction...execute instructionexecute instructioncreate post-instruction\u00a0accounts snapshotscreate post-instruction...check invariants \ud83d\udc64check invariants \ud83d\udc64fuzzer_iterations++fuzzer_iterations++Generate instructionspre_ixs \ud83d\udc64pre_ixs \ud83d\udc64ixs \ud83d\udc64ixs \ud83d\udc64post_ixs \ud83d\udc64post_ixs \ud83d\udc64endendfor ix in instructionsfor ix in instructionsget instruction accounts \u26a1get instruction accounts \u26a1get instruction data \u26a1get instruction data \u26a1next ixnext ixText is not SVG - cannot display"},{"location":"features/limitations/","title":"Current limitations","text":"
This section summarizes some known limitations in the current development stage. Further development will be focused on resolving these limitations.
Remaining accounts in check methods are not supported.
Trident allows you to specify genesis programs (SBF targets) that will be included in the fuzzing environment in order to perform all desired Cross-Program-Invocations.
Including these types of programs will lead to performance decline of Trident.
Trident allwos you to specify genesis accounts to inlcude in the fuzzing environment. In most cases it is helpful to dump some accounts from mainnet and use then during testing.
Allow processing of duplicate transactions. Setting to true might speed up fuzzing but can cause false positive crashes (default: false)
[fuzz]\n# Allow processing of duplicate transactions.\n# Setting to true might speed up fuzzing but can cause\n# false positive crashes (default: false)\nallow_duplicate_txs = false\n
Trident will show statistics after the fuzzing session. This option forces use of honggfuzz parameter keep_output as true in order to be able to catch fuzzer stdout. (default: false)
[fuzz]\n# Trident will show statistics after the fuzzing session.\n# This option forces use of honggfuzz parameter\n# `keep_output` as true in order to be able to catch fuzzer stdout.\n# (default: false)\nfuzzing_with_stats = true\n
Save all test-cases (not only the unique ones) by appending the current time-stamp to the filenames (default: false)
[honggfuzz]\n# Save all test-cases\n# (not only the unique ones) by appending the current\n# time-stamp to the filename (default: false)\nsave_all = false\n
Tip
Consider checking the Examples section for more tips.
If you have Trident already initialized, you can add new fuzz test using trident fuzz add.
"},{"location":"writing-fuzz-test/writing-fuzz-test/#fill-the-fuzz-test-template","title":"Fill the Fuzz test Template","text":""},{"location":"writing-fuzz-test/writing-fuzz-test/#derive-accountssnapshots","title":"Derive AccountsSnapshots","text":"
For every Account Context specified in the Anchor project derive AccountsSnapshots such as:
Define AccountsStorage type for each Account you would like to use.
Important
Keep in mind:
You do not need to specify every AccountStorage, some accounts do not necessarily need to be stored in their corresponding storage.
For example System Program does not need to be stored, rather can be used from the solana_sdk.
If you are about to Initialize Mint or Token Account in your Solana Program.
use Keypair or PdaStore (not MintStore or TokenStore).
If you are going to initialize Associated Token Account in your Solana Program.
use PdaStore.
You can rename FuzzAccounts fields to whatever you want. The default names were generated based on the Program's IDL.
#[doc = r\" Use AccountsStorage<T> where T can be one of:\"]\n#[doc = r\" Keypair, PdaStore, TokenStore, MintStore, ProgramStore\"]\n#[derive(Default)]\npub struct FuzzAccounts {\n author: AccountsStorage<Keypair>,\n hello_world_account: AccountsStorage<PdaStore>,\n // No need to fuzz system_program\n // system_program: AccountsStorage<todo!()>,\n}\n
Tip
For more details about the AccountsStorage check AccountsStorage.
Each Instruction in the Fuzz Test has to have defined the following functions:
get_program_id()
Specifies to which program the Instruction belongs. This function is automatically defined and should not need any updates. The importance is such that if you have multiple programs in your workspace, Trident can generate Instruction Sequences of Instruction corresponding to different programs.
get_data()
Specifies what Instruction inputs are send to the Program Instructions.
get_accounts()
Specifies what Accounts are send to the Program Instructions.
Tip
For more info about how to write these functions, check the Fuzz Instructions.
For the examples how to write these functions, check the Examples.