Skip to content
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
5 changes: 3 additions & 2 deletions sdk-libs/instruction-decoder-derive/src/attribute_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,13 @@ fn generate_match_arms(instructions: &[InstructionInfo]) -> Vec<TokenStream2> {
quote! { Vec::new() }
} else {
let params_struct_name = format_ident!("{}DecoderParams", pascal_name);
// Generate field accessors for each parameter - use empty name to print value directly
// Generate field accessors for each parameter with their field names
let field_pushes: Vec<TokenStream2> = info.params.iter().map(|param| {
let field_name = &param.name;
let field_name_str = field_name.to_string();
quote! {
fields.push(light_instruction_decoder::DecodedField::new(
"",
#field_name_str,
format!("{:#?}", params.#field_name),
));
}
Expand Down
86 changes: 77 additions & 9 deletions sdk-libs/instruction-decoder-derive/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ impl<'a> InstructionDecoderBuilder<'a> {
variant_args: &VariantDecoderArgs,
) -> syn::Result<TokenStream2> {
let instruction_name = variant.ident.to_string();
// Pass crate_ctx to resolve struct field names at compile time
let account_names_code = variant_args.account_names_code(self.crate_ctx.as_ref());
let fields_code = self.generate_fields_code(variant, variant_args)?;

// Generate the body code based on whether we have a dynamic resolver
let body_code = self.generate_match_arm_body(variant, variant_args)?;

match self.args.discriminator_size {
1 => {
Expand Down Expand Up @@ -173,8 +173,7 @@ impl<'a> InstructionDecoderBuilder<'a> {
};
Ok(quote! {
#disc => {
let account_names: Vec<String> = #account_names_code;
let fields = { #fields_code };
#body_code
Some(light_instruction_decoder::DecodedInstruction::with_fields_and_accounts(
#instruction_name,
fields,
Expand Down Expand Up @@ -207,8 +206,7 @@ impl<'a> InstructionDecoderBuilder<'a> {
};
Ok(quote! {
#disc => {
let account_names: Vec<String> = #account_names_code;
let fields = { #fields_code };
#body_code
Some(light_instruction_decoder::DecodedInstruction::with_fields_and_accounts(
#instruction_name,
fields,
Expand Down Expand Up @@ -237,8 +235,7 @@ impl<'a> InstructionDecoderBuilder<'a> {
let disc_array = discriminator.iter();
Ok(quote! {
[#(#disc_array),*] => {
let account_names: Vec<String> = #account_names_code;
let fields = { #fields_code };
#body_code
Some(light_instruction_decoder::DecodedInstruction::with_fields_and_accounts(
#instruction_name,
fields,
Expand All @@ -254,6 +251,77 @@ impl<'a> InstructionDecoderBuilder<'a> {
}
}

/// Generate the body code for a match arm.
///
/// When `account_names_resolver_from_params` is specified, this generates code that:
/// 1. Parses params first
/// 2. Calls the resolver function with params and accounts to get dynamic account names
/// 3. Calls the formatter if specified
///
/// Otherwise, it uses static account names from `accounts` or `account_names`.
fn generate_match_arm_body(
&self,
variant: &syn::Variant,
variant_args: &VariantDecoderArgs,
) -> syn::Result<TokenStream2> {
// Check if we have a dynamic account names resolver
if let (Some(resolver_path), Some(params_ty)) = (
&variant_args.account_names_resolver_from_params,
variant_args.params_type(),
) {
// Dynamic resolver mode: parse params first, then call resolver
let fields_code = if let Some(formatter_path) = &variant_args.pretty_formatter {
// Use custom formatter
quote! {
let mut fields = Vec::new();
let formatted = #formatter_path(&params, accounts);
fields.push(light_instruction_decoder::DecodedField::new(
"",
formatted,
));
fields
}
} else {
// Use Debug formatting
quote! {
let mut fields = Vec::new();
fields.push(light_instruction_decoder::DecodedField::new(
"",
format!("{:#?}", params),
));
fields
}
};

Ok(quote! {
let (account_names, fields) = if let Ok(params) = <#params_ty as borsh::BorshDeserialize>::try_from_slice(remaining) {
let account_names = #resolver_path(&params, accounts);
let fields = { #fields_code };
(account_names, fields)
} else {
let account_names: Vec<String> = Vec::new();
let mut fields = Vec::new();
if !remaining.is_empty() {
fields.push(light_instruction_decoder::DecodedField::new(
"data_len",
remaining.len().to_string(),
));
}
(account_names, fields)
};
})
} else {
// Static account names mode
let account_names_code = variant_args.account_names_code(self.crate_ctx.as_ref());
let fields_code = self.generate_fields_code(variant, variant_args)?;

Ok(quote! {
let account_names: Vec<String> = #account_names_code;
let fields = { #fields_code };
})
}
}

/// Generate field parsing code for a variant.
fn generate_fields_code(
&self,
Expand Down
6 changes: 6 additions & 0 deletions sdk-libs/instruction-decoder-derive/src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ pub struct VariantDecoderArgs {
/// The function must have signature `fn(&ParamsType, &[AccountMeta]) -> String`.
#[darling(default)]
pub pretty_formatter: Option<syn::Path>,

/// Optional function to resolve account names dynamically from parsed params.
/// The function must have signature `fn(&ParamsType, &[AccountMeta]) -> Vec<String>`.
/// When specified, this takes precedence over `accounts` and `account_names`.
#[darling(default)]
pub account_names_resolver_from_params: Option<syn::Path>,
}

impl VariantDecoderArgs {
Expand Down
Loading
Loading