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

Commit 4da9cb3

Browse files
mvinesmergify[bot]
authored andcommitted
Add associated-token-account program
1 parent b68163f commit 4da9cb3

File tree

11 files changed

+953
-85
lines changed

11 files changed

+953
-85
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"associated-token-account/program",
34
"memo/program",
45
"shared-memory/program",
56
"stake-pool/program",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "spl-associated-token-account"
3+
version = "1.0.0"
4+
description = "SPL Associated Token Account"
5+
authors = ["Solana Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2018"
9+
10+
[features]
11+
exclude_entrypoint = []
12+
13+
[dependencies]
14+
solana-program = "1.4.4"
15+
spl-token = { path = "../../token/program", features = ["exclude_entrypoint"] }
16+
17+
[dev-dependencies]
18+
solana-program-test = "1.4.4"
19+
solana-sdk = "1.4.4"
20+
tokio = { version = "0.3", features = ["macros"]}
21+
22+
[lib]
23+
crate-type = ["cdylib", "lib"]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::process::{exit, Command};
2+
3+
fn main() {
4+
if std::env::var("XARGO").is_err()
5+
&& std::env::var("RUSTC_WRAPPER").is_err()
6+
&& std::env::var("RUSTC_WORKSPACE_WRAPPER").is_err()
7+
{
8+
println!(
9+
"cargo:warning=(not a warning) Building BPF {} program",
10+
std::env::var("CARGO_PKG_NAME").unwrap()
11+
);
12+
if !Command::new("cargo")
13+
.arg("build-bpf")
14+
.status()
15+
.expect("Failed to build bpf")
16+
.success()
17+
{
18+
exit(1);
19+
}
20+
}
21+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
3+
set -ex
4+
cd "$(dirname "$0")"
5+
cargo clippy
6+
cargo build
7+
cargo build-bpf
8+
9+
if [[ $1 = -v ]]; then
10+
export RUST_LOG=solana=debug
11+
fi
12+
13+
bpf=1 cargo test
14+
# TODO: bpf=0 not supported until native CPI rework in the monorepo completes
15+
#bpf=0 cargo test
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//! Program entrypoint
2+
3+
#![cfg(all(target_arch = "bpf", not(feature = "exclude_entrypoint")))]
4+
5+
use solana_program::{
6+
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
7+
};
8+
9+
entrypoint!(process_instruction);
10+
fn process_instruction(
11+
program_id: &Pubkey,
12+
accounts: &[AccountInfo],
13+
instruction_data: &[u8],
14+
) -> ProgramResult {
15+
crate::processor::process_instruction(program_id, accounts, instruction_data)
16+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//! Convention for associating token accounts with a primary account (such as a user wallet)
2+
#![deny(missing_docs)]
3+
#![forbid(unsafe_code)]
4+
5+
mod entrypoint;
6+
pub mod processor;
7+
8+
// Export current sdk types for downstream users building with a different sdk version
9+
pub use solana_program;
10+
use solana_program::{
11+
instruction::{AccountMeta, Instruction},
12+
program_pack::Pack,
13+
pubkey::Pubkey,
14+
sysvar,
15+
};
16+
17+
solana_program::declare_id!("3medvrcM8s3UnkoYqqV3RAURii1ysuT5oD7t8nmfgJmj");
18+
19+
pub(crate) fn get_associated_token_address_and_bump_seed(
20+
wallet_address: &Pubkey,
21+
spl_token_mint_address: &Pubkey,
22+
program_id: &Pubkey,
23+
) -> (Pubkey, u8) {
24+
Pubkey::find_program_address(
25+
&[
26+
&wallet_address.to_bytes(),
27+
&spl_token::id().to_bytes(),
28+
&spl_token_mint_address.to_bytes(),
29+
],
30+
program_id,
31+
)
32+
}
33+
34+
/// Derives the associated SPL token address for the given wallet address and SPL Token mint
35+
pub fn get_associated_token_address(
36+
wallet_address: &Pubkey,
37+
spl_token_mint_address: &Pubkey,
38+
) -> Pubkey {
39+
get_associated_token_address_and_bump_seed(&wallet_address, &spl_token_mint_address, &id()).0
40+
}
41+
42+
/// Create an associated token account for a wallet address
43+
///
44+
/// Accounts expected by this instruction:
45+
///
46+
/// 0. `[writeable,signer]` Funding account (must be a system account)
47+
/// 1. `[writeable]` Associated token account address
48+
/// 2. `[]` Wallet address for the new associated token account
49+
/// 3. `[]` The SPL token mint for the associated token account
50+
/// 4. `[]` System program
51+
/// 4. `[]` SPL Token program
52+
/// 5. `[]` Rent sysvar
53+
///
54+
pub fn create_associated_token_account(
55+
funding_address: &Pubkey,
56+
wallet_address: &Pubkey,
57+
spl_token_mint_address: &Pubkey,
58+
) -> Instruction {
59+
let associated_account_address =
60+
get_associated_token_address(wallet_address, spl_token_mint_address);
61+
62+
Instruction {
63+
program_id: id(),
64+
accounts: vec![
65+
AccountMeta::new(*funding_address, true),
66+
AccountMeta::new(associated_account_address, false),
67+
AccountMeta::new_readonly(*wallet_address, false),
68+
AccountMeta::new_readonly(*spl_token_mint_address, false),
69+
AccountMeta::new_readonly(solana_program::system_program::id(), false),
70+
AccountMeta::new_readonly(spl_token::id(), false),
71+
AccountMeta::new_readonly(sysvar::rent::id(), false),
72+
],
73+
data: vec![],
74+
}
75+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//! Program state processor
2+
3+
use crate::*;
4+
use solana_program::{
5+
account_info::{next_account_info, AccountInfo},
6+
entrypoint::ProgramResult,
7+
info,
8+
log::sol_log_compute_units,
9+
program::{invoke, invoke_signed},
10+
program_error::ProgramError,
11+
pubkey::Pubkey,
12+
rent::Rent,
13+
system_instruction,
14+
sysvar::Sysvar,
15+
};
16+
17+
/// Instruction processor
18+
pub fn process_instruction(
19+
program_id: &Pubkey,
20+
accounts: &[AccountInfo],
21+
_input: &[u8],
22+
) -> ProgramResult {
23+
let account_info_iter = &mut accounts.iter();
24+
25+
let funder_info = next_account_info(account_info_iter)?;
26+
let associated_token_account_info = next_account_info(account_info_iter)?;
27+
let wallet_account_info = next_account_info(account_info_iter)?;
28+
let spl_token_mint_info = next_account_info(account_info_iter)?;
29+
let system_program_info = next_account_info(account_info_iter)?;
30+
let spl_token_program_info = next_account_info(account_info_iter)?;
31+
let rent_sysvar_info = next_account_info(account_info_iter)?;
32+
33+
let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed(
34+
&wallet_account_info.key,
35+
&spl_token_mint_info.key,
36+
program_id,
37+
);
38+
if associated_token_address != *associated_token_account_info.key {
39+
info!("Error: Associated address does not match seed derivation");
40+
return Err(ProgramError::InvalidSeeds);
41+
}
42+
43+
let associated_token_account_signer_seeds: &[&[_]] = &[
44+
&wallet_account_info.key.to_bytes(),
45+
&spl_token::id().to_bytes(),
46+
&spl_token_mint_info.key.to_bytes(),
47+
&[bump_seed],
48+
];
49+
50+
sol_log_compute_units();
51+
52+
// Fund the associated token account with the minimum balance to be rent exempt
53+
let rent = &Rent::from_account_info(rent_sysvar_info)?;
54+
let required_lamports = rent
55+
.minimum_balance(spl_token::state::Account::LEN)
56+
.max(1)
57+
.saturating_sub(associated_token_account_info.lamports());
58+
59+
if required_lamports > 0 {
60+
invoke(
61+
&system_instruction::transfer(
62+
&funder_info.key,
63+
associated_token_account_info.key,
64+
required_lamports,
65+
),
66+
&[
67+
funder_info.clone(),
68+
associated_token_account_info.clone(),
69+
system_program_info.clone(),
70+
],
71+
)?;
72+
}
73+
74+
// Allocate space for the associated token account
75+
invoke_signed(
76+
&system_instruction::allocate(
77+
associated_token_account_info.key,
78+
spl_token::state::Account::LEN as u64,
79+
),
80+
&[
81+
associated_token_account_info.clone(),
82+
system_program_info.clone(),
83+
],
84+
&[&associated_token_account_signer_seeds],
85+
)?;
86+
87+
// Assign the associated token account to the SPL Token program
88+
invoke_signed(
89+
&system_instruction::assign(associated_token_account_info.key, &spl_token::id()),
90+
&[
91+
associated_token_account_info.clone(),
92+
system_program_info.clone(),
93+
],
94+
&[&associated_token_account_signer_seeds],
95+
)?;
96+
97+
// Initialize the associated token account
98+
invoke(
99+
&spl_token::instruction::initialize_account(
100+
&spl_token::id(),
101+
associated_token_account_info.key,
102+
spl_token_mint_info.key,
103+
wallet_account_info.key,
104+
)?,
105+
&[
106+
associated_token_account_info.clone(),
107+
spl_token_mint_info.clone(),
108+
wallet_account_info.clone(),
109+
rent_sysvar_info.clone(),
110+
spl_token_program_info.clone(),
111+
],
112+
)
113+
}
Binary file not shown.

0 commit comments

Comments
 (0)