Skip to content

Commit

Permalink
lang: Fix using non-instruction composite accounts with `declare_prog…
Browse files Browse the repository at this point in the history
…ram!` (#3290)
  • Loading branch information
acheroncrypto authored Oct 3, 2024
1 parent 17b6c6d commit cfe82aa
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ The minor version will be incremented upon a breaking change and the patch versi
- lang: Allow the `cfg` attribute above the instructions ([#2339](https://github.com/coral-xyz/anchor/pull/2339)).
- idl: Log output with `ANCHOR_LOG` on failure and improve build error message ([#3284](https://github.com/coral-xyz/anchor/pull/3284)).
- lang: Fix constant bytes declarations when using `declare_program!` ([#3287](https://github.com/coral-xyz/anchor/pull/3287)).
- lang: Fix using non-instruction composite accounts with `declare_program!` ([#3290](https://github.com/coral-xyz/anchor/pull/3290)).

### Breaking

Expand Down
54 changes: 50 additions & 4 deletions lang/attribute/program/src/declare_program/mods/internal.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use anchor_lang_idl::types::{Idl, IdlInstructionAccountItem};
use anchor_lang_idl::types::{
Idl, IdlInstruction, IdlInstructionAccountItem, IdlInstructionAccounts,
};
use anchor_syn::{
codegen::accounts::{__client_accounts, __cpi_client_accounts},
parser::accounts,
Expand Down Expand Up @@ -105,8 +107,53 @@ fn gen_internal_accounts_common(
idl: &Idl,
gen_accounts: impl Fn(&AccountsStruct, proc_macro2::TokenStream) -> proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let accounts = idl
// It's possible to declare an accounts struct and not use it as an instruction, see
// https://github.com/coral-xyz/anchor/issues/3274
fn get_non_instruction_composite_accounts<'a>(
accs: &'a [IdlInstructionAccountItem],
idl: &'a Idl,
) -> Vec<&'a IdlInstructionAccounts> {
accs.iter()
.flat_map(|acc| match acc {
IdlInstructionAccountItem::Composite(accs)
if !idl
.instructions
.iter()
.any(|ix| ix.accounts == accs.accounts) =>
{
let mut non_ix_composite_accs =
get_non_instruction_composite_accounts(&accs.accounts, idl);
if !non_ix_composite_accs.contains(&accs) {
non_ix_composite_accs.push(accs);
}
non_ix_composite_accs
}
_ => Default::default(),
})
.collect()
}

let ix_accs = idl
.instructions
.iter()
.flat_map(|ix| ix.accounts.to_owned())
.collect::<Vec<_>>();
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(),
})
.chain(idl.instructions.iter().cloned())
.collect::<Vec<_>>();

let accounts = combined_ixs
.iter()
.map(|ix| {
let ident = format_ident!("{}", ix.name.to_camel_case());
Expand Down Expand Up @@ -143,8 +190,7 @@ fn gen_internal_accounts_common(
}
IdlInstructionAccountItem::Composite(accs) => {
let name = format_ident!("{}", accs.name);
let ty_name = idl
.instructions
let ty_name = combined_ixs
.iter()
.find(|ix| ix.accounts == accs.accounts)
.map(|ix| format_ident!("{}", ix.name.to_camel_case()))
Expand Down
46 changes: 46 additions & 0 deletions tests/declare-program/idls/external.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,52 @@
"type": "u32"
}
]
},
{
"name": "update_non_instruction_composite",
"discriminator": [
49,
218,
69,
196,
204,
66,
36,
29
],
"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"
}
]
}
],
"accounts": [
Expand Down
22 changes: 18 additions & 4 deletions tests/declare-program/programs/declare-program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub mod declare_program {
pub fn cpi_composite(ctx: Context<Cpi>, value: u32) -> Result<()> {
let cpi_my_account = &mut ctx.accounts.cpi_my_account;

// Composite accounts that's also an instruction
let cpi_ctx = CpiContext::new(
ctx.accounts.external_program.to_account_info(),
external::cpi::accounts::UpdateComposite {
Expand All @@ -44,8 +45,22 @@ pub mod declare_program {
},
},
);
external::cpi::update_composite(cpi_ctx, value)?;
external::cpi::update_composite(cpi_ctx, 42)?;
cpi_my_account.reload()?;
require_eq!(cpi_my_account.field, 42);

// Composite accounts but not an actual instruction
let cpi_ctx = CpiContext::new(
ctx.accounts.external_program.to_account_info(),
external::cpi::accounts::UpdateNonInstructionComposite {
non_instruction_update: external::cpi::accounts::NonInstructionUpdate {
authority: ctx.accounts.authority.to_account_info(),
my_account: cpi_my_account.to_account_info(),
program: ctx.accounts.external_program.to_account_info(),
},
},
);
external::cpi::update_non_instruction_composite(cpi_ctx, value)?;
cpi_my_account.reload()?;
require_eq!(cpi_my_account.field, value);

Expand All @@ -60,7 +75,7 @@ pub mod declare_program {
return Err(ProgramError::Custom(0).into());
}

const DISC: &[u8] = &external::accounts::MyAccount::DISCRIMINATOR;
const DISC: &[u8] = external::accounts::MyAccount::DISCRIMINATOR;

// Correct discriminator but invalid data
if Account::try_from_bytes(DISC).is_ok() {
Expand All @@ -84,8 +99,7 @@ pub mod declare_program {
return Err(ProgramError::Custom(0).into());
}

const DISC: &[u8] =
&<external::events::MyEvent as anchor_lang::Discriminator>::DISCRIMINATOR;
const DISC: &[u8] = external::events::MyEvent::DISCRIMINATOR;

// Correct discriminator but invalid data
if Event::try_from_bytes(DISC).is_ok() {
Expand Down
22 changes: 22 additions & 0 deletions tests/declare-program/programs/external/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ pub mod external {
Ok(())
}

// Test the issue described in https://github.com/coral-xyz/anchor/issues/3274
pub fn update_non_instruction_composite(
ctx: Context<UpdateNonInstructionComposite>,
value: u32,
) -> Result<()> {
ctx.accounts.non_instruction_update.my_account.field = value;
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<TestCompilation>,
Expand Down Expand Up @@ -64,11 +73,24 @@ pub struct Update<'info> {
pub my_account: Account<'info, MyAccount>,
}

#[derive(Accounts)]
pub struct NonInstructionUpdate<'info> {
pub authority: Signer<'info>,
#[account(mut, seeds = [authority.key.as_ref()], bump)]
pub my_account: Account<'info, MyAccount>,
pub program: Program<'info, program::External>,
}

#[derive(Accounts)]
pub struct UpdateComposite<'info> {
pub update: Update<'info>,
}

#[derive(Accounts)]
pub struct UpdateNonInstructionComposite<'info> {
pub non_instruction_update: NonInstructionUpdate<'info>,
}

#[account]
pub struct MyAccount {
pub field: u32,
Expand Down

0 comments on commit cfe82aa

Please sign in to comment.