Skip to content

Commit 0f551d4

Browse files
Decode token instructions (solana-labs#11281)
* Token->SplToken * Add spl_token instruction parsing * Rebase on master * Gracefully fail key len mismatches
1 parent 1733f6c commit 0f551d4

File tree

7 files changed

+906
-20
lines changed

7 files changed

+906
-20
lines changed

Cargo.lock

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

account-decoder/src/parse_account_data.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ lazy_static! {
1616
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
1717
let mut m = HashMap::new();
1818
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
19-
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::Token);
19+
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
2020
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
2121
m
2222
};
@@ -41,7 +41,7 @@ pub enum ParseAccountError {
4141
#[serde(rename_all = "camelCase")]
4242
pub enum ParsableAccount {
4343
Nonce,
44-
Token,
44+
SplToken,
4545
Vote,
4646
}
4747

@@ -51,7 +51,7 @@ pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, Par
5151
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
5252
let parsed_json = match program_name {
5353
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
54-
ParsableAccount::Token => serde_json::to_value(parse_token(data)?)?,
54+
ParsableAccount::SplToken => serde_json::to_value(parse_token(data)?)?,
5555
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
5656
};
5757
Ok(json!({

account-decoder/src/parse_token.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
1616
let mut data = data.to_vec();
1717
if data.len() == size_of::<Account>() {
1818
let account: Account = *State::unpack(&mut data)
19-
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
19+
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
2020
Ok(TokenAccountType::Account(UiTokenAccount {
2121
mint: account.mint.to_string(),
2222
owner: account.owner.to_string(),
@@ -31,7 +31,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
3131
}))
3232
} else if data.len() == size_of::<Mint>() {
3333
let mint: Mint = *State::unpack(&mut data)
34-
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
34+
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
3535
Ok(TokenAccountType::Mint(UiMint {
3636
owner: match mint.owner {
3737
COption::Some(pubkey) => Some(pubkey.to_string()),
@@ -42,7 +42,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
4242
}))
4343
} else if data.len() == size_of::<Multisig>() {
4444
let multisig: Multisig = *State::unpack(&mut data)
45-
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
45+
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
4646
Ok(TokenAccountType::Multisig(UiMultisig {
4747
num_required_signers: multisig.m,
4848
num_valid_signers: multisig.n,
@@ -61,7 +61,7 @@ pub fn parse_token(data: &[u8]) -> Result<TokenAccountType, ParseAccountError> {
6161
}))
6262
} else {
6363
Err(ParseAccountError::AccountNotParsable(
64-
ParsableAccount::Token,
64+
ParsableAccount::SplToken,
6565
))
6666
}
6767
}

transaction-status/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ bincode = "1.3.1"
1313
bs58 = "0.3.1"
1414
Inflector = "0.11.4"
1515
lazy_static = "1.4.0"
16+
solana-account-decoder = { path = "../account-decoder", version = "1.3.0" }
1617
solana-sdk = { path = "../sdk", version = "1.3.0" }
1718
solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
1819
solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
1920
spl-memo-v1-0 = { package = "spl-memo", version = "1.0.4", features = ["skip-no-mangle"] }
21+
spl-token-v1-0 = { package = "spl-token", version = "1.0.2", features = ["skip-no-mangle"] }
2022
serde = "1.0.112"
2123
serde_derive = "1.0.103"
2224
serde_json = "1.0.56"
25+
thiserror = "1.0"
2326

2427
[package.metadata.docs.rs]
2528
targets = ["x86_64-unknown-linux-gnu"]

transaction-status/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate serde_derive;
55

66
pub mod parse_accounts;
77
pub mod parse_instruction;
8+
pub mod parse_token;
89

910
use crate::{parse_accounts::parse_accounts, parse_instruction::parse};
1011
use serde_json::Value;
@@ -220,7 +221,11 @@ impl EncodedTransaction {
220221
.map(|instruction| {
221222
let program_id =
222223
instruction.program_id(&transaction.message.account_keys);
223-
if let Some(parsed_instruction) = parse(program_id, instruction) {
224+
if let Ok(parsed_instruction) = parse(
225+
program_id,
226+
instruction,
227+
&transaction.message.account_keys,
228+
) {
224229
UiInstruction::Parsed(parsed_instruction)
225230
} else {
226231
UiInstruction::Compiled(instruction.into())

transaction-status/src/parse_instruction.rs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,60 @@
1+
use crate::parse_token::parse_token;
12
use inflector::Inflector;
23
use serde_json::{json, Value};
4+
use solana_account_decoder::parse_token::spl_token_id_v1_0;
35
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
46
use std::{
57
collections::HashMap,
68
str::{from_utf8, FromStr},
79
};
10+
use thiserror::Error;
811

912
lazy_static! {
1013
static ref MEMO_PROGRAM_ID: Pubkey =
1114
Pubkey::from_str(&spl_memo_v1_0::id().to_string()).unwrap();
15+
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v1_0();
1216
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
1317
let mut m = HashMap::new();
1418
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
19+
m.insert(*TOKEN_PROGRAM_ID, ParsableProgram::SplToken);
1520
m
1621
};
1722
}
1823

19-
#[derive(Debug, Serialize, Deserialize)]
24+
#[derive(Error, Debug, PartialEq)]
25+
pub enum ParseInstructionError {
26+
#[error("{0:?} instruction not parsable")]
27+
InstructionNotParsable(ParsableProgram),
28+
29+
#[error("{0:?} instruction key mismatch")]
30+
InstructionKeyMismatch(ParsableProgram),
31+
32+
#[error("Program not parsable")]
33+
ProgramNotParsable,
34+
}
35+
36+
#[derive(Debug, Serialize, Deserialize, PartialEq)]
2037
#[serde(rename_all = "camelCase")]
21-
enum ParsableProgram {
38+
pub enum ParsableProgram {
2239
SplMemo,
40+
SplToken,
2341
}
2442

25-
pub fn parse(program_id: &Pubkey, instruction: &CompiledInstruction) -> Option<Value> {
26-
PARSABLE_PROGRAM_IDS.get(program_id).map(|program_name| {
27-
let parsed_json = match program_name {
28-
ParsableProgram::SplMemo => parse_memo(instruction),
29-
};
30-
json!({ format!("{:?}", program_name).to_kebab_case(): parsed_json })
31-
})
43+
pub fn parse(
44+
program_id: &Pubkey,
45+
instruction: &CompiledInstruction,
46+
account_keys: &[Pubkey],
47+
) -> Result<Value, ParseInstructionError> {
48+
let program_name = PARSABLE_PROGRAM_IDS
49+
.get(program_id)
50+
.ok_or_else(|| ParseInstructionError::ProgramNotParsable)?;
51+
let parsed_json = match program_name {
52+
ParsableProgram::SplMemo => parse_memo(instruction),
53+
ParsableProgram::SplToken => parse_token(instruction, account_keys)?,
54+
};
55+
Ok(json!({
56+
format!("{:?}", program_name).to_kebab_case(): parsed_json
57+
}))
3258
}
3359

3460
fn parse_memo(instruction: &CompiledInstruction) -> Value {
@@ -50,11 +76,14 @@ mod test {
5076
"spl-memo": "🦖"
5177
});
5278
assert_eq!(
53-
parse(&MEMO_PROGRAM_ID, &memo_instruction),
54-
Some(expected_json)
79+
parse(&MEMO_PROGRAM_ID, &memo_instruction, &[]).unwrap(),
80+
expected_json
5581
);
5682

5783
let non_parsable_program_id = Pubkey::new(&[1; 32]);
58-
assert_eq!(parse(&non_parsable_program_id, &memo_instruction), None);
84+
assert_eq!(
85+
parse(&non_parsable_program_id, &memo_instruction, &[]).unwrap_err(),
86+
ParseInstructionError::ProgramNotParsable
87+
);
5988
}
6089
}

0 commit comments

Comments
 (0)