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

View File

@ -15,7 +15,9 @@ Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", 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_derive = "1.0.103"
serde_json = "1.0.56"

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

@ -16,7 +16,7 @@ lazy_static = "1.4.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 = "1.0.2"
spl-memo = { version = "1.0.4", features = ["skip-no-mangle"] }
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.56"