Add token account decoding (#11136)
* Add token decoding * Bump versions
This commit is contained in:
parent
eac423f92c
commit
32fea0496e
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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!({
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue