Add token account decoding (bp #11136) (#11202)

* Add token account decoding (#11136)

* Add token decoding

* Bump versions

(cherry picked from commit 32fea0496e)

# Conflicts:
#	Cargo.lock
#	account-decoder/Cargo.toml
#	transaction-status/Cargo.toml

* Fix conflicts

Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
This commit is contained in:
mergify[bot]
2020-07-25 01:37:07 +00:00
committed by GitHub
parent dfa27b04d7
commit 05ef21cd3b
6 changed files with 251 additions and 40 deletions

102
Cargo.lock generated
View File

@ -412,6 +412,24 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "cbindgen"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "104ca409bbff8293739438c71820a2606111b5f8f81835536dc673dfd807369e"
dependencies = [
"clap",
"heck",
"log 0.4.8",
"proc-macro2 1.0.18",
"quote 1.0.7",
"serde",
"serde_json",
"syn 1.0.33",
"tempfile",
"toml",
]
[[package]]
name = "cc"
version = "1.0.49"
@ -3150,9 +3168,9 @@ checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
checksum = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24"
dependencies = [
"winapi 0.3.8",
]
@ -3714,8 +3732,10 @@ dependencies = [
"serde_derive",
"serde_json",
"solana-sdk 1.2.13",
"solana-sdk 1.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"solana-vote-program",
"spl-memo",
"spl-token",
"thiserror",
]
@ -4633,30 +4653,6 @@ dependencies = [
"serde",
]
[[package]]
name = "solana-sdk"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b628fa500e0b83df3e96f7cc21dc998d8841a994f1c2109475273e6448afd4"
dependencies = [
"bincode",
"bs58 0.3.1",
"bv",
"hex 0.4.2",
"hmac",
"itertools 0.9.0",
"log 0.4.8",
"num-derive 0.3.0",
"num-traits",
"pbkdf2",
"serde",
"serde_bytes",
"serde_derive",
"sha2",
"solana-sdk-macro 1.2.4",
"thiserror",
]
[[package]]
name = "solana-sdk"
version = "1.2.13"
@ -4694,26 +4690,47 @@ dependencies = [
]
[[package]]
name = "solana-sdk-bpf-test"
version = "1.2.4"
name = "solana-sdk"
version = "1.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1e668937b3fc2ecb13ad0285f318b0f81ba06c1b923d4cbc4d9868fb09d39d8"
checksum = "cae779f912f1cbe2fe91df656ecc5ccdda9dd3378c0934f4390b7ba33501b3dc"
dependencies = [
"bincode",
"bs58 0.3.1",
"bv",
"hex 0.4.2",
"hmac",
"itertools 0.9.0",
"log 0.4.8",
"num-derive 0.3.0",
"num-traits",
"pbkdf2",
"rustc_version",
"rustversion",
"serde",
"serde_bytes",
"serde_derive",
"sha2",
"solana-sdk-macro 1.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror",
]
[[package]]
name = "solana-sdk-macro"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5f311e7735323eb0ad348c68170c2503a2c56cfa1a261646d8182b373fa670"
version = "1.2.13"
dependencies = [
"bs58 0.3.1",
"proc-macro2 1.0.18",
"quote 1.0.7",
"rustversion",
"syn 1.0.33",
]
[[package]]
name = "solana-sdk-macro"
version = "1.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8924af69f2685287988a530014c3268d78d6ce3fe4a1a61f87537c545afa8427"
dependencies = [
"bs58 0.3.1",
"proc-macro2 1.0.18",
@ -5351,12 +5368,25 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spl-memo"
version = "1.0.1"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ebc6a6d50b55cbe7c84c7f6cd0259f4f82a169eb49ef65ca62b9c69a76a1f"
checksum = "451acd972c0ed2d114c42f8fffdd9a6007840e51fe7791f250d183e72c8ff7b5"
dependencies = [
"solana-sdk 1.2.4",
"solana-sdk-bpf-test",
"solana-sdk 1.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "spl-token"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21aa023866f97c2644f932c2562d3c20e23a817e8abb0702625e0601cf9af80"
dependencies = [
"cbindgen",
"num-derive 0.2.5",
"num-traits",
"remove_dir_all",
"solana-sdk 1.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror",
]
[[package]]

View File

@ -13,9 +13,11 @@ bincode = "1.2.1"
bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.2.13" }
solana-sdk = { path = "../sdk", version = "1.2.13" }
solana-vote-program = { path = "../programs/vote", version = "1.2.13" }
spl-memo = "1.0.1"
spl-memo = { version = "1.0.4", features = ["skip-no-mangle"] }
spl-sdk = { package = "solana-sdk", version = "=1.2.13", default-features = false }
spl-token = { version = "1.0.2", features = ["skip-no-mangle"] }
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"

View File

@ -5,6 +5,7 @@ extern crate serde_derive;
pub mod parse_account_data;
pub mod parse_nonce;
pub mod parse_token;
pub mod parse_vote;
use crate::parse_account_data::parse_account_data;

View File

@ -1,4 +1,4 @@
use crate::{parse_nonce::parse_nonce, parse_vote::parse_vote};
use crate::{parse_nonce::parse_nonce, parse_token::parse_token, parse_vote::parse_vote};
use inflector::Inflector;
use serde_json::{json, Value};
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
@ -8,11 +8,13 @@ use thiserror::Error;
lazy_static! {
static ref SYSTEM_PROGRAM_ID: Pubkey =
Pubkey::from_str(&system_program::id().to_string()).unwrap();
static ref TOKEN_PROGRAM_ID: Pubkey = Pubkey::from_str(&spl_token::id().to_string()).unwrap();
static ref VOTE_PROGRAM_ID: Pubkey =
Pubkey::from_str(&solana_vote_program::id().to_string()).unwrap();
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(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m
};
@ -20,6 +22,9 @@ lazy_static! {
#[derive(Error, Debug)]
pub enum ParseAccountError {
#[error("{0:?} account not parsable")]
AccountNotParsable(ParsableAccount),
#[error("Program not parsable")]
ProgramNotParsable,
@ -34,6 +39,7 @@ pub enum ParseAccountError {
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
Nonce,
Token,
Vote,
}
@ -43,6 +49,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::Vote => serde_json::to_value(parse_vote(data)?)?,
};
Ok(json!({

View File

@ -0,0 +1,171 @@
use crate::parse_account_data::{ParsableAccount, ParseAccountError};
use spl_sdk::pubkey::Pubkey;
use spl_token::{
option::COption,
state::{Account, Mint, Multisig, State},
};
use std::mem::size_of;
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))?;
Ok(TokenAccountType::Account(UiTokenAccount {
mint: account.mint.to_string(),
owner: account.owner.to_string(),
amount: account.amount,
delegate: match account.delegate {
COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None,
},
is_initialized: account.is_initialized,
is_native: account.is_native,
delegated_amount: account.delegated_amount,
}))
} else if data.len() == size_of::<Mint>() {
let mint: Mint = *State::unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
Ok(TokenAccountType::Mint(UiMint {
owner: match mint.owner {
COption::Some(pubkey) => Some(pubkey.to_string()),
COption::None => None,
},
decimals: mint.decimals,
is_initialized: mint.is_initialized,
}))
} else if data.len() == size_of::<Multisig>() {
let multisig: Multisig = *State::unpack(&mut data)
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::Token))?;
Ok(TokenAccountType::Multisig(UiMultisig {
num_required_signers: multisig.m,
num_valid_signers: multisig.n,
is_initialized: multisig.is_initialized,
signers: multisig
.signers
.iter()
.filter_map(|pubkey| {
if pubkey != &Pubkey::default() {
Some(pubkey.to_string())
} else {
None
}
})
.collect(),
}))
} else {
Err(ParseAccountError::AccountNotParsable(
ParsableAccount::Token,
))
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum TokenAccountType {
Account(UiTokenAccount),
Mint(UiMint),
Multisig(UiMultisig),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiTokenAccount {
pub mint: String,
pub owner: String,
pub amount: u64,
pub delegate: Option<String>,
pub is_initialized: bool,
pub is_native: bool,
pub delegated_amount: u64,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiMint {
pub owner: Option<String>,
pub decimals: u8,
pub is_initialized: bool,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiMultisig {
pub num_required_signers: u8,
pub num_valid_signers: u8,
pub is_initialized: bool,
pub signers: Vec<String>,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_token() {
let mint_pubkey = Pubkey::new(&[2; 32]);
let owner_pubkey = Pubkey::new(&[3; 32]);
let mut account_data = [0; size_of::<Account>()];
let mut account: &mut Account = State::unpack_unchecked(&mut account_data).unwrap();
account.mint = mint_pubkey;
account.owner = owner_pubkey;
account.amount = 42;
account.is_initialized = true;
assert_eq!(
parse_token(&account_data).unwrap(),
TokenAccountType::Account(UiTokenAccount {
mint: mint_pubkey.to_string(),
owner: owner_pubkey.to_string(),
amount: 42,
delegate: None,
is_initialized: true,
is_native: false,
delegated_amount: 0,
}),
);
let mut mint_data = [0; size_of::<Mint>()];
let mut mint: &mut Mint = State::unpack_unchecked(&mut mint_data).unwrap();
mint.owner = COption::Some(owner_pubkey);
mint.decimals = 3;
mint.is_initialized = true;
assert_eq!(
parse_token(&mint_data).unwrap(),
TokenAccountType::Mint(UiMint {
owner: Some(owner_pubkey.to_string()),
decimals: 3,
is_initialized: true,
}),
);
let signer1 = Pubkey::new(&[1; 32]);
let signer2 = Pubkey::new(&[2; 32]);
let signer3 = Pubkey::new(&[3; 32]);
let mut multisig_data = [0; size_of::<Multisig>()];
let mut multisig: &mut Multisig = State::unpack_unchecked(&mut multisig_data).unwrap();
let mut signers = [Pubkey::default(); 11];
signers[0] = signer1;
signers[1] = signer2;
signers[2] = signer3;
multisig.m = 2;
multisig.n = 3;
multisig.is_initialized = true;
multisig.signers = signers;
assert_eq!(
parse_token(&multisig_data).unwrap(),
TokenAccountType::Multisig(UiMultisig {
num_required_signers: 2,
num_valid_signers: 3,
is_initialized: true,
signers: vec![
signer1.to_string(),
signer2.to_string(),
signer3.to_string()
],
}),
);
let bad_data = vec![0; 4];
assert!(parse_token(&bad_data).is_err());
}
}

View File

@ -14,7 +14,7 @@ bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.2.13" }
spl-memo = "1.0.1"
spl-memo = { version = "1.0.4", features = ["skip-no-mangle"] }
serde = "1.0.110"
serde_derive = "1.0.103"
serde_json = "1.0.54"