Add associated-token-account program
This commit is contained in:
parent
b68163ff23
commit
4da9cb3631
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
"associated-token-account/program",
|
||||||
"memo/program",
|
"memo/program",
|
||||||
"shared-memory/program",
|
"shared-memory/program",
|
||||||
"stake-pool/program",
|
"stake-pool/program",
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "spl-associated-token-account"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "SPL Associated Token Account"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||||
|
repository = "https://github.com/solana-labs/solana-program-library"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
exclude_entrypoint = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
solana-program = "1.4.4"
|
||||||
|
spl-token = { path = "../../token/program", features = ["exclude_entrypoint"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
solana-program-test = "1.4.4"
|
||||||
|
solana-sdk = "1.4.4"
|
||||||
|
tokio = { version = "0.3", features = ["macros"]}
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
|
@ -0,0 +1,2 @@
|
||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
|
@ -0,0 +1,21 @@
|
||||||
|
use std::process::{exit, Command};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if std::env::var("XARGO").is_err()
|
||||||
|
&& std::env::var("RUSTC_WRAPPER").is_err()
|
||||||
|
&& std::env::var("RUSTC_WORKSPACE_WRAPPER").is_err()
|
||||||
|
{
|
||||||
|
println!(
|
||||||
|
"cargo:warning=(not a warning) Building BPF {} program",
|
||||||
|
std::env::var("CARGO_PKG_NAME").unwrap()
|
||||||
|
);
|
||||||
|
if !Command::new("cargo")
|
||||||
|
.arg("build-bpf")
|
||||||
|
.status()
|
||||||
|
.expect("Failed to build bpf")
|
||||||
|
.success()
|
||||||
|
{
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
cargo clippy
|
||||||
|
cargo build
|
||||||
|
cargo build-bpf
|
||||||
|
|
||||||
|
if [[ $1 = -v ]]; then
|
||||||
|
export RUST_LOG=solana=debug
|
||||||
|
fi
|
||||||
|
|
||||||
|
bpf=1 cargo test
|
||||||
|
# TODO: bpf=0 not supported until native CPI rework in the monorepo completes
|
||||||
|
#bpf=0 cargo test
|
|
@ -0,0 +1,16 @@
|
||||||
|
//! Program entrypoint
|
||||||
|
|
||||||
|
#![cfg(all(target_arch = "bpf", not(feature = "exclude_entrypoint")))]
|
||||||
|
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
entrypoint!(process_instruction);
|
||||||
|
fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
crate::processor::process_instruction(program_id, accounts, instruction_data)
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
//! Convention for associating token accounts with a primary account (such as a user wallet)
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
mod entrypoint;
|
||||||
|
pub mod processor;
|
||||||
|
|
||||||
|
// Export current sdk types for downstream users building with a different sdk version
|
||||||
|
pub use solana_program;
|
||||||
|
use solana_program::{
|
||||||
|
instruction::{AccountMeta, Instruction},
|
||||||
|
program_pack::Pack,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
solana_program::declare_id!("3medvrcM8s3UnkoYqqV3RAURii1ysuT5oD7t8nmfgJmj");
|
||||||
|
|
||||||
|
pub(crate) fn get_associated_token_address_and_bump_seed(
|
||||||
|
wallet_address: &Pubkey,
|
||||||
|
spl_token_mint_address: &Pubkey,
|
||||||
|
program_id: &Pubkey,
|
||||||
|
) -> (Pubkey, u8) {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&[
|
||||||
|
&wallet_address.to_bytes(),
|
||||||
|
&spl_token::id().to_bytes(),
|
||||||
|
&spl_token_mint_address.to_bytes(),
|
||||||
|
],
|
||||||
|
program_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derives the associated SPL token address for the given wallet address and SPL Token mint
|
||||||
|
pub fn get_associated_token_address(
|
||||||
|
wallet_address: &Pubkey,
|
||||||
|
spl_token_mint_address: &Pubkey,
|
||||||
|
) -> Pubkey {
|
||||||
|
get_associated_token_address_and_bump_seed(&wallet_address, &spl_token_mint_address, &id()).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an associated token account for a wallet address
|
||||||
|
///
|
||||||
|
/// Accounts expected by this instruction:
|
||||||
|
///
|
||||||
|
/// 0. `[writeable,signer]` Funding account (must be a system account)
|
||||||
|
/// 1. `[writeable]` Associated token account address
|
||||||
|
/// 2. `[]` Wallet address for the new associated token account
|
||||||
|
/// 3. `[]` The SPL token mint for the associated token account
|
||||||
|
/// 4. `[]` System program
|
||||||
|
/// 4. `[]` SPL Token program
|
||||||
|
/// 5. `[]` Rent sysvar
|
||||||
|
///
|
||||||
|
pub fn create_associated_token_account(
|
||||||
|
funding_address: &Pubkey,
|
||||||
|
wallet_address: &Pubkey,
|
||||||
|
spl_token_mint_address: &Pubkey,
|
||||||
|
) -> Instruction {
|
||||||
|
let associated_account_address =
|
||||||
|
get_associated_token_address(wallet_address, spl_token_mint_address);
|
||||||
|
|
||||||
|
Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts: vec![
|
||||||
|
AccountMeta::new(*funding_address, true),
|
||||||
|
AccountMeta::new(associated_account_address, false),
|
||||||
|
AccountMeta::new_readonly(*wallet_address, false),
|
||||||
|
AccountMeta::new_readonly(*spl_token_mint_address, false),
|
||||||
|
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||||
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
|
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||||
|
],
|
||||||
|
data: vec![],
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
info,
|
||||||
|
log::sol_log_compute_units,
|
||||||
|
program::{invoke, invoke_signed},
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
|
system_instruction,
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Instruction processor
|
||||||
|
pub fn process_instruction(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
_input: &[u8],
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let funder_info = next_account_info(account_info_iter)?;
|
||||||
|
let associated_token_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let wallet_account_info = next_account_info(account_info_iter)?;
|
||||||
|
let spl_token_mint_info = next_account_info(account_info_iter)?;
|
||||||
|
let system_program_info = next_account_info(account_info_iter)?;
|
||||||
|
let spl_token_program_info = next_account_info(account_info_iter)?;
|
||||||
|
let rent_sysvar_info = next_account_info(account_info_iter)?;
|
||||||
|
|
||||||
|
let (associated_token_address, bump_seed) = get_associated_token_address_and_bump_seed(
|
||||||
|
&wallet_account_info.key,
|
||||||
|
&spl_token_mint_info.key,
|
||||||
|
program_id,
|
||||||
|
);
|
||||||
|
if associated_token_address != *associated_token_account_info.key {
|
||||||
|
info!("Error: Associated address does not match seed derivation");
|
||||||
|
return Err(ProgramError::InvalidSeeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
let associated_token_account_signer_seeds: &[&[_]] = &[
|
||||||
|
&wallet_account_info.key.to_bytes(),
|
||||||
|
&spl_token::id().to_bytes(),
|
||||||
|
&spl_token_mint_info.key.to_bytes(),
|
||||||
|
&[bump_seed],
|
||||||
|
];
|
||||||
|
|
||||||
|
sol_log_compute_units();
|
||||||
|
|
||||||
|
// Fund the associated token account with the minimum balance to be rent exempt
|
||||||
|
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||||
|
let required_lamports = rent
|
||||||
|
.minimum_balance(spl_token::state::Account::LEN)
|
||||||
|
.max(1)
|
||||||
|
.saturating_sub(associated_token_account_info.lamports());
|
||||||
|
|
||||||
|
if required_lamports > 0 {
|
||||||
|
invoke(
|
||||||
|
&system_instruction::transfer(
|
||||||
|
&funder_info.key,
|
||||||
|
associated_token_account_info.key,
|
||||||
|
required_lamports,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
funder_info.clone(),
|
||||||
|
associated_token_account_info.clone(),
|
||||||
|
system_program_info.clone(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate space for the associated token account
|
||||||
|
invoke_signed(
|
||||||
|
&system_instruction::allocate(
|
||||||
|
associated_token_account_info.key,
|
||||||
|
spl_token::state::Account::LEN as u64,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
associated_token_account_info.clone(),
|
||||||
|
system_program_info.clone(),
|
||||||
|
],
|
||||||
|
&[&associated_token_account_signer_seeds],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Assign the associated token account to the SPL Token program
|
||||||
|
invoke_signed(
|
||||||
|
&system_instruction::assign(associated_token_account_info.key, &spl_token::id()),
|
||||||
|
&[
|
||||||
|
associated_token_account_info.clone(),
|
||||||
|
system_program_info.clone(),
|
||||||
|
],
|
||||||
|
&[&associated_token_account_signer_seeds],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Initialize the associated token account
|
||||||
|
invoke(
|
||||||
|
&spl_token::instruction::initialize_account(
|
||||||
|
&spl_token::id(),
|
||||||
|
associated_token_account_info.key,
|
||||||
|
spl_token_mint_info.key,
|
||||||
|
wallet_account_info.key,
|
||||||
|
)?,
|
||||||
|
&[
|
||||||
|
associated_token_account_info.clone(),
|
||||||
|
spl_token_mint_info.clone(),
|
||||||
|
wallet_account_info.clone(),
|
||||||
|
rent_sysvar_info.clone(),
|
||||||
|
spl_token_program_info.clone(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,251 @@
|
||||||
|
use solana_program::{
|
||||||
|
instruction::*, program_pack::Pack, pubkey::Pubkey, system_instruction, sysvar::rent::Rent,
|
||||||
|
};
|
||||||
|
use solana_program_test::*;
|
||||||
|
use solana_sdk::{
|
||||||
|
signature::Signer,
|
||||||
|
transaction::{Transaction, TransactionError},
|
||||||
|
};
|
||||||
|
use spl_associated_token_account::*;
|
||||||
|
|
||||||
|
fn program_test(token_mint_address: Pubkey) -> ProgramTest {
|
||||||
|
let mut pc = ProgramTest::new(
|
||||||
|
"spl_associated_token_account",
|
||||||
|
id(),
|
||||||
|
// TODO: BPF only until native CPI rework in the monorepo completes
|
||||||
|
None, //processor!(processor::process_instruction),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add Token program
|
||||||
|
pc.add_program(
|
||||||
|
"spl_token",
|
||||||
|
spl_token::id(),
|
||||||
|
processor!(spl_token::processor::Processor::process),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a token mint account
|
||||||
|
//
|
||||||
|
// The account data was generated by running:
|
||||||
|
// $ solana account EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
|
||||||
|
// --output-file tests/fixtures/token-mint-data.bin
|
||||||
|
//
|
||||||
|
pc.add_account_with_file_data(
|
||||||
|
token_mint_address,
|
||||||
|
1461600,
|
||||||
|
spl_token::id(),
|
||||||
|
"token-mint-data.bin",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dial down the BPF compute budget to detect if the program gets bloated in the future
|
||||||
|
pc.set_bpf_compute_max_units(50_000);
|
||||||
|
|
||||||
|
pc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_associated_token_address() {
|
||||||
|
let wallet_address = Pubkey::new_unique();
|
||||||
|
let token_mint_address = Pubkey::new_unique();
|
||||||
|
let associated_token_address =
|
||||||
|
get_associated_token_address(&wallet_address, &token_mint_address);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) =
|
||||||
|
program_test(token_mint_address).start().await;
|
||||||
|
let rent = Rent::default(); // <-- TODO: get Rent from `ProgramTest`
|
||||||
|
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
|
||||||
|
|
||||||
|
// Associated account does not exist
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.get_account(associated_token_address)
|
||||||
|
.await
|
||||||
|
.expect("get_account"),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[create_associated_token_account(
|
||||||
|
&payer.pubkey(),
|
||||||
|
&wallet_address,
|
||||||
|
&token_mint_address,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
// Associated account now exists
|
||||||
|
let associated_account = banks_client
|
||||||
|
.get_account(associated_token_address)
|
||||||
|
.await
|
||||||
|
.expect("get_account")
|
||||||
|
.expect("associated_account not none");
|
||||||
|
assert_eq!(
|
||||||
|
associated_account.data.len(),
|
||||||
|
spl_token::state::Account::LEN
|
||||||
|
);
|
||||||
|
assert_eq!(associated_account.owner, spl_token::id());
|
||||||
|
assert_eq!(associated_account.lamports, expected_token_account_balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_with_a_lamport() {
|
||||||
|
let wallet_address = Pubkey::new_unique();
|
||||||
|
let token_mint_address = Pubkey::new_unique();
|
||||||
|
let associated_token_address =
|
||||||
|
get_associated_token_address(&wallet_address, &token_mint_address);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) =
|
||||||
|
program_test(token_mint_address).start().await;
|
||||||
|
let rent = Rent::default(); // <-- TOOD: get Rent from `ProgramTest`
|
||||||
|
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
|
||||||
|
|
||||||
|
// Transfer 1 lamport into `associated_token_address` before creating it
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[system_instruction::transfer(
|
||||||
|
&payer.pubkey(),
|
||||||
|
&associated_token_address,
|
||||||
|
1,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.get_balance(associated_token_address)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the program adds the extra lamports
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[create_associated_token_account(
|
||||||
|
&payer.pubkey(),
|
||||||
|
&wallet_address,
|
||||||
|
&token_mint_address,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.get_balance(associated_token_address)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
expected_token_account_balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_with_excess_lamports() {
|
||||||
|
let wallet_address = Pubkey::new_unique();
|
||||||
|
let token_mint_address = Pubkey::new_unique();
|
||||||
|
let associated_token_address =
|
||||||
|
get_associated_token_address(&wallet_address, &token_mint_address);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) =
|
||||||
|
program_test(token_mint_address).start().await;
|
||||||
|
let rent = Rent::default(); // <-- TOOD: get Rent from `ProgramTest`
|
||||||
|
let expected_token_account_balance = rent.minimum_balance(spl_token::state::Account::LEN);
|
||||||
|
|
||||||
|
// Transfer 1 lamport into `associated_token_address` before creating it
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[system_instruction::transfer(
|
||||||
|
&payer.pubkey(),
|
||||||
|
&associated_token_address,
|
||||||
|
expected_token_account_balance + 1,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.get_balance(associated_token_address)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
expected_token_account_balance + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the program doesn't add any lamports
|
||||||
|
let mut transaction = Transaction::new_with_payer(
|
||||||
|
&[create_associated_token_account(
|
||||||
|
&payer.pubkey(),
|
||||||
|
&wallet_address,
|
||||||
|
&token_mint_address,
|
||||||
|
)],
|
||||||
|
Some(&payer.pubkey()),
|
||||||
|
);
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
banks_client.process_transaction(transaction).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.get_balance(associated_token_address)
|
||||||
|
.await
|
||||||
|
.unwrap(),
|
||||||
|
expected_token_account_balance + 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_account_mismatch() {
|
||||||
|
let wallet_address = Pubkey::new_unique();
|
||||||
|
let token_mint_address = Pubkey::new_unique();
|
||||||
|
let _associated_token_address =
|
||||||
|
get_associated_token_address(&wallet_address, &token_mint_address);
|
||||||
|
|
||||||
|
let (mut banks_client, payer, recent_blockhash) =
|
||||||
|
program_test(token_mint_address).start().await;
|
||||||
|
|
||||||
|
let mut instruction =
|
||||||
|
create_associated_token_account(&payer.pubkey(), &wallet_address, &token_mint_address);
|
||||||
|
instruction.accounts[1] = AccountMeta::new(Pubkey::default(), false); // <-- Invalid associated_account_address
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::InvalidSeeds)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut instruction =
|
||||||
|
create_associated_token_account(&payer.pubkey(), &wallet_address, &token_mint_address);
|
||||||
|
instruction.accounts[2] = AccountMeta::new(Pubkey::default(), false); // <-- Invalid wallet_address
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::InvalidSeeds)
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut instruction =
|
||||||
|
create_associated_token_account(&payer.pubkey(), &wallet_address, &token_mint_address);
|
||||||
|
instruction.accounts[3] = AccountMeta::new(Pubkey::default(), false); // <-- Invalid token_mint_address
|
||||||
|
|
||||||
|
let mut transaction = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
|
||||||
|
transaction.sign(&[&payer], recent_blockhash);
|
||||||
|
assert_eq!(
|
||||||
|
banks_client
|
||||||
|
.process_transaction(transaction)
|
||||||
|
.await
|
||||||
|
.unwrap_err()
|
||||||
|
.unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::InvalidSeeds)
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue