2020-08-07 11:37:39 -06:00
|
|
|
use crate::{
|
|
|
|
parse_account_data::{ParsableAccount, ParseAccountError},
|
|
|
|
StringAmount,
|
|
|
|
};
|
2020-07-28 23:00:48 -06:00
|
|
|
use solana_sdk::pubkey::Pubkey;
|
|
|
|
use spl_token_v1_0::{
|
2020-07-24 17:45:21 -06:00
|
|
|
option::COption,
|
2020-07-30 14:52:28 -07:00
|
|
|
solana_sdk::pubkey::Pubkey as SplTokenPubkey,
|
2020-08-01 23:31:22 -07:00
|
|
|
state::{unpack, Account, Mint, Multisig},
|
2020-07-24 17:45:21 -06:00
|
|
|
};
|
2020-07-28 23:00:48 -06:00
|
|
|
use std::{mem::size_of, str::FromStr};
|
|
|
|
|
2020-08-05 00:48:09 -06:00
|
|
|
// A helper function to convert spl_token_v1_0::id() as spl_sdk::pubkey::Pubkey to
|
|
|
|
// solana_sdk::pubkey::Pubkey
|
2020-07-28 23:00:48 -06:00
|
|
|
pub fn spl_token_id_v1_0() -> Pubkey {
|
|
|
|
Pubkey::from_str(&spl_token_v1_0::id().to_string()).unwrap()
|
|
|
|
}
|
2020-07-24 17:45:21 -06:00
|
|
|
|
2020-08-05 00:48:09 -06:00
|
|
|
// A helper function to convert spl_token_v1_0::native_mint::id() as spl_sdk::pubkey::Pubkey to
|
|
|
|
// solana_sdk::pubkey::Pubkey
|
|
|
|
pub fn spl_token_v1_0_native_mint() -> Pubkey {
|
|
|
|
Pubkey::from_str(&spl_token_v1_0::native_mint::id().to_string()).unwrap()
|
|
|
|
}
|
|
|
|
|
2020-08-07 11:37:39 -06:00
|
|
|
pub fn parse_token(
|
|
|
|
data: &[u8],
|
|
|
|
mint_decimals: Option<u8>,
|
|
|
|
) -> Result<TokenAccountType, ParseAccountError> {
|
2020-07-24 17:45:21 -06:00
|
|
|
let mut data = data.to_vec();
|
|
|
|
if data.len() == size_of::<Account>() {
|
2020-08-01 23:31:22 -07:00
|
|
|
let account: Account = *unpack(&mut data)
|
2020-07-31 13:26:09 -06:00
|
|
|
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
2020-08-07 11:37:39 -06:00
|
|
|
let decimals = mint_decimals.ok_or_else(|| {
|
|
|
|
ParseAccountError::AdditionalDataMissing(
|
|
|
|
"no mint_decimals provided to parse spl-token account".to_string(),
|
|
|
|
)
|
|
|
|
})?;
|
|
|
|
let ui_token_amount = token_amount_to_ui_amount(account.amount, decimals);
|
2020-07-24 17:45:21 -06:00
|
|
|
Ok(TokenAccountType::Account(UiTokenAccount {
|
|
|
|
mint: account.mint.to_string(),
|
|
|
|
owner: account.owner.to_string(),
|
2020-08-07 11:37:39 -06:00
|
|
|
token_amount: ui_token_amount,
|
2020-07-24 17:45:21 -06:00
|
|
|
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>() {
|
2020-08-01 23:31:22 -07:00
|
|
|
let mint: Mint = *unpack(&mut data)
|
2020-07-31 13:26:09 -06:00
|
|
|
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
2020-07-24 17:45:21 -06:00
|
|
|
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>() {
|
2020-08-01 23:31:22 -07:00
|
|
|
let multisig: Multisig = *unpack(&mut data)
|
2020-07-31 13:26:09 -06:00
|
|
|
.map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
|
2020-07-24 17:45:21 -06:00
|
|
|
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| {
|
2020-07-30 14:52:28 -07:00
|
|
|
if pubkey != &SplTokenPubkey::default() {
|
2020-07-24 17:45:21 -06:00
|
|
|
Some(pubkey.to_string())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Err(ParseAccountError::AccountNotParsable(
|
2020-07-31 13:26:09 -06:00
|
|
|
ParsableAccount::SplToken,
|
2020-07-24 17:45:21 -06:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
2020-08-05 00:59:10 -06:00
|
|
|
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
2020-07-24 17:45:21 -06:00
|
|
|
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,
|
2020-08-07 11:37:39 -06:00
|
|
|
pub token_amount: UiTokenAmount,
|
2020-07-24 17:45:21 -06:00
|
|
|
pub delegate: Option<String>,
|
|
|
|
pub is_initialized: bool,
|
|
|
|
pub is_native: bool,
|
|
|
|
pub delegated_amount: u64,
|
|
|
|
}
|
|
|
|
|
2020-08-07 11:37:39 -06:00
|
|
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
pub struct UiTokenAmount {
|
|
|
|
pub ui_amount: f64,
|
|
|
|
pub decimals: u8,
|
|
|
|
pub amount: StringAmount,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
|
|
|
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
|
|
|
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
|
|
|
UiTokenAmount {
|
|
|
|
ui_amount: amount_decimals,
|
|
|
|
decimals,
|
|
|
|
amount: amount.to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-24 17:45:21 -06:00
|
|
|
#[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>,
|
|
|
|
}
|
|
|
|
|
2020-08-07 11:37:39 -06:00
|
|
|
pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
|
|
|
|
if data.len() == size_of::<Account>() {
|
|
|
|
Some(Pubkey::new(&data[0..32]))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-24 17:45:21 -06:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
2020-08-01 23:31:22 -07:00
|
|
|
use spl_token_v1_0::state::unpack_unchecked;
|
2020-07-24 17:45:21 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_token() {
|
2020-07-30 14:52:28 -07:00
|
|
|
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
|
|
|
let owner_pubkey = SplTokenPubkey::new(&[3; 32]);
|
2020-07-24 17:45:21 -06:00
|
|
|
let mut account_data = [0; size_of::<Account>()];
|
2020-08-01 23:31:22 -07:00
|
|
|
let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
|
2020-07-24 17:45:21 -06:00
|
|
|
account.mint = mint_pubkey;
|
|
|
|
account.owner = owner_pubkey;
|
|
|
|
account.amount = 42;
|
|
|
|
account.is_initialized = true;
|
2020-08-07 11:37:39 -06:00
|
|
|
assert!(parse_token(&account_data, None).is_err());
|
2020-07-24 17:45:21 -06:00
|
|
|
assert_eq!(
|
2020-08-07 11:37:39 -06:00
|
|
|
parse_token(&account_data, Some(2)).unwrap(),
|
2020-07-24 17:45:21 -06:00
|
|
|
TokenAccountType::Account(UiTokenAccount {
|
|
|
|
mint: mint_pubkey.to_string(),
|
|
|
|
owner: owner_pubkey.to_string(),
|
2020-08-07 11:37:39 -06:00
|
|
|
token_amount: UiTokenAmount {
|
|
|
|
ui_amount: 0.42,
|
|
|
|
decimals: 2,
|
|
|
|
amount: "42".to_string()
|
|
|
|
},
|
2020-07-24 17:45:21 -06:00
|
|
|
delegate: None,
|
|
|
|
is_initialized: true,
|
|
|
|
is_native: false,
|
|
|
|
delegated_amount: 0,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut mint_data = [0; size_of::<Mint>()];
|
2020-08-01 23:31:22 -07:00
|
|
|
let mut mint: &mut Mint = unpack_unchecked(&mut mint_data).unwrap();
|
2020-07-24 17:45:21 -06:00
|
|
|
mint.owner = COption::Some(owner_pubkey);
|
|
|
|
mint.decimals = 3;
|
|
|
|
mint.is_initialized = true;
|
|
|
|
assert_eq!(
|
2020-08-07 11:37:39 -06:00
|
|
|
parse_token(&mint_data, None).unwrap(),
|
2020-07-24 17:45:21 -06:00
|
|
|
TokenAccountType::Mint(UiMint {
|
|
|
|
owner: Some(owner_pubkey.to_string()),
|
|
|
|
decimals: 3,
|
|
|
|
is_initialized: true,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2020-07-30 14:52:28 -07:00
|
|
|
let signer1 = SplTokenPubkey::new(&[1; 32]);
|
|
|
|
let signer2 = SplTokenPubkey::new(&[2; 32]);
|
|
|
|
let signer3 = SplTokenPubkey::new(&[3; 32]);
|
2020-07-24 17:45:21 -06:00
|
|
|
let mut multisig_data = [0; size_of::<Multisig>()];
|
2020-08-01 23:31:22 -07:00
|
|
|
let mut multisig: &mut Multisig = unpack_unchecked(&mut multisig_data).unwrap();
|
2020-07-30 14:52:28 -07:00
|
|
|
let mut signers = [SplTokenPubkey::default(); 11];
|
2020-07-24 17:45:21 -06:00
|
|
|
signers[0] = signer1;
|
|
|
|
signers[1] = signer2;
|
|
|
|
signers[2] = signer3;
|
|
|
|
multisig.m = 2;
|
|
|
|
multisig.n = 3;
|
|
|
|
multisig.is_initialized = true;
|
|
|
|
multisig.signers = signers;
|
|
|
|
assert_eq!(
|
2020-08-07 11:37:39 -06:00
|
|
|
parse_token(&multisig_data, None).unwrap(),
|
2020-07-24 17:45:21 -06:00
|
|
|
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];
|
2020-08-07 11:37:39 -06:00
|
|
|
assert!(parse_token(&bad_data, None).is_err());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_token_account_mint() {
|
|
|
|
let mint_pubkey = SplTokenPubkey::new(&[2; 32]);
|
|
|
|
let mut account_data = [0; size_of::<Account>()];
|
|
|
|
let mut account: &mut Account = unpack_unchecked(&mut account_data).unwrap();
|
|
|
|
account.mint = mint_pubkey;
|
|
|
|
|
|
|
|
let expected_mint_pubkey = Pubkey::new(&[2; 32]);
|
|
|
|
assert_eq!(
|
|
|
|
get_token_account_mint(&account_data),
|
|
|
|
Some(expected_mint_pubkey)
|
|
|
|
);
|
2020-07-24 17:45:21 -06:00
|
|
|
}
|
|
|
|
}
|