Decode token instructions (#11281)

* Token->SplToken

* Add spl_token instruction parsing

* Rebase on master

* Gracefully fail key len mismatches
This commit is contained in:
Tyera Eulberg 2020-07-31 13:26:09 -06:00 committed by GitHub
parent 1733f6c9e9
commit 0f551d4f75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 908 additions and 22 deletions

3
Cargo.lock generated
View File

@ -4057,10 +4057,13 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"solana-account-decoder",
"solana-sdk 1.3.0",
"solana-stake-program",
"solana-vote-program",
"spl-memo",
"spl-token",
"thiserror",
]
[[package]]

View File

@ -16,7 +16,7 @@ lazy_static! {
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new();
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::Token);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m
};
@ -41,7 +41,7 @@ pub enum ParseAccountError {
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
Nonce,
Token,
SplToken,
Vote,
}
@ -51,7 +51,7 @@ pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, Par
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let parsed_json = match program_name {
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::Token => serde_json::to_value(parse_token(data)?)?,
ParsableAccount::SplToken => serde_json::to_value(parse_token(data)?)?,
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
};
Ok(json!({

View File

@ -16,7 +16,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
let mut data = data.to_vec();
if data.len() == size_of::<Account>() {
let account: Account = *State::unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Account(UiTokenAccount {
mint: account.mint.to_string(),
owner: account.owner.to_string(),
@ -31,7 +31,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
}))
} else if data.len() == size_of::<Mint>() {
let mint: Mint = *State::unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Mint(UiMint {
owner: match mint.owner {
COption::Some(pubkey) => Some(pubkey.to_string()),
@ -42,7 +42,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
}))
} else if data.len() == size_of::<Multisig>() {
let multisig: Multisig = *State::unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
Ok(TokenAccountType::Multisig(UiMultisig {
num_required_signers: multisig.m,
num_valid_signers: multisig.n,
@ -61,7 +61,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
}))
} else {
Err(ParseAccountError::AccountNotParsable(
ParsableAccount::Token,
ParsableAccount::SplToken,
))
}
}

View File

@ -13,13 +13,16 @@ bincode = "1.3.1"
bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
spl-memo-v1-0 = { package = "spl-memo", version = "1.0.4", features = ["skip-no-mangle"] }
spl-token-v1-0 = { package = "spl-token", version = "1.0.2", features = ["skip-no-mangle"] }
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"
thiserror = "1.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -5,6 +5,7 @@ extern crate serde_derive;
pub mod parse_accounts;
pub mod parse_instruction;
pub mod parse_token;
use crate::{parse_accounts::parse_accounts, parse_instruction::parse};
use serde_json::Value;
@ -220,7 +221,11 @@ impl EncodedTransaction {
.map(|instruction| {
let program_id =
instruction.program_id(&transaction.message.account_keys);
if let Some(parsed_instruction) = parse(program_id, instruction) {
if let Ok(parsed_instruction) = parse(
program_id,
instruction,
&transaction.message.account_keys,
) {
UiInstruction::Parsed(parsed_instruction)
} else {
UiInstruction::Compiled(instruction.into())

View File

@ -1,34 +1,60 @@
use crate::parse_token::parse_token;
use inflector::Inflector;
use serde_json::{json, Value};
use solana_account_decoder::parse_token::spl_token_id_v1_0;
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
use std::{
collections::HashMap,
str::{from_utf8, FromStr},
};
use thiserror::Error;
lazy_static! {
static ref MEMO_PROGRAM_ID: Pubkey =
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
let mut m = HashMap::new();
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken);
m
};
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum ParsableProgram {
SplMemo,
#[derive(Error, Debug, PartialEq)]
pub enum ParseInstructionError {
#[error("{0:?} instruction not parsable")]
InstructionNotParsable(ParsableProgram),
#[error("{0:?} instruction key mismatch")]
InstructionKeyMismatch(ParsableProgram),
#[error("Program not parsable")]
ProgramNotParsable,
}
pub fn parse(program_id: &Pubkey, instruction: &CompiledInstruction) -> Option<Value> {
PARSABLE_PROGRAM_IDS.get(program_id).map(|program_name| {
let parsed_json = match program_name {
ParsableProgram::SplMemo => parse_memo(instruction),
};
json!({ format!("{:?}", program_name).to_kebab_case(): parsed_json })
})
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum ParsableProgram {
SplMemo,
SplToken,
}
pub fn parse(
program_id: &Pubkey,
instruction: &CompiledInstruction,
account_keys: &[Pubkey],
) -> Result<Value, ParseInstructionError> {
let program_name = PARSABLE_PROGRAM_IDS
.get(program_id)
.ok_or_else(|| ParseInstructionError::ProgramNotParsable)?;
let parsed_json = match program_name {
ParsableProgram::SplMemo => parse_memo(instruction),
ParsableProgram::SplToken => parse_token(instruction, account_keys)?,
};
Ok(json!({
format!("{:?}", program_name).to_kebab_case(): parsed_json
}))
}
fn parse_memo(instruction: &CompiledInstruction) -> Value {
@ -50,11 +76,14 @@ mod test {
"spl-memo": "🦖"
});
assert_eq!(
parse(&MEMO_PROGRAM_ID, &memo_instruction),
Some(expected_json)
parse(&MEMO_PROGRAM_ID, &memo_instruction, &[]).unwrap(),
expected_json
);
let non_parsable_program_id = Pubkey::new(&[1; 32]);
assert_eq!(parse(&non_parsable_program_id, &memo_instruction), None);
assert_eq!(
parse(&non_parsable_program_id, &memo_instruction, &[]).unwrap_err(),
ParseInstructionError::ProgramNotParsable
);
}
}

View File

@ -0,0 +1,846 @@
use crate::parse_instruction::{ParsableProgram, ParseInstructionError};
use serde_json::{json, Map, Value};
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
use spl_token_v1_0::instruction::TokenInstruction;
pub fn parse_token(
instruction: &CompiledInstruction,
account_keys: &[Pubkey],
) -> Result<Value, ParseInstructionError> {
let token_instruction = TokenInstruction::unpack(&instruction.data)
.map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken))?;
if instruction.accounts.len() > account_keys.len() {
// Runtime should prevent this from ever happening
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
match token_instruction {
TokenInstruction::InitializeMint { amount, decimals } => {
if instruction.accounts.len() < 2 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "initializeMint",
"mint": account_keys[instruction.accounts[0] as usize].to_string(),
"amount": amount,
"decimals":decimals,
});
let map = value.as_object_mut().unwrap();
if amount == 0 {
map.insert(
"owner".to_string(),
json!(account_keys[instruction.accounts[1] as usize].to_string()),
);
} else {
map.insert(
"account".to_string(),
json!(account_keys[instruction.accounts[1] as usize].to_string()),
);
if let Some(i) = instruction.accounts.get(2) {
map.insert(
"owner".to_string(),
json!(account_keys[*i as usize].to_string()),
);
}
}
Ok(value)
}
TokenInstruction::InitializeAccount => {
if instruction.accounts.len() < 3 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
Ok(json!({
"type": "initializeAccount",
"account": account_keys[instruction.accounts[0] as usize].to_string(),
"mint": account_keys[instruction.accounts[1] as usize].to_string(),
"owner": account_keys[instruction.accounts[2] as usize].to_string(),
}))
}
TokenInstruction::InitializeMultisig { m } => {
if instruction.accounts.len() < 2 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut signers: Vec<String> = vec![];
for i in instruction.accounts[1..].iter() {
signers.push(account_keys[*i as usize].to_string());
}
Ok(json!({
"type": "initializeMultisig",
"multisig": account_keys[instruction.accounts[0] as usize].to_string(),
"signers": signers,
"m": m,
}))
}
TokenInstruction::Transfer { amount } => {
if instruction.accounts.len() < 3 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "transfer",
"source": account_keys[instruction.accounts[0] as usize].to_string(),
"destination": account_keys[instruction.accounts[1] as usize].to_string(),
"amount": amount,
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
2,
account_keys,
&instruction.accounts,
"authority",
"multisigAuthority",
);
Ok(value)
}
TokenInstruction::Approve { amount } => {
if instruction.accounts.len() < 3 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "approve",
"source": account_keys[instruction.accounts[0] as usize].to_string(),
"delegate": account_keys[instruction.accounts[1] as usize].to_string(),
"amount": amount,
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
2,
account_keys,
&instruction.accounts,
"owner",
"multisigOwner",
);
Ok(value)
}
TokenInstruction::Revoke => {
if instruction.accounts.len() < 2 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "revoke",
"source": account_keys[instruction.accounts[0] as usize].to_string(),
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
1,
account_keys,
&instruction.accounts,
"owner",
"multisigOwner",
);
Ok(value)
}
TokenInstruction::SetOwner => {
if instruction.accounts.len() < 3 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "setOwner",
"owned": account_keys[instruction.accounts[0] as usize].to_string(),
"newOwner": account_keys[instruction.accounts[1] as usize].to_string(),
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
2,
account_keys,
&instruction.accounts,
"owner",
"multisigOwner",
);
Ok(value)
}
TokenInstruction::MintTo { amount } => {
if instruction.accounts.len() < 3 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "mintTo",
"mint": account_keys[instruction.accounts[0] as usize].to_string(),
"account": account_keys[instruction.accounts[1] as usize].to_string(),
"amount": amount,
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
2,
account_keys,
&instruction.accounts,
"owner",
"multisigOwner",
);
Ok(value)
}
TokenInstruction::Burn { amount } => {
if instruction.accounts.len() < 2 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "burn",
"account": account_keys[instruction.accounts[0] as usize].to_string(),
"amount": amount,
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
1,
account_keys,
&instruction.accounts,
"authority",
"multisigAuthority",
);
Ok(value)
}
TokenInstruction::CloseAccount => {
if instruction.accounts.len() < 3 {
return Err(ParseInstructionError::InstructionKeyMismatch(
ParsableProgram::SplToken,
));
}
let mut value = json!({
"type": "closeAccount",
"account": account_keys[instruction.accounts[0] as usize].to_string(),
"destination": account_keys[instruction.accounts[1] as usize].to_string(),
});
let mut map = value.as_object_mut().unwrap();
parse_signers(
&mut map,
2,
account_keys,
&instruction.accounts,
"owner",
"multisigOwner",
);
Ok(value)
}
}
}
fn parse_signers(
map: &mut Map<String, Value>,
last_nonsigner_index: usize,
account_keys: &[Pubkey],
accounts: &[u8],
owner_field_name: &str,
multisig_field_name: &str,
) {
if accounts.len() > last_nonsigner_index + 1 {
let mut signers: Vec<String> = vec![];
for i in accounts[last_nonsigner_index + 1..].iter() {
signers.push(account_keys[*i as usize].to_string());
}
map.insert(
multisig_field_name.to_string(),
json!(account_keys[accounts[last_nonsigner_index] as usize].to_string()),
);
map.insert("signers".to_string(), json!(signers));
} else {
map.insert(
owner_field_name.to_string(),
json!(account_keys[accounts[last_nonsigner_index] as usize].to_string()),
);
}
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::instruction::CompiledInstruction;
use spl_token_v1_0::{
instruction::*,
solana_sdk::{
instruction::CompiledInstruction as SplTokenCompiledInstruction, message::Message,
pubkey::Pubkey as SplTokenPubkey,
},
};
use std::str::FromStr;
fn convert_pubkey(pubkey: Pubkey) -> SplTokenPubkey {
SplTokenPubkey::from_str(&pubkey.to_string()).unwrap()
}
fn convert_compiled_instruction(
instruction: &SplTokenCompiledInstruction,
) -> CompiledInstruction {
CompiledInstruction {
program_id_index: instruction.program_id_index,
accounts: instruction.accounts.clone(),
data: instruction.data.clone(),
}
}
#[test]
fn test_parse_token() {
let mut keys: Vec<Pubkey> = vec![];
for _ in 0..10 {
keys.push(Pubkey::new_rand());
}
// Test InitializeMint variations
let initialize_mint_ix = initialize_mint(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
Some(&convert_pubkey(keys[1])),
Some(&convert_pubkey(keys[2])),
42,
2,
)
.unwrap();
let message = Message::new(&[initialize_mint_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "initializeMint",
"mint": keys[0].to_string(),
"amount": 42,
"decimals": 2,
"account": keys[1].to_string(),
"owner": keys[2].to_string(),
})
);
let initialize_mint_ix = initialize_mint(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
Some(&convert_pubkey(keys[1])),
None,
42,
2,
)
.unwrap();
let message = Message::new(&[initialize_mint_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "initializeMint",
"mint": keys[0].to_string(),
"amount": 42,
"decimals": 2,
"account": keys[1].to_string(),
})
);
let initialize_mint_ix = initialize_mint(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
None,
Some(&convert_pubkey(keys[1])),
0,
2,
)
.unwrap();
let message = Message::new(&[initialize_mint_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "initializeMint",
"mint": keys[0].to_string(),
"amount": 0,
"decimals": 2,
"owner": keys[1].to_string(),
})
);
// Test InitializeAccount
let initialize_account_ix = initialize_account(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
)
.unwrap();
let message = Message::new(&[initialize_account_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "initializeAccount",
"account": keys[0].to_string(),
"mint": keys[1].to_string(),
"owner": keys[2].to_string(),
})
);
// Test InitializeMultisig
let initialize_multisig_ix = initialize_multisig(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
&[
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[3]),
],
2,
)
.unwrap();
let message = Message::new(&[initialize_multisig_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "initializeMultisig",
"multisig": keys[0].to_string(),
"m": 2,
"signers": keys[1..4].iter().map(|key| key.to_string()).collect::<Vec<String>>(),
})
);
// Test Transfer, incl multisig
let transfer_ix = transfer(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[transfer_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "transfer",
"source": keys[1].to_string(),
"destination": keys[2].to_string(),
"authority": keys[0].to_string(),
"amount": 42,
})
);
let transfer_ix = transfer(
&spl_token_v1_0::id(),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[3]),
&convert_pubkey(keys[4]),
&[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
42,
)
.unwrap();
let message = Message::new(&[transfer_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "transfer",
"source": keys[2].to_string(),
"destination": keys[3].to_string(),
"multisigAuthority": keys[4].to_string(),
"signers": keys[0..2].iter().map(|key| key.to_string()).collect::<Vec<String>>(),
"amount": 42,
})
);
// Test Approve, incl multisig
let approve_ix = approve(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[approve_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "approve",
"source": keys[1].to_string(),
"delegate": keys[2].to_string(),
"owner": keys[0].to_string(),
"amount": 42,
})
);
let approve_ix = approve(
&spl_token_v1_0::id(),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[3]),
&convert_pubkey(keys[4]),
&[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
42,
)
.unwrap();
let message = Message::new(&[approve_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "approve",
"source": keys[2].to_string(),
"delegate": keys[3].to_string(),
"multisigOwner": keys[4].to_string(),
"signers": keys[0..2].iter().map(|key| key.to_string()).collect::<Vec<String>>(),
"amount": 42,
})
);
// Test Revoke
let revoke_ix = revoke(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[0]),
&[],
)
.unwrap();
let message = Message::new(&[revoke_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "revoke",
"source": keys[1].to_string(),
"owner": keys[0].to_string(),
})
);
// Test SetOwner
let set_owner_ix = set_owner(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
)
.unwrap();
let message = Message::new(&[set_owner_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "setOwner",
"owned": keys[1].to_string(),
"newOwner": keys[2].to_string(),
"owner": keys[0].to_string(),
})
);
// Test MintTo
let mint_to_ix = mint_to(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[mint_to_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "mintTo",
"mint": keys[1].to_string(),
"account": keys[2].to_string(),
"owner": keys[0].to_string(),
"amount": 42,
})
);
// Test Burn
let burn_ix = burn(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[burn_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "burn",
"account": keys[1].to_string(),
"authority": keys[0].to_string(),
"amount": 42,
})
);
// Test CloseAccount
let close_account_ix = close_account(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
)
.unwrap();
let message = Message::new(&[close_account_ix], None);
let compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert_eq!(
parse_token(&compiled_instruction, &keys).unwrap(),
json!({
"type": "closeAccount",
"account": keys[1].to_string(),
"destination": keys[2].to_string(),
"owner": keys[0].to_string(),
})
);
}
#[test]
fn test_token_ix_not_enough_keys() {
let mut keys: Vec<Pubkey> = vec![];
for _ in 0..10 {
keys.push(Pubkey::new_rand());
}
// Test InitializeMint variations
let initialize_mint_ix = initialize_mint(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
Some(&convert_pubkey(keys[1])),
Some(&convert_pubkey(keys[2])),
42,
2,
)
.unwrap();
let message = Message::new(&[initialize_mint_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 2].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
let initialize_mint_ix = initialize_mint(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
Some(&convert_pubkey(keys[1])),
None,
42,
2,
)
.unwrap();
let message = Message::new(&[initialize_mint_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
let initialize_mint_ix = initialize_mint(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
None,
Some(&convert_pubkey(keys[1])),
0,
2,
)
.unwrap();
let message = Message::new(&[initialize_mint_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test InitializeAccount
let initialize_account_ix = initialize_account(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
)
.unwrap();
let message = Message::new(&[initialize_account_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test InitializeMultisig
let initialize_multisig_ix = initialize_multisig(
&spl_token_v1_0::id(),
&convert_pubkey(keys[0]),
&[
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[3]),
],
2,
)
.unwrap();
let message = Message::new(&[initialize_multisig_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..3]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 3].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test Transfer, incl multisig
let transfer_ix = transfer(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[transfer_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
let transfer_ix = transfer(
&spl_token_v1_0::id(),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[3]),
&convert_pubkey(keys[4]),
&[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
42,
)
.unwrap();
let message = Message::new(&[transfer_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..4]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 3].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test Approve, incl multisig
let approve_ix = approve(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[approve_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
let approve_ix = approve(
&spl_token_v1_0::id(),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[3]),
&convert_pubkey(keys[4]),
&[&convert_pubkey(keys[0]), &convert_pubkey(keys[1])],
42,
)
.unwrap();
let message = Message::new(&[approve_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..4]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 3].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test Revoke
let revoke_ix = revoke(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[0]),
&[],
)
.unwrap();
let message = Message::new(&[revoke_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test SetOwner
let set_owner_ix = set_owner(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
)
.unwrap();
let message = Message::new(&[set_owner_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test MintTo
let mint_to_ix = mint_to(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[mint_to_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test Burn
let burn_ix = burn(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[0]),
&[],
42,
)
.unwrap();
let message = Message::new(&[burn_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..1]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
// Test CloseAccount
let close_account_ix = close_account(
&spl_token_v1_0::id(),
&convert_pubkey(keys[1]),
&convert_pubkey(keys[2]),
&convert_pubkey(keys[0]),
&[],
)
.unwrap();
let message = Message::new(&[close_account_ix], None);
let mut compiled_instruction = convert_compiled_instruction(&message.instructions[0]);
assert!(parse_token(&compiled_instruction, &keys[0..2]).is_err());
compiled_instruction.accounts =
compiled_instruction.accounts[0..compiled_instruction.accounts.len() - 1].to_vec();
assert!(parse_token(&compiled_instruction, &keys).is_err());
}
}