Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 CPI Support #182

Merged
merged 5 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions .github/workflows/run_examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [master]

name: Test Escrow and Turnstile
name: Test Escrow

env:
SOLANA_CLI_VERSION: 1.18.12
Expand All @@ -27,19 +27,3 @@ jobs:
- name: Test Escrow
working-directory: examples/integration-tests/escrow
run: cargo run --manifest-path ../../../Cargo.toml test
test_turnstile:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/setup-rust/
- uses: ./.github/actions/setup-solana/
- uses: ./.github/actions/setup-anchor/
id: rust-setup
- uses: Swatinem/rust-cache@v2
name: Cache Rust and it's packages
- name: Build Turnstile
working-directory: examples/integration-tests/turnstile
run: anchor build
- name: Test Turnstile
working-directory: examples/integration-tests/turnstile
run: cargo run --manifest-path ../../../Cargo.toml test
8 changes: 4 additions & 4 deletions .github/workflows/run_fuzz_example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ on:
pull_request:

env:
SOLANA_CLI_VERSION: 1.18.17
SOLANA_CLI_VERSION: 1.18.18
HONGGFUZZ_VERSION: 0.5.56

jobs:
unchecked-arithmetic-0:
simple-cpi-6:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3

- name: Set Anchor Version
run: echo "ANCHOR_VERSION=0.29.0" >> $GITHUB_ENV
run: echo "ANCHOR_VERSION=0.30.1" >> $GITHUB_ENV

- uses: Swatinem/rust-cache@v2
name: Cache Rust and it's packages
Expand All @@ -30,7 +30,7 @@ jobs:
id: rust-setup

- name: Test Fuzz
working-directory: examples/fuzz-tests/unchecked-arithmetic-0
working-directory: examples/fuzz-tests/simple-cpi-6
run: trident fuzz run fuzz_0
arbitrary-limit-inputs-5:
runs-on: ubuntu-20.04
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
incremented upon a breaking change and the patch version will be incremented for features.

## [dev] - Unreleased
- feat/ add Support for CPI ([182](https://github.com/Ackee-Blockchain/trident/pull/182))
- feat/ add option to initialize Trident with Macro/File (for Snapshots) option based on preference ([179](https://github.com/Ackee-Blockchain/trident/pull/179))
- del/remove localnet subcommand ([178](https://github.com/Ackee-Blockchain/trident/pull/178))
- feat/create AccountsSnapshots derive macro for Snapshots creation ([#177](https://github.com/Ackee-Blockchain/trident/pull/177))
Expand Down
11 changes: 11 additions & 0 deletions crates/client/src/commander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ impl Commander {
// arguments so we need to parse the variable content.
let hfuzz_run_args = std::env::var("HFUZZ_RUN_ARGS").unwrap_or_default();

let genesis_folder = PathBuf::from(self.root.to_string()).join("trident-genesis");

let rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();

let rustflags = config.get_rustflags_args(rustflags);
Expand Down Expand Up @@ -190,6 +192,7 @@ impl Commander {
// enforce keep output to be true
fuzz_args.push_str("--keep_output");
let mut child = Command::new("cargo")
.env("GENESIS_FOLDER", genesis_folder)
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
Expand All @@ -203,6 +206,7 @@ impl Commander {
}
false => {
let mut child = Command::new("cargo")
.env("GENESIS_FOLDER", genesis_folder)
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
Expand Down Expand Up @@ -233,6 +237,8 @@ impl Commander {

let hfuzz_run_args = std::env::var("HFUZZ_RUN_ARGS").unwrap_or_default();

let genesis_folder = PathBuf::from(self.root.to_string()).join("trident-genesis");

let cargo_target_dir = std::env::var("CARGO_TARGET_DIR")
.unwrap_or_else(|_| config.get_env_arg("CARGO_TARGET_DIR"));
let hfuzz_workspace = std::env::var("HFUZZ_WORKSPACE")
Expand All @@ -249,6 +255,7 @@ impl Commander {
// enforce keep output to be true
fuzz_args.push_str("--keep_output");
let mut child = Command::new("cargo")
.env("GENESIS_FOLDER", genesis_folder)
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
Expand All @@ -262,6 +269,7 @@ impl Commander {
}
false => {
let mut child = Command::new("cargo")
.env("GENESIS_FOLDER", genesis_folder)
.env("HFUZZ_RUN_ARGS", fuzz_args)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("HFUZZ_WORKSPACE", hfuzz_workspace)
Expand Down Expand Up @@ -387,6 +395,8 @@ impl Commander {

let crash_file = std::path::Path::new(&self.root as &str).join(crash_file_path);

let genesis_folder = PathBuf::from(self.root.to_string()).join("trident-genesis");

if !crash_file.try_exists()? {
println!("{ERROR} The crash file [{:?}] not found", crash_file);
throw!(Error::CrashFileNotFound);
Expand All @@ -401,6 +411,7 @@ impl Commander {

// using exec rather than spawn and replacing current process to avoid unflushed terminal output after ctrl+c signal
std::process::Command::new("cargo")
.env("GENESIS_FOLDER", genesis_folder)
.env("CARGO_TARGET_DIR", cargo_target_dir)
.env("RUSTFLAGS", rustflags)
.arg("hfuzz")
Expand Down
1 change: 1 addition & 0 deletions crates/client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ pub mod fuzzing {
pub use trident_fuzz::*;

pub use solana_program_test::processor;
pub use trident_fuzz::program_test_client_blocking::FuzzingProgram;
pub use trident_fuzz::program_test_client_blocking::ProgramEntry;

pub use super::temp_clone::*;
Expand Down
14 changes: 10 additions & 4 deletions crates/client/src/templates/trident-tests/test_fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@ mod fuzz_instructions;
// uncomment the line below
// mod accounts_snapshots;

const PROGRAM_NAME: &str = "###PROGRAM_NAME###";

struct MyFuzzData;

impl FuzzDataBuilder<FuzzInstruction> for MyFuzzData {}

fn main() {
loop {
fuzz_trident!(fuzz_ix: FuzzInstruction, |fuzz_data: MyFuzzData| {

// Specify programs you want to include in genesis
// Programs without an `entry_fn`` will be searched for within `trident-genesis` folder.
// `entry_fn`` example: processor!(convert_entry!(program_entry))
let fuzzing_program1 = FuzzingProgram::new(todo!(),todo!(),processor!(convert_entry!(todo!())));

let mut client =
ProgramTestClientBlocking::new(PROGRAM_NAME, PROGRAM_ID, processor!(convert_entry!(entry)))
ProgramTestClientBlocking::new(&[todo!()])
.unwrap();
let _ = fuzz_data.run_with_runtime(PROGRAM_ID, &mut client);

// fill Program ID of program you are going to call
let _ = fuzz_data.run_with_runtime(todo!(), &mut client);
});
}
}
59 changes: 39 additions & 20 deletions crates/client/src/test_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,33 +496,52 @@ impl TestGenerator {

#[throws]
pub async fn initialize_fuzz(&self, new_fuzz_test_dir: &Path) {
let program_name = if !&self.programs_data.is_empty() {
&self
.programs_data
.first()
.unwrap()
.program_idl
.name
.snake_case
} else {
if self.programs_data.is_empty() {
throw!(Error::NoProgramsFound)
};
}

let fuzz_test_path = new_fuzz_test_dir.join(FUZZ_TEST);

let fuzz_test_content = load_template!("/src/templates/trident-tests/test_fuzz.rs");

let use_entry = format!("use {}::entry;\n", program_name);
let use_instructions = format!("use {}::ID as PROGRAM_ID;\n", program_name);
let use_fuzz_instructions = format!(
"use fuzz_instructions::{}_fuzz_instructions::FuzzInstruction;\n",
program_name
let mut entry_points: String = String::new();
let mut program_ids: String = String::new();
let mut program_names: String = String::new();
let mut fuzz_instructions: String = String::new();

for x in self.programs_data.iter() {
let program_name = &x.program_idl.name.snake_case;

let use_entry = format!("use {}::entry as entry_{};\n", program_name, program_name);
entry_points.push_str(&use_entry);

let program_name_var = format!(
"const PROGRAM_NAME_{}: &str = \"{}\";\n",
program_name.to_uppercase(),
program_name,
);
program_names.push_str(&program_name_var);

let program_id = format!(
"use {}::ID as PROGRAM_ID_{};\n",
program_name,
program_name.to_uppercase()
);
program_ids.push_str(&program_id);

let use_fuzz_instructions = format!(
"use fuzz_instructions::{}_fuzz_instructions::FuzzInstruction as FuzzInstruction_{};\n",
program_name,program_name
);
fuzz_instructions.push_str(&use_fuzz_instructions);
}

let template = format!(
"{}{}{}{}{}",
entry_points, program_ids, program_names, fuzz_instructions, fuzz_test_content
);
let template =
format!("{use_entry}{use_instructions}{use_fuzz_instructions}{fuzz_test_content}");
let fuzz_test_content = template.replace("###PROGRAM_NAME###", program_name);

self.create_file(&fuzz_test_path, &fuzz_test_content)
.await?;
self.create_file(&fuzz_test_path, &template).await?;
}

#[throws]
Expand Down
65 changes: 60 additions & 5 deletions crates/fuzz/src/program_test_client_blocking.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;

use solana_program_runtime::invoke_context::BuiltinFunctionWithContext;
use solana_program_test::ProgramTest;
use solana_program_test::ProgramTestContext;
Expand Down Expand Up @@ -27,13 +31,49 @@ pub struct ProgramTestClientBlocking {
rt: tokio::runtime::Runtime,
}

impl ProgramTestClientBlocking {
pub struct FuzzingProgram {
pub program_name: String,
pub program_id: Pubkey,
pub entry: Option<BuiltinFunctionWithContext>,
}
impl FuzzingProgram {
pub fn new(
program_name: &str,
program_id: Pubkey,
entry: Option<BuiltinFunctionWithContext>,
) -> Result<Self, FuzzClientError> {
let program_test = ProgramTest::new(program_name, program_id, entry);
program_id: &Pubkey,
entry_fn: Option<BuiltinFunctionWithContext>,
) -> FuzzingProgram {
Self {
program_name: program_name.to_string(),
program_id: *program_id,
entry: entry_fn,
}
}
}

impl ProgramTestClientBlocking {
pub fn new(program_: &[FuzzingProgram]) -> Result<Self, FuzzClientError> {
let mut program_test = ProgramTest::default();
for x in program_ {
match x.entry {
Some(entry) => {
program_test.add_builtin_program(&x.program_name, x.program_id, entry);
}
None => {
let data = read_program(&x.program_name);

program_test.add_account(
x.program_id,
Account {
lamports: Rent::default().minimum_balance(data.len()).max(1),
data,
owner: solana_sdk::bpf_loader::id(),
executable: true,
rent_epoch: 0,
},
);
}
}
}
let rt: tokio::runtime::Runtime = Builder::new_current_thread().enable_all().build()?;

let ctx = rt.block_on(program_test.start_with_context());
Expand Down Expand Up @@ -201,3 +241,18 @@ impl FuzzClient for ProgramTestClientBlocking {
Ok(self.rt.block_on(self.ctx.banks_client.get_rent())?)
}
}

fn read_program(program_name: &str) -> Vec<u8> {
let genesis_folder = std::env::var("GENESIS_FOLDER")
.unwrap_or_else(|err| panic!("Failed to read env variable GENESIS_FOLDER: {}", err));

let program_path = PathBuf::from(genesis_folder).join(format!("{program_name}.so"));

let mut file = File::open(&program_path)
.unwrap_or_else(|err| panic!("Failed to open \"{}\": {}", program_path.display(), err));

let mut file_data = Vec::new();
file.read_to_end(&mut file_data)
.unwrap_or_else(|err| panic!("Failed to read \"{}\": {}", program_path.display(), err));
file_data
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anchor_lang::prelude::*;
use arbitrary_custom_types_4::ID as PROGRAM_ID;
use trident_client::fuzzing::{anchor_lang, FuzzingError};
pub struct InitializeSnapshot<'info> {
pub counter: Option<Account<'info, arbitrary_custom_types_4::Counter>>,
Expand All @@ -23,7 +22,7 @@ impl<'info> InitializeSnapshot<'info> {
.ok_or(FuzzingError::NotEnoughAccounts("counter".to_string()))?
.as_ref()
.map(|acc| {
if acc.key() != PROGRAM_ID {
if acc.key() != *_program_id {
anchor_lang::accounts::account::Account::try_from(acc)
.map_err(|_| FuzzingError::CannotDeserializeAccount("counter".to_string()))
} else {
Expand Down
Loading
Loading