Skip to content

Commit 1336cf7

Browse files
idl: Add accounts resolution for associated token accounts (otter-sec#2927)
1 parent abed894 commit 1336cf7

5 files changed

Lines changed: 123 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The minor version will be incremented upon a breaking change and the patch versi
1313
### Features
1414

1515
- avm: Support customizing the installation location using `AVM_HOME` environment variable ([#2917](https://github.com/coral-xyz/anchor/pull/2917))
16+
- idl, ts: Add accounts resolution for associated token accounts ([#2927](https://github.com/coral-xyz/anchor/pull/2927))
1617

1718
### Fixes
1819

lang/syn/src/idl/accounts.rs

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use proc_macro2::TokenStream;
33
use quote::{quote, ToTokens};
44

55
use super::common::{get_idl_module_path, get_no_docs};
6-
use crate::{AccountField, AccountsStruct, Field, Ty};
6+
use crate::{AccountField, AccountsStruct, Field, InitKind, Ty};
77

88
/// Generate the IDL build impl for the Accounts struct.
99
pub fn gen_idl_build_impl_accounts_struct(accounts: &AccountsStruct) -> TokenStream {
@@ -168,26 +168,87 @@ fn get_address(acc: &Field) -> TokenStream {
168168

169169
fn get_pda(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
170170
let idl = get_idl_module_path();
171+
let parse_default = |expr: &syn::Expr| parse_seed(expr, accounts);
172+
173+
// Seeds
171174
let seed_constraints = acc.constraints.seeds.as_ref();
172-
let seeds = seed_constraints
173-
.map(|seed| seed.seeds.iter().map(|seed| parse_seed(seed, accounts)))
174-
.and_then(|seeds| seeds.collect::<Result<Vec<_>>>().ok());
175-
let program = seed_constraints
176-
.and_then(|seed| seed.program_seed.as_ref())
177-
.and_then(|program| parse_seed(program, accounts).ok())
178-
.map(|program| quote! { Some(#program) })
179-
.unwrap_or_else(|| quote! { None });
180-
match seeds {
181-
Some(seeds) => quote! {
182-
Some(
183-
#idl::IdlPda {
184-
seeds: vec![#(#seeds),*],
185-
program: #program,
186-
}
187-
)
188-
},
189-
_ => quote! { None },
175+
let pda = seed_constraints
176+
.map(|seed| seed.seeds.iter().map(parse_default))
177+
.and_then(|seeds| seeds.collect::<Result<Vec<_>>>().ok())
178+
.map(|seeds| {
179+
let program = seed_constraints
180+
.and_then(|seed| seed.program_seed.as_ref())
181+
.and_then(|program| parse_default(program).ok())
182+
.map(|program| quote! { Some(#program) })
183+
.unwrap_or_else(|| quote! { None });
184+
185+
quote! {
186+
Some(
187+
#idl::IdlPda {
188+
seeds: vec![#(#seeds),*],
189+
program: #program,
190+
}
191+
)
192+
}
193+
});
194+
if let Some(pda) = pda {
195+
return pda;
190196
}
197+
198+
// Associated token
199+
let pda = acc
200+
.constraints
201+
.init
202+
.as_ref()
203+
.and_then(|init| match &init.kind {
204+
InitKind::AssociatedToken {
205+
owner,
206+
mint,
207+
token_program,
208+
} => Some((owner, mint, token_program)),
209+
_ => None,
210+
})
211+
.or_else(|| {
212+
acc.constraints
213+
.associated_token
214+
.as_ref()
215+
.map(|ata| (&ata.wallet, &ata.mint, &ata.token_program))
216+
})
217+
.and_then(|(wallet, mint, token_program)| {
218+
// ATA constraints have implicit `.key()` call
219+
let parse_expr = |ts| parse_default(&syn::parse2(ts).unwrap()).ok();
220+
let parse_ata = |expr| parse_expr(quote! { #expr.key().as_ref() });
221+
222+
let wallet = parse_ata(wallet);
223+
let mint = parse_ata(mint);
224+
let token_program = token_program
225+
.as_ref()
226+
.and_then(parse_ata)
227+
.or_else(|| parse_expr(quote!(anchor_spl::token::ID)));
228+
229+
let seeds = match (wallet, mint, token_program) {
230+
(Some(w), Some(m), Some(tp)) => quote! { vec![#w, #tp, #m] },
231+
_ => return None,
232+
};
233+
234+
let program = parse_expr(quote!(anchor_spl::associated_token::ID))
235+
.map(|program| quote! { Some(#program) })
236+
.unwrap();
237+
238+
Some(quote! {
239+
Some(
240+
#idl::IdlPda {
241+
seeds: #seeds,
242+
program: #program,
243+
}
244+
)
245+
})
246+
});
247+
if let Some(pda) = pda {
248+
return pda;
249+
}
250+
251+
quote! { None }
191252
}
192253

193254
/// Parse a seeds constraint, extracting the `IdlSeed` types.

tests/pda-derivation/programs/pda-derivation/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ no-entrypoint = []
1414
no-idl = []
1515
cpi = ["no-entrypoint"]
1616
default = []
17-
idl-build = ["anchor-lang/idl-build"]
17+
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
1818

1919
[dependencies]
2020
anchor-lang = { path = "../../../../lang" }
21+
anchor-spl = { path = "../../../../spl" }

tests/pda-derivation/programs/pda-derivation/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
mod other;
55

66
use anchor_lang::prelude::*;
7+
use anchor_spl::{
8+
associated_token::AssociatedToken,
9+
token::{Mint, Token, TokenAccount},
10+
};
711

812
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
913

@@ -34,6 +38,10 @@ pub mod pda_derivation {
3438
ctx.accounts.account.data = 1337;
3539
Ok(())
3640
}
41+
42+
pub fn associated_token_resolution(_ctx: Context<AssociatedTokenResolution>) -> Result<()> {
43+
Ok(())
44+
}
3745
}
3846

3947
#[derive(Accounts)]
@@ -115,6 +123,29 @@ pub struct Nested<'info> {
115123
account_nested: AccountInfo<'info>,
116124
}
117125

126+
#[derive(Accounts)]
127+
pub struct AssociatedTokenResolution<'info> {
128+
#[account(
129+
init,
130+
payer = payer,
131+
mint::authority = payer,
132+
mint::decimals = 9,
133+
)]
134+
pub mint: Account<'info, Mint>,
135+
#[account(
136+
init,
137+
payer = payer,
138+
associated_token::authority = payer,
139+
associated_token::mint = mint,
140+
)]
141+
pub ata: Account<'info, TokenAccount>,
142+
#[account(mut)]
143+
pub payer: Signer<'info>,
144+
pub system_program: Program<'info, System>,
145+
pub token_program: Program<'info, Token>,
146+
pub associated_token_program: Program<'info, AssociatedToken>,
147+
}
148+
118149
#[account]
119150
pub struct MyAccount {
120151
data: u64,

tests/pda-derivation/tests/typescript.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,13 @@ describe("typescript", () => {
103103

104104
expect(called).is.true;
105105
});
106+
107+
it("Can resolve associated token accounts", async () => {
108+
const mintKp = anchor.web3.Keypair.generate();
109+
await program.methods
110+
.associatedTokenResolution()
111+
.accounts({ mint: mintKp.publicKey })
112+
.signers([mintKp])
113+
.rpc();
114+
});
106115
});

0 commit comments

Comments
 (0)