diff --git a/CHANGELOG.md b/CHANGELOG.md index 7835335486..333e7e4836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Use OS-agnostic paths ([#3307](https://github.com/coral-xyz/anchor/pull/3307)). - avm: Use `rustc 1.79.0` when installing versions older than v0.31 ([#3315](https://github.com/coral-xyz/anchor/pull/3315)). - cli: Fix priority fee calculation causing panic on localnet ([#3318](https://github.com/coral-xyz/anchor/pull/3318)). +- lang: Fix `declare_program!` using non-instruction composite accounts multiple times ([#3350](https://github.com/coral-xyz/anchor/pull/3350)). ### Breaking diff --git a/lang/attribute/program/src/declare_program/mods/internal.rs b/lang/attribute/program/src/declare_program/mods/internal.rs index 4a394a5979..301a6215ef 100644 --- a/lang/attribute/program/src/declare_program/mods/internal.rs +++ b/lang/attribute/program/src/declare_program/mods/internal.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use anchor_lang_idl::types::{ Idl, IdlInstruction, IdlInstructionAccountItem, IdlInstructionAccounts, }; @@ -138,17 +140,25 @@ fn gen_internal_accounts_common( .iter() .flat_map(|ix| ix.accounts.to_owned()) .collect::>(); + let mut account_names: HashSet = std::collections::HashSet::new(); let combined_ixs = get_non_instruction_composite_accounts(&ix_accs, idl) .into_iter() - .map(|accs| IdlInstruction { - // The name is not guaranteed to be the same as the one used in the actual source code - // of the program because the IDL only stores the field names. - name: accs.name.to_owned(), - accounts: accs.accounts.to_owned(), - args: Default::default(), - discriminator: Default::default(), - docs: Default::default(), - returns: Default::default(), + .filter_map(|accs| { + let name = accs.name.to_owned(); + if account_names.insert(name) { + Some(IdlInstruction { + // The name is not guaranteed to be the same as the one used in the actual source code + // of the program because the IDL only stores the field names. + name: accs.name.to_owned(), + accounts: accs.accounts.to_owned(), + args: Default::default(), + discriminator: Default::default(), + docs: Default::default(), + returns: Default::default(), + }) + } else { + None + } }) .chain(idl.instructions.iter().cloned()) .collect::>(); diff --git a/tests/declare-program/idls/external.json b/tests/declare-program/idls/external.json index 3bb4e4b641..10c301d13b 100644 --- a/tests/declare-program/idls/external.json +++ b/tests/declare-program/idls/external.json @@ -44,6 +44,52 @@ ], "args": [] }, + { + "name": "second_use_of_non_instruction_composite", + "discriminator": [ + 103, + 165, + 187, + 198, + 36, + 148, + 158, + 119 + ], + "accounts": [ + { + "name": "non_instruction_update", + "accounts": [ + { + "name": "authority", + "signer": true + }, + { + "name": "my_account", + "writable": true, + "pda": { + "seeds": [ + { + "kind": "account", + "path": "authority" + } + ] + } + }, + { + "name": "program", + "address": "Externa111111111111111111111111111111111111" + } + ] + } + ], + "args": [ + { + "name": "value", + "type": "u32" + } + ] + }, { "name": "test_compilation_defined_type_param", "discriminator": [ diff --git a/tests/declare-program/programs/external/src/lib.rs b/tests/declare-program/programs/external/src/lib.rs index 86a08ae0b8..ac792ff2bd 100644 --- a/tests/declare-program/programs/external/src/lib.rs +++ b/tests/declare-program/programs/external/src/lib.rs @@ -33,6 +33,14 @@ pub mod external { Ok(()) } + // Test the issue described in https://github.com/coral-xyz/anchor/issues/3349 + pub fn second_use_of_non_instruction_composite( + _ctx: Context, + _value: u32, + ) -> Result<()> { + Ok(()) + } + // Compilation test for whether a defined type (an account in this case) can be used in `cpi` client. pub fn test_compilation_defined_type_param( _ctx: Context, @@ -92,6 +100,11 @@ pub struct UpdateNonInstructionComposite<'info> { pub non_instruction_update: NonInstructionUpdate<'info>, } +#[derive(Accounts)] +pub struct SecondUseOfNonInstructionComposite<'info> { + pub non_instruction_update: NonInstructionUpdate<'info>, +} + #[account] pub struct MyAccount { pub field: u32,