Add token account decoding (#11136)

* Add token decoding

* Bump versions
This commit is contained in:
Tyera Eulberg 2020-07-24 17:45:21 -06:00 committed by GitHub
parent eac423f92c
commit 32fea0496e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 12 deletions

70
Cargo.lock generated
View File

@ -357,6 +357,24 @@ dependencies = [
"rustc_version", "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.17",
"quote 1.0.6",
"serde",
"serde_json",
"syn 1.0.27",
"tempfile",
"toml",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.49" version = "1.0.49"
@ -1101,6 +1119,15 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.13" version = "0.1.13"
@ -2470,9 +2497,9 @@ checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
version = "0.5.2" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" checksum = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24"
dependencies = [ dependencies = [
"winapi 0.3.8", "winapi 0.3.8",
] ]
@ -2912,9 +2939,11 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"solana-sdk 1.2.13",
"solana-sdk 1.3.0", "solana-sdk 1.3.0",
"solana-vote-program", "solana-vote-program",
"spl-memo", "spl-memo",
"spl-token",
"thiserror", "thiserror",
] ]
@ -3812,9 +3841,9 @@ dependencies = [
[[package]] [[package]]
name = "solana-sdk" name = "solana-sdk"
version = "1.2.10" version = "1.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3869d4a4784d10fc0db91fc666a7ccb916fd47e74636e37c328df5a3b7f8157f" checksum = "cae779f912f1cbe2fe91df656ecc5ccdda9dd3378c0934f4390b7ba33501b3dc"
dependencies = [ dependencies = [
"bincode", "bincode",
"bs58", "bs58",
@ -3826,11 +3855,13 @@ dependencies = [
"num-derive 0.3.0", "num-derive 0.3.0",
"num-traits", "num-traits",
"pbkdf2", "pbkdf2",
"rustc_version",
"rustversion",
"serde", "serde",
"serde_bytes", "serde_bytes",
"serde_derive", "serde_derive",
"sha2", "sha2",
"solana-sdk-macro 1.2.10", "solana-sdk-macro 1.2.13",
"thiserror", "thiserror",
] ]
@ -3873,13 +3904,14 @@ dependencies = [
[[package]] [[package]]
name = "solana-sdk-macro" name = "solana-sdk-macro"
version = "1.2.10" version = "1.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fba494b48ee7d1b1c9cfd2df292d9dad69ccfd1f2b107eb030e29601cc4103" checksum = "8924af69f2685287988a530014c3268d78d6ce3fe4a1a61f87537c545afa8427"
dependencies = [ dependencies = [
"bs58", "bs58",
"proc-macro2 1.0.17", "proc-macro2 1.0.17",
"quote 1.0.6", "quote 1.0.6",
"rustversion",
"syn 1.0.27", "syn 1.0.27",
] ]
@ -4203,11 +4235,25 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "spl-memo" name = "spl-memo"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "451acd972c0ed2d114c42f8fffdd9a6007840e51fe7791f250d183e72c8ff7b5"
dependencies = [
"solana-sdk 1.2.13",
]
[[package]]
name = "spl-token"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d28cb8e0db4c8d578cde21a416af3e376ce4c3eeb6f5bcd7025fdf8980a3ce8" checksum = "a21aa023866f97c2644f932c2562d3c20e23a817e8abb0702625e0601cf9af80"
dependencies = [ dependencies = [
"solana-sdk 1.2.10", "cbindgen",
"num-derive 0.2.5",
"num-traits",
"remove_dir_all",
"solana-sdk 1.2.13",
"thiserror",
] ]
[[package]] [[package]]
@ -4797,6 +4843,12 @@ dependencies = [
"smallvec 1.4.0", "smallvec 1.4.0",
] ]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.7" version = "0.1.7"

View File

@ -15,7 +15,9 @@ Inflector = "0.11.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.3.0" } solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" } solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
spl-memo = "1.0.2" 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 = "1.0.112"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.56" serde_json = "1.0.56"

View File

@ -5,6 +5,7 @@ extern crate serde_derive;
pub mod parse_account_data; pub mod parse_account_data;
pub mod parse_nonce; pub mod parse_nonce;
pub mod parse_token;
pub mod parse_vote; pub mod parse_vote;
use crate::parse_account_data::parse_account_data; 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 inflector::Inflector;
use serde_json::{json, Value}; use serde_json::{json, Value};
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program}; use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
@ -8,11 +8,13 @@ use thiserror::Error;
lazy_static! { lazy_static! {
static ref SYSTEM_PROGRAM_ID: Pubkey = static ref SYSTEM_PROGRAM_ID: Pubkey =
Pubkey::from_str(&system_program::id().to_string()).unwrap(); 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 = static ref VOTE_PROGRAM_ID: Pubkey =
Pubkey::from_str(&solana_vote_program::id().to_string()).unwrap(); Pubkey::from_str(&solana_vote_program::id().to_string()).unwrap();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = { pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new(); let mut m = HashMap::new();
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce); m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::Token);
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote); m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m m
}; };
@ -20,6 +22,9 @@ lazy_static! {
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ParseAccountError { pub enum ParseAccountError {
#[error("{0:?} account not parsable")]
AccountNotParsable(ParsableAccount),
#[error("Program not parsable")] #[error("Program not parsable")]
ProgramNotParsable, ProgramNotParsable,
@ -34,6 +39,7 @@ pub enum ParseAccountError {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum ParsableAccount { pub enum ParsableAccount {
Nonce, Nonce,
Token,
Vote, Vote,
} }
@ -43,6 +49,7 @@ pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, Par
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?; .ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let parsed_json = match program_name { let parsed_json = match program_name {
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?, 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)?)?, ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
}; };
Ok(json!({ 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

@ -16,7 +16,7 @@ lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.3.0" } solana-sdk = { path = "../sdk", version = "1.3.0" }
solana-stake-program = { path = "../programs/stake", version = "1.3.0" } solana-stake-program = { path = "../programs/stake", version = "1.3.0" }
solana-vote-program = { path = "../programs/vote", version = "1.3.0" } solana-vote-program = { path = "../programs/vote", version = "1.3.0" }
spl-memo = "1.0.2" spl-memo = { version = "1.0.4", features = ["skip-no-mangle"] }
serde = "1.0.112" serde = "1.0.112"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.56" serde_json = "1.0.56"