Skip to content
Open
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
89 changes: 80 additions & 9 deletions lang-v2/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,13 +1134,26 @@ fn impl_accounts(input: &DeriveInput) -> TokenStream2 {
})
.collect();
let idl_accounts_fn = idl::build_accounts_emission(&accounts_fields);
// Only path-typed fields drive the IDL dep walk. `idl_field_ty` is
// already the post-Option-unwrap base type (see `parse::parse_field`),
// and is `None` for non-Path fields — filter those out so the emitted
// trait call has a concrete type to dispatch on.
let idl_field_tys: Vec<&syn::Type> = fields
// Most field types register transitive IDL deps through
// `IdlAccountType::__register_idl_deps`. `Nested<Inner>` is special:
// `#[derive(Accounts)]` emits an inherent `Inner::__idl_register_deps`
// helper, not an `IdlAccountType` impl for `Inner`, so route those
// fields to the inner helper directly.
let idl_dep_walkers: Vec<TokenStream2> = fields
.iter()
.filter_map(|f| f.idl_field_ty.as_ref())
.filter_map(|f| {
if let Some(inner_ty) = parse::extract_nested_inner_type(&f.ty) {
Some(quote! {
<#inner_ty>::__idl_register_deps(accounts, types);
})
} else {
f.idl_field_ty.as_ref().map(|ty| {
quote! {
<#ty as anchor_lang_v2::IdlAccountType>::__register_idl_deps(accounts, types);
}
})
}
})
.collect();

let (ix_deser, ix_args_assoc, ix_args_return) = if ix_args.is_empty() {
Expand Down Expand Up @@ -1795,9 +1808,7 @@ fn impl_accounts(input: &DeriveInput) -> TokenStream2 {
accounts: &mut anchor_lang_v2::__alloc::vec::Vec<&'static str>,
types: &mut anchor_lang_v2::__alloc::vec::Vec<&'static str>,
) {
#(
<#idl_field_tys as anchor_lang_v2::IdlAccountType>::__register_idl_deps(accounts, types);
)*
#(#idl_dep_walkers)*
}
}
}
Expand Down Expand Up @@ -2496,6 +2507,8 @@ fn gen_declared_program(name: &Ident, idl: &serde_json::Value) -> syn::Result<To
pub struct #marker_name;

impl anchor_lang_v2::Id for #marker_name {
const IDL_ADDRESS: &'static str = #address_lit;

fn id() -> anchor_lang_v2::Address {
super::ID
}
Expand Down Expand Up @@ -5936,4 +5949,62 @@ mod tests {
"interface mode must not emit entrypoint runtime: {generated}"
);
}

#[test]
fn nested_accounts_register_idl_deps_through_inner_helper() {
let input: syn::DeriveInput = syn::parse_quote! {
pub struct Outer {
pub nested: anchor_lang_v2::Nested<Inner>,
}
};

let generated = impl_accounts(&input).to_string();

assert!(
generated.contains("< Inner > :: __idl_register_deps"),
"nested accounts should forward IDL dep registration through the inner helper: \
{generated}"
);
assert!(
!generated.contains(
"< anchor_lang_v2 :: Nested < Inner > as anchor_lang_v2 :: IdlAccountType > \
:: __register_idl_deps"
),
"nested accounts should not require an IdlAccountType impl on Inner via Nested: \
{generated}"
);
}

#[test]
fn declare_program_markers_emit_known_idl_addresses() {
let idl = serde_json::json!({
"address": "Externa1111111111111111111111111111111111111",
"metadata": {
"name": "fixture",
"version": "0.1.0",
"spec": "0.1.0"
},
"instructions": [{
"name": "ping",
"discriminator": [1, 2, 3, 4, 5, 6, 7, 8],
"accounts": [],
"args": []
}],
"accounts": [],
"types": []
});
let name: syn::Ident = syn::parse_quote!(fixture);

let generated = gen_declared_program(&name, &idl)
.expect("fixture IDL should generate")
.to_string();

assert!(
generated.contains(
"const IDL_ADDRESS : & 'static str = \"Externa1111111111111111111111111111111111111\""
),
"declare_program markers should expose their known address for IDL emission: \
{generated}"
);
}
}
2 changes: 1 addition & 1 deletion lang-v2/derive/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,7 @@ pub fn parse_field(
idl_address_v1_source: None,
idl_docs: vec![],
idl_pda: None,
idl_field_ty: None,
idl_field_ty: Some(field_ty.clone()),
});
}

Expand Down
25 changes: 25 additions & 0 deletions tests-v2/programs/accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,3 +1042,28 @@ pub struct CheckAssociatedTokenProgramSeed {
#[account(seeds = [b"vault"], bump, seeds::program = AssociatedToken::id())]
pub data: UncheckedAccount,
}

#[account]
pub struct NestedVault {
pub value: u64,
}

#[derive(Accounts)]
pub struct NestedIdlDepsInner {
pub vault: Account<NestedVault>,
}

#[derive(Accounts)]
pub struct NestedIdlDepsOuter {
pub inner: Nested<NestedIdlDepsInner>,
}

#[derive(Accounts)]
pub struct NestedNoDepsInner {
pub authority: Signer,
}

#[derive(Accounts)]
pub struct NestedNoDepsOuter {
pub inner: Nested<NestedNoDepsInner>,
}
35 changes: 35 additions & 0 deletions tests-v2/tests/idl_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
anchor_lang_idl_spec::{IdlInstructionAccount, IdlInstructionAccountItem, IdlSeed},
anchor_lang_v2::{programs::AssociatedToken, Id},
declare_program_surface::surface,
};

fn parse_accounts(json: &str) -> Vec<IdlInstructionAccountItem> {
Expand Down Expand Up @@ -31,3 +32,37 @@ fn marker_id_program_seed_emits_marker_address_bytes() {
other => panic!("expected const program seed, got {other:?}"),
}
}

#[test]
fn nested_accounts_register_transitive_idl_deps() {
let mut accounts = Vec::new();
let mut types = Vec::new();
accounts_test::NestedIdlDepsOuter::__idl_register_deps(&mut accounts, &mut types);

assert!(
accounts.iter().any(|entry| entry.contains("\"name\":\"NestedVault\"")),
"nested account data should register its account entry: {accounts:?}"
);
assert!(
types.iter().any(|entry| entry.contains("\"name\":\"NestedVault\"")),
"nested account data should register its type entry: {types:?}"
);
}

#[test]
fn nested_accounts_without_data_do_not_invent_idl_deps() {
let mut accounts = Vec::new();
let mut types = Vec::new();
accounts_test::NestedNoDepsOuter::__idl_register_deps(&mut accounts, &mut types);

assert!(accounts.is_empty(), "expected no account deps, got {accounts:?}");
assert!(types.is_empty(), "expected no type deps, got {types:?}");
}

#[test]
fn declared_program_markers_expose_known_idl_address() {
assert_eq!(
surface::program::Surface::IDL_ADDRESS,
"D9t6cEFPTDWmTZfcikokLbnuuyeJT6oXnpEbyXB45LU2"
);
}
Loading