Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 886e7e6

Browse files
authored
ATA: Create explicit CreateAssociatedTokenAccount instruction (#2397)
* feat: create explicit CreateAssociatedTokenAccount instruction * feat: add create_associated_token_account for explicit instruction * chore: update comments and make clippy happy * chore: redirect legacy instruction creator to new instruction * chore: update comments * wip: scaffold mint to instruction creator and test * Revert "wip: scaffold mint to instruction creator and test" This reverts commit f825405. * chore: flag legacy create_associated_token_account as deprecated * chore: rename CreateAssociatedTokenAccount to Create * chore: move create_pda_account to tools:account * chore: comment deprecated attribute to prevent warnings until 1.0.4 is released * chore: fix test name * feat: remove Rent account from explicit instruction * chore: use current spl_token for implicit instruction test * chore: replicate legacy implicit instruction in test * chore: remove program name from instruction msg
1 parent bb843de commit 886e7e6

File tree

9 files changed

+358
-127
lines changed

9 files changed

+358
-127
lines changed

Cargo.lock

Lines changed: 11 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

associated-token-account/program/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "spl-associated-token-account"
3-
version = "1.0.3"
3+
version = "1.0.4"
44
description = "Solana Program Library Associated Token Account"
55
authors = ["Solana Maintainers <[email protected]>"]
66
repository = "https://github.com/solana-labs/solana-program-library"
@@ -12,6 +12,7 @@ no-entrypoint = []
1212
test-bpf = []
1313

1414
[dependencies]
15+
borsh = "0.9.1"
1516
solana-program = "1.7.11"
1617
spl-token = { version = "3.2", path = "../../token/program", features = ["no-entrypoint"] }
1718

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//! Program instructions
2+
3+
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4+
use solana_program::{
5+
instruction::{AccountMeta, Instruction},
6+
pubkey::Pubkey,
7+
};
8+
9+
use crate::{get_associated_token_address, id};
10+
11+
/// Instructions supported by the AssociatedTokenAccount program
12+
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
13+
pub enum AssociatedTokenAccountInstruction {
14+
/// Creates an associated token account for the given wallet address and token mint
15+
///
16+
/// 0. `[writeable,signer]` Funding account (must be a system account)
17+
/// 1. `[writeable]` Associated token account address to be created
18+
/// 2. `[]` Wallet address for the new associated token account
19+
/// 3. `[]` The token mint for the new associated token account
20+
/// 4. `[]` System program
21+
/// 5. `[]` SPL Token program
22+
Create,
23+
}
24+
25+
/// Creates CreateAssociatedTokenAccount instruction
26+
pub fn create_associated_token_account(
27+
funding_address: &Pubkey,
28+
wallet_address: &Pubkey,
29+
spl_token_mint_address: &Pubkey,
30+
) -> Instruction {
31+
let associated_account_address =
32+
get_associated_token_address(wallet_address, spl_token_mint_address);
33+
34+
let instruction_data = AssociatedTokenAccountInstruction::Create {};
35+
36+
Instruction {
37+
program_id: id(),
38+
accounts: vec![
39+
AccountMeta::new(*funding_address, true),
40+
AccountMeta::new(associated_account_address, false),
41+
AccountMeta::new_readonly(*wallet_address, false),
42+
AccountMeta::new_readonly(*spl_token_mint_address, false),
43+
AccountMeta::new_readonly(solana_program::system_program::id(), false),
44+
AccountMeta::new_readonly(spl_token::id(), false),
45+
],
46+
data: instruction_data.try_to_vec().unwrap(),
47+
}
48+
}

associated-token-account/program/src/lib.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#![forbid(unsafe_code)]
44

55
mod entrypoint;
6+
pub mod instruction;
67
pub mod processor;
8+
pub mod tools;
79

810
// Export current SDK types for downstream users building with a different SDK version
911
pub use solana_program;
@@ -65,25 +67,26 @@ fn get_associated_token_address_and_bump_seed_internal(
6567
/// 5. `[]` SPL Token program
6668
/// 6. `[]` Rent sysvar
6769
///
70+
// TODO: Uncomment after 1.0.4 is released
71+
// #[deprecated(
72+
// since = "1.0.4",
73+
// note = "please use `instruction::create_associated_token_account` instead"
74+
// )]
6875
pub fn create_associated_token_account(
6976
funding_address: &Pubkey,
7077
wallet_address: &Pubkey,
7178
spl_token_mint_address: &Pubkey,
7279
) -> Instruction {
73-
let associated_account_address =
74-
get_associated_token_address(wallet_address, spl_token_mint_address);
80+
let mut instruction = instruction::create_associated_token_account(
81+
funding_address,
82+
wallet_address,
83+
spl_token_mint_address,
84+
);
7585

76-
Instruction {
77-
program_id: id(),
78-
accounts: vec![
79-
AccountMeta::new(*funding_address, true),
80-
AccountMeta::new(associated_account_address, false),
81-
AccountMeta::new_readonly(*wallet_address, false),
82-
AccountMeta::new_readonly(*spl_token_mint_address, false),
83-
AccountMeta::new_readonly(solana_program::system_program::id(), false),
84-
AccountMeta::new_readonly(spl_token::id(), false),
85-
AccountMeta::new_readonly(sysvar::rent::id(), false),
86-
],
87-
data: vec![],
88-
}
86+
// TODO: Remove after ATA 1.0.4 and Token 3.2.0 are released (Token::InitializeAccount3 is required if rent account is not provided)
87+
instruction
88+
.accounts
89+
.push(AccountMeta::new_readonly(sysvar::rent::id(), false));
90+
91+
instruction
8992
}
Lines changed: 58 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,45 @@
11
//! Program state processor
22
33
use crate::*;
4+
use crate::{instruction::AssociatedTokenAccountInstruction, tools::account::create_pda_account};
5+
use borsh::BorshDeserialize;
46
use solana_program::{
57
account_info::{next_account_info, AccountInfo},
68
entrypoint::ProgramResult,
79
msg,
8-
program::{invoke, invoke_signed},
10+
program::invoke,
911
program_error::ProgramError,
1012
pubkey::Pubkey,
1113
rent::Rent,
12-
system_instruction,
1314
sysvar::Sysvar,
1415
};
1516

1617
/// Instruction processor
1718
pub fn process_instruction(
1819
program_id: &Pubkey,
1920
accounts: &[AccountInfo],
20-
_input: &[u8],
21+
input: &[u8],
22+
) -> ProgramResult {
23+
let instruction = if input.is_empty() {
24+
AssociatedTokenAccountInstruction::Create
25+
} else {
26+
AssociatedTokenAccountInstruction::try_from_slice(input)
27+
.map_err(|_| ProgramError::InvalidInstructionData)?
28+
};
29+
30+
msg!("{:?}", instruction);
31+
32+
match instruction {
33+
AssociatedTokenAccountInstruction::Create {} => {
34+
process_create_associated_token_account(program_id, accounts)
35+
}
36+
}
37+
}
38+
39+
/// Processes CreateAssociatedTokenAccount instruction
40+
pub fn process_create_associated_token_account(
41+
program_id: &Pubkey,
42+
accounts: &[AccountInfo],
2143
) -> ProgramResult {
2244
let account_info_iter = &mut accounts.iter();
2345

@@ -28,7 +50,11 @@ pub fn process_instruction(
2850
let system_program_info = next_account_info(account_info_iter)?;
2951
let spl_token_program_info = next_account_info(account_info_iter)?;
3052
let spl_token_program_id = spl_token_program_info.key;
31-
let rent_sysvar_info = next_account_info(account_info_iter)?;
53+
54+
// TODO: Remove after ATA 1.0.4 and Token 3.2.0 are released and just use Rent::get()
55+
let (rent_sysvar_info, rent) = next_account_info(account_info_iter)
56+
.map(|info| (Some(info), Rent::from_account_info(info).unwrap()))
57+
.unwrap_or((None, Rent::get().unwrap()));
3258

3359
let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed_internal(
3460
wallet_account_info.key,
@@ -48,11 +74,9 @@ pub fn process_instruction(
4874
&[bump_seed],
4975
];
5076

51-
let rent = &Rent::from_account_info(rent_sysvar_info)?;
52-
5377
create_pda_account(
5478
funder_info,
55-
rent,
79+
&rent,
5680
spl_token::state::Account::LEN,
5781
&spl_token::id(),
5882
system_program_info,
@@ -61,75 +85,38 @@ pub fn process_instruction(
6185
)?;
6286

6387
msg!("Initialize the associated token account");
64-
invoke(
65-
&spl_token::instruction::initialize_account(
66-
spl_token_program_id,
67-
associated_token_account_info.key,
68-
spl_token_mint_info.key,
69-
wallet_account_info.key,
70-
)?,
71-
&[
72-
associated_token_account_info.clone(),
73-
spl_token_mint_info.clone(),
74-
wallet_account_info.clone(),
75-
rent_sysvar_info.clone(),
76-
spl_token_program_info.clone(),
77-
],
78-
)
79-
}
80-
81-
fn create_pda_account<'a>(
82-
funder: &AccountInfo<'a>,
83-
rent: &Rent,
84-
space: usize,
85-
owner: &Pubkey,
86-
system_program: &AccountInfo<'a>,
87-
new_pda_account: &AccountInfo<'a>,
88-
new_pda_signer_seeds: &[&[u8]],
89-
) -> ProgramResult {
90-
if new_pda_account.lamports() > 0 {
91-
let required_lamports = rent
92-
.minimum_balance(space)
93-
.max(1)
94-
.saturating_sub(new_pda_account.lamports());
9588

96-
if required_lamports > 0 {
97-
invoke(
98-
&system_instruction::transfer(funder.key, new_pda_account.key, required_lamports),
99-
&[
100-
funder.clone(),
101-
new_pda_account.clone(),
102-
system_program.clone(),
103-
],
104-
)?;
105-
}
106-
107-
invoke_signed(
108-
&system_instruction::allocate(new_pda_account.key, space as u64),
109-
&[new_pda_account.clone(), system_program.clone()],
110-
&[new_pda_signer_seeds],
111-
)?;
112-
113-
invoke_signed(
114-
&system_instruction::assign(new_pda_account.key, owner),
115-
&[new_pda_account.clone(), system_program.clone()],
116-
&[new_pda_signer_seeds],
89+
if let Some(rent_sysvar_info) = rent_sysvar_info {
90+
invoke(
91+
&spl_token::instruction::initialize_account(
92+
spl_token_program_id,
93+
associated_token_account_info.key,
94+
spl_token_mint_info.key,
95+
wallet_account_info.key,
96+
)?,
97+
&[
98+
associated_token_account_info.clone(),
99+
spl_token_mint_info.clone(),
100+
wallet_account_info.clone(),
101+
rent_sysvar_info.clone(),
102+
spl_token_program_info.clone(),
103+
],
117104
)
118105
} else {
119-
invoke_signed(
120-
&system_instruction::create_account(
121-
funder.key,
122-
new_pda_account.key,
123-
rent.minimum_balance(space).max(1),
124-
space as u64,
125-
owner,
126-
),
106+
// Use InitializeAccount3 when Rent account is not provided
107+
invoke(
108+
&spl_token::instruction::initialize_account3(
109+
spl_token_program_id,
110+
associated_token_account_info.key,
111+
spl_token_mint_info.key,
112+
wallet_account_info.key,
113+
)?,
127114
&[
128-
funder.clone(),
129-
new_pda_account.clone(),
130-
system_program.clone(),
115+
associated_token_account_info.clone(),
116+
spl_token_mint_info.clone(),
117+
wallet_account_info.clone(),
118+
spl_token_program_info.clone(),
131119
],
132-
&[new_pda_signer_seeds],
133120
)
134121
}
135122
}

0 commit comments

Comments
 (0)