-
Notifications
You must be signed in to change notification settings - Fork 149
Example: program that creates/closes a SPL Token account that it owns #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,15 @@ | ||
use solana_program::{ | ||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, info, pubkey::Pubkey, | ||
account_info::{next_account_info, AccountInfo}, | ||
entrypoint, | ||
entrypoint::ProgramResult, | ||
info, | ||
program::{invoke, invoke_signed}, | ||
program_error::ProgramError, | ||
program_pack::Pack, | ||
pubkey::Pubkey, | ||
rent::Rent, | ||
system_instruction, | ||
sysvar::Sysvar, | ||
}; | ||
|
||
entrypoint!(process_instruction); | ||
|
@@ -14,39 +24,184 @@ fn process_instruction( | |
accounts.len(), | ||
instruction_data | ||
)); | ||
Ok(()) | ||
|
||
let account_info_iter = &mut accounts.iter(); | ||
let program_token_info = next_account_info(account_info_iter)?; | ||
let (program_token_address, program_token_bump_seed) = | ||
Pubkey::find_program_address(&[br"program-token"], program_id); | ||
|
||
if program_token_address != *program_token_info.key { | ||
info!("Error: program token address derivation mismatch"); | ||
return Err(ProgramError::InvalidArgument); | ||
} | ||
|
||
let program_token_signer_seeds: &[&[_]] = &[br"program-token", &[program_token_bump_seed]]; | ||
|
||
match instruction_data.get(0) { | ||
Some(0) => { | ||
info!("Create program token account..."); | ||
let funder_info = next_account_info(account_info_iter)?; | ||
let 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 rent = &Rent::from_account_info(rent_sysvar_info)?; | ||
|
||
invoke_signed( | ||
&system_instruction::create_account( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i noticed you used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, very different. Sadly the names are close enough that I can see where confusion can arise!
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, and i presume you chose to use i'll try it myself, but it seems to me that i can also just call the then, i can use a totally different program_id for a whole different SPL token in this same program and be able to intake 2 SPL tokens: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and after creating them, this program can do invoke_signed on them and send them wherever they want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, that's right.
Alternatively you use the same program, but adding more seeds to
Here's an example to check out: https://github.com/solana-labs/solana-program-library/blob/487ad2d2d7cb0b8a58f02a156a659e6c8a74f354/associated-token-account/program/src/lib.rs#L24-L32 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you, this is very helpful There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is |
||
funder_info.key, | ||
program_token_info.key, | ||
1.max(rent.minimum_balance(spl_token::state::Account::get_packed_len())), | ||
spl_token::state::Account::get_packed_len() as u64, | ||
&spl_token::id(), | ||
), | ||
&[ | ||
funder_info.clone(), | ||
program_token_info.clone(), | ||
system_program_info.clone(), | ||
], | ||
&[&program_token_signer_seeds], | ||
)?; | ||
|
||
info!("Initializing program token account"); | ||
invoke( | ||
&spl_token::instruction::initialize_account( | ||
&spl_token::id(), | ||
program_token_info.key, | ||
mint_info.key, | ||
program_token_info.key, // token owner is also `program_token` address | ||
)?, | ||
&[ | ||
program_token_info.clone(), | ||
spl_token_program_info.clone(), | ||
rent_sysvar_info.clone(), | ||
mint_info.clone(), | ||
], | ||
)?; | ||
Ok(()) | ||
} | ||
Some(1) => { | ||
info!("Close program token account..."); | ||
let funder_info = next_account_info(account_info_iter)?; | ||
let spl_token_program_info = next_account_info(account_info_iter)?; | ||
|
||
invoke_signed( | ||
&spl_token::instruction::close_account( | ||
&spl_token::id(), | ||
program_token_info.key, | ||
funder_info.key, | ||
program_token_info.key, // token owner is also `program_token` address | ||
&[], | ||
) | ||
.expect("close_account"), | ||
&[ | ||
funder_info.clone(), | ||
spl_token_program_info.clone(), | ||
program_token_info.clone(), | ||
], | ||
&[&program_token_signer_seeds], | ||
) | ||
} | ||
_ => { | ||
info!("Error: Unsupported instruction"); | ||
Err(ProgramError::InvalidInstructionData) | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
#![cfg(feature = "test-bpf")] | ||
|
||
use super::*; | ||
use assert_matches::*; | ||
use solana_program::instruction::{AccountMeta, Instruction}; | ||
use solana_program::{ | ||
instruction::{AccountMeta, Instruction}, | ||
sysvar, | ||
}; | ||
use solana_program_test::*; | ||
use solana_sdk::{signature::Signer, transaction::Transaction}; | ||
|
||
fn program_test(program_id: Pubkey) -> ProgramTest { | ||
let mut pc = ProgramTest::new( | ||
"bpf_program_template", | ||
program_id, | ||
processor!(process_instruction), | ||
); | ||
|
||
// Add SPL Token program | ||
pc.add_program( | ||
"spl_token", | ||
mvines marked this conversation as resolved.
Show resolved
Hide resolved
|
||
spl_token::id(), | ||
Comment on lines
+134
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm still confused by this what is going back to my confusion of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
processor!(spl_token::processor::Processor::process), | ||
); | ||
|
||
pc | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_transaction() { | ||
async fn test_create_then_close() { | ||
let program_id = Pubkey::new_unique(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i was just interpreting it incorrectly, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the |
||
let (mut banks_client, payer, recent_blockhash) = program_test(program_id).start().await; | ||
|
||
let (mut banks_client, payer, recent_blockhash) = | ||
ProgramTest::new("bpf_program_template", program_id, processor!(process_instruction)) | ||
.start() | ||
.await; | ||
let program_token_address = | ||
Pubkey::find_program_address(&[br"program-token"], &program_id).0; | ||
|
||
// Create the program-owned token account | ||
let mut transaction = Transaction::new_with_payer( | ||
&[Instruction { | ||
program_id, | ||
accounts: vec![AccountMeta::new(payer.pubkey(), false)], | ||
data: vec![1, 2, 3], | ||
accounts: vec![ | ||
AccountMeta::new(program_token_address, false), | ||
AccountMeta::new(payer.pubkey(), true), | ||
AccountMeta::new_readonly(spl_token::native_mint::id(), 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], | ||
}], | ||
Some(&payer.pubkey()), | ||
); | ||
transaction.sign(&[&payer], recent_blockhash); | ||
assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); | ||
|
||
assert_matches!( | ||
banks_client.process_transaction(transaction).await, | ||
Ok(()) | ||
// Fetch the program-owned token account and confirm it now exists | ||
let program_token_account = banks_client | ||
.get_account(program_token_address) | ||
.await | ||
.expect("success") | ||
.expect("some account"); | ||
let program_token_account = | ||
spl_token::state::Account::unpack_from_slice(&program_token_account.data) | ||
.expect("unpack success"); | ||
assert_eq!(program_token_account.mint, spl_token::native_mint::id()); | ||
assert_eq!(program_token_account.owner, program_token_address); | ||
|
||
// Close the the program-owned token account | ||
let mut transaction = Transaction::new_with_payer( | ||
&[Instruction { | ||
program_id, | ||
accounts: vec![ | ||
AccountMeta::new(program_token_address, false), | ||
AccountMeta::new(payer.pubkey(), true), | ||
AccountMeta::new_readonly(spl_token::id(), false), | ||
], | ||
data: vec![1], | ||
}], | ||
Some(&payer.pubkey()), | ||
); | ||
transaction.sign(&[&payer], recent_blockhash); | ||
assert_matches!(banks_client.process_transaction(transaction).await, Ok(())); | ||
|
||
// Fetch the program-owned token account and confirm it no longer now exists | ||
assert_eq!( | ||
banks_client | ||
.get_account(program_token_address) | ||
.await | ||
.expect("success"), | ||
None | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the create instruction is currently unprivileged, meaning that anybody can grief you by creating an arbitrary token at the fixed program address. In the real world, some authority should be checked before creating the token account