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

fix/Support of non-corresponding instruction and context names #130

Merged
merged 3 commits into from
Feb 28, 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
3 changes: 0 additions & 3 deletions Fuzzing.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,6 @@ impl FuzzDataBuilder<FuzzInstruction> for MyFuzzData {
## Current known limitations
This section summarizes some known limitations in the current development stage. Further development will be focused on resolving these limitations.

- Only AccountInfo, Signer, Account<T> and Program<T> types are supported.
- The name of the instruction and context must correspond where the instruction name should be in snake_case and the context struct name in CamelCase).
- ex.: `initialize_escrow` for instruction and `InitializeEscrow` for corresponding Context struct.
- Only fuzzing of one program without CPIs to other custom programs is supported.
- Remaining accounts in check methods are not supported.

Expand Down
10 changes: 3 additions & 7 deletions crates/client/src/fuzzer/fuzzer_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ pub fn generate_source_code(idl: &Idl) -> String {
|mut instructions_ixops_impl, (idl_instruction, idl_account_group)| {
let instruction_name: syn::Ident =
format_ident!("{}", &idl_instruction.name.upper_camel_case);
let ctx_name: syn::Ident =
format_ident!("{}", &idl_account_group.name.upper_camel_case);

let ix_snapshot: syn::Ident =
format_ident!("{}Snapshot", &idl_instruction.name.upper_camel_case);
Expand Down Expand Up @@ -165,7 +167,7 @@ pub fn generate_source_code(idl: &Idl) -> String {
fuzz_accounts: &mut FuzzAccounts,
) -> Result<(Vec<Keypair>, Vec<AccountMeta>), FuzzingError> {
let signers = vec![todo!()];
let acc_meta = #module_name::accounts::#instruction_name {
let acc_meta = #module_name::accounts::#ctx_name {
#(#accounts),*
}
.to_account_metas(None);
Expand Down Expand Up @@ -224,12 +226,6 @@ pub fn generate_source_code(idl: &Idl) -> String {
pub struct FuzzAccounts {
#(#sorted_fuzz_accounts: AccountsStorage<todo!()>),*
}

impl FuzzAccounts {
pub fn new() -> Self {
Default::default()
}
}
}
};
fuzzer_module.into_token_stream().to_string()
Expand Down
98 changes: 60 additions & 38 deletions crates/client/src/fuzzer/snapshot_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// The parsing of individual Anchor accounts is done using Anchor syn parser:
// https://github.com/coral-xyz/anchor/blob/master/lang/syn/src/parser/accounts/mod.rs

use std::collections::HashMap;
use std::{error::Error, fs::File, io::Read};

use anchor_lang::anchor_syn::{AccountField, Ty};
use cargo_metadata::camino::Utf8PathBuf;
use heck::ToUpperCamelCase;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::parse::{Error as ParseError, Result as ParseResult};
Expand Down Expand Up @@ -42,14 +44,17 @@ pub fn generate_snapshots_code(code_path: &[(String, Utf8PathBuf)]) -> Result<St

let ix_ctx_pairs = get_ix_ctx_pairs(&items)?;

let (structs, impls) = get_snapshot_structs_and_impls(code, &ix_ctx_pairs)?;
let (structs, impls, type_aliases) = get_snapshot_structs_and_impls(code, &ix_ctx_pairs)?;

let use_statements = quote! {
use trdelnik_client::anchor_lang::{prelude::*, self};
use trdelnik_client::fuzzing::FuzzingError;
}
.into_token_stream();
Ok(format!("{}{}{}", use_statements, structs, impls))
Ok(format!(
"{}{}{}{}",
use_statements, structs, impls, type_aliases
))
});

code.into_iter().collect()
Expand All @@ -60,44 +65,62 @@ pub fn generate_snapshots_code(code_path: &[(String, Utf8PathBuf)]) -> Result<St
fn get_snapshot_structs_and_impls(
code: &str,
ix_ctx_pairs: &[(Ident, GenericArgument)],
) -> Result<(String, String), String> {
) -> Result<(String, String, String), String> {
let mut structs = String::new();
let mut impls = String::new();
let mut type_aliases = String::new();
let parse_result = syn::parse_file(code).map_err(|e| e.to_string())?;
for pair in ix_ctx_pairs {
let mut ty = None;
if let GenericArgument::Type(syn::Type::Path(tp)) = &pair.1 {
ty = tp.path.get_ident().cloned();
// TODO add support for types with fully qualified path such as ix::Initialize
let mut unique_ctxs: HashMap<GenericArgument, Ident> = HashMap::new();
for (ix, ctx) in ix_ctx_pairs {
let mut ctx_ident = None;
let ix_name = ix.to_string().to_upper_camel_case();
if let GenericArgument::Type(syn::Type::Path(tp)) = ctx {
ctx_ident = tp.path.get_ident().cloned();
}
let ty = ty.ok_or(format!("malformed parameters of {} instruction", pair.0))?;

// recursively find the context struct and create a new version with wrapped fields into Option
if let Some(ctx) = find_ctx_struct(&parse_result.items, &ty) {
let fields_parsed = if let Fields::Named(f) = ctx.fields.clone() {
let field_deser: ParseResult<Vec<AccountField>> =
f.named.iter().map(parse_account_field).collect();
field_deser
} else {
Err(ParseError::new(
ctx.fields.span(),
"Context struct parse errror.",
))
let ctx_ident =
ctx_ident.ok_or(format!("malformed parameters of {} instruction", ix_name))?;

// If ctx is in the HashMap, we do not need to generate deserialization code again, we can only create a type alias
match unique_ctxs.get(ctx) {
Some(base_ix_snapshot_name) => {
let snapshot_alias_name = format_ident!("{}Snapshot", ix_name);
let type_alias =
quote! {pub type #snapshot_alias_name<'info> = #base_ix_snapshot_name<'info>;};
type_aliases = format!("{}{}", type_aliases, type_alias.into_token_stream());
}
.map_err(|e| e.to_string())?;

let wrapped_struct = create_snapshot_struct(ctx, &fields_parsed).unwrap();
let deser_code =
deserialize_ctx_struct_anchor(ctx, &fields_parsed).map_err(|e| e.to_string())?;
// let deser_code = deserialize_ctx_struct(ctx).unwrap();
structs = format!("{}{}", structs, wrapped_struct.into_token_stream());
impls = format!("{}{}", impls, deser_code.into_token_stream());
} else {
return Err(format!("The Context struct {} was not found", ty));
}
None => {
// recursively find the context struct and create a new version with wrapped fields into Option
if let Some(ctx_struct_item) = find_ctx_struct(&parse_result.items, &ctx_ident) {
let fields_parsed = if let Fields::Named(f) = ctx_struct_item.fields.clone() {
let field_deser: ParseResult<Vec<AccountField>> =
f.named.iter().map(parse_account_field).collect();
field_deser
} else {
Err(ParseError::new(
ctx_struct_item.fields.span(),
"Context struct parse errror.",
))
}
.map_err(|e| e.to_string())?;

let ix_snapshot_name = format_ident!("{}Snapshot", ix_name);
let wrapped_struct =
create_snapshot_struct(&ix_snapshot_name, ctx_struct_item, &fields_parsed)
.unwrap();
let deser_code =
deserialize_ctx_struct_anchor(&ix_snapshot_name, &fields_parsed)
.map_err(|e| e.to_string())?;
structs = format!("{}{}", structs, wrapped_struct.into_token_stream());
impls = format!("{}{}", impls, deser_code.into_token_stream());
unique_ctxs.insert(ctx.clone(), ix_snapshot_name);
} else {
return Err(format!("The Context struct {} was not found", ctx_ident));
}
}
};
}

Ok((structs, impls))
Ok((structs, impls, type_aliases))
}

/// Iterates through items and finds functions with the Context<_> parameter. Returns pairs with the function name and the Context's inner type.
Expand Down Expand Up @@ -184,10 +207,10 @@ fn is_optional(parsed_field: &AccountField) -> bool {

/// Creates new Snapshot struct from the context struct. Removes Box<> types.
fn create_snapshot_struct(
snapshot_name: &Ident,
orig_struct: &ItemStruct,
parsed_fields: &[AccountField],
) -> Result<TokenStream, Box<dyn Error>> {
let struct_name = format_ident!("{}Snapshot", orig_struct.ident);
let wrapped_fields = match orig_struct.fields.clone() {
Fields::Named(named) => {
let field_wrappers =
Expand Down Expand Up @@ -247,7 +270,7 @@ fn create_snapshot_struct(

// Generate the new struct with Option-wrapped fields
let generated_struct: syn::ItemStruct = parse_quote! {
pub struct #struct_name<'info> #wrapped_fields
pub struct #snapshot_name<'info> #wrapped_fields
};

Ok(generated_struct.to_token_stream())
Expand All @@ -272,10 +295,9 @@ fn extract_inner_type(field_type: &Type) -> Option<&Type> {

/// Generates code to deserialize the snapshot structs.
fn deserialize_ctx_struct_anchor(
snapshot_struct: &ItemStruct,
snapshot_name: &Ident,
parsed_fields: &[AccountField],
) -> Result<TokenStream, Box<dyn Error>> {
let impl_name = format_ident!("{}Snapshot", snapshot_struct.ident);
let names_deser_pairs: Result<Vec<(TokenStream, TokenStream)>, _> = parsed_fields
.iter()
.map(|parsed_f| match parsed_f {
Expand Down Expand Up @@ -308,7 +330,7 @@ fn deserialize_ctx_struct_anchor(
let (names, fields_deser): (Vec<_>, Vec<_>) = names_deser_pairs?.iter().cloned().unzip();

let generated_deser_impl: syn::Item = parse_quote! {
impl<'info> #impl_name<'info> {
impl<'info> #snapshot_name<'info> {
pub fn deserialize_option(
accounts: &'info mut [Option<AccountInfo<'info>>],
) -> core::result::Result<Self, FuzzingError> {
Expand Down
Loading
Loading