use solana_account::Account;
use solana_pubkey::Pubkey;
use wp_solana_pool_types::metadata::{MintInfo, TokenProgram};
use crate::plan_error::FetchError;
pub fn decode_mint_info(address: Pubkey, account: Option<Account>) -> Result<MintInfo, FetchError> {
let account = account.ok_or(FetchError::AccountNotFound(address))?;
let program = TokenProgram::from_program_id(&account.owner).ok_or_else(|| {
FetchError::InvalidInput(format!(
"mint {} owner is neither SPL Token nor Token-2022",
address
))
})?;
let decimals = read_mint_decimals(&account.data, address)?;
Ok(MintInfo { address, decimals, program })
}
pub fn read_mint_decimals(data: &[u8], address: Pubkey) -> Result<u8, FetchError> {
const DECIMALS_OFFSET: usize = 44;
if data.len() <= DECIMALS_OFFSET {
return Err(FetchError::AccountDecode(format!(
"mint account {} too short ({} bytes)",
address,
data.len()
)));
}
Ok(data[DECIMALS_OFFSET])
}
#[cfg(test)]
mod tests {
use super::*;
fn mint_data(decimals: u8) -> Vec<u8> {
let mut v = vec![0u8; 82];
v[44] = decimals;
v
}
fn account_owned_by(owner: Pubkey, data: Vec<u8>) -> Account {
Account { lamports: 1, data, owner, executable: false, rent_epoch: 0 }
}
#[test]
fn reads_decimals_at_offset_44() {
let addr = Pubkey::new_unique();
assert_eq!(read_mint_decimals(&mint_data(9), addr).unwrap(), 9);
assert_eq!(read_mint_decimals(&mint_data(0), addr).unwrap(), 0);
assert_eq!(read_mint_decimals(&mint_data(6), addr).unwrap(), 6);
}
#[test]
fn rejects_short_account_data() {
let addr = Pubkey::new_unique();
let err = read_mint_decimals(&[0u8; 44], addr).unwrap_err();
assert!(matches!(err, FetchError::AccountDecode(_)), "got {err:?}");
}
#[test]
fn decode_mint_info_spl_token() {
let addr = Pubkey::new_unique();
let acc = account_owned_by(spl_token_interface::ID, mint_data(9));
let info = decode_mint_info(addr, Some(acc)).unwrap();
assert_eq!(info, MintInfo { address: addr, decimals: 9, program: TokenProgram::SplToken });
}
#[test]
fn decode_mint_info_token_2022() {
let addr = Pubkey::new_unique();
let acc = account_owned_by(spl_token_2022_interface::ID, mint_data(6));
let info = decode_mint_info(addr, Some(acc)).unwrap();
assert_eq!(
info,
MintInfo { address: addr, decimals: 6, program: TokenProgram::SplToken2022 }
);
}
#[test]
fn decode_mint_info_missing_account_is_not_found() {
let addr = Pubkey::new_unique();
let err = decode_mint_info(addr, None).unwrap_err();
assert!(matches!(err, FetchError::AccountNotFound(a) if a == addr), "got {err:?}");
}
#[test]
fn decode_mint_info_unknown_owner_is_invalid_input() {
let addr = Pubkey::new_unique();
let acc = account_owned_by(Pubkey::new_unique(), mint_data(9));
let err = decode_mint_info(addr, Some(acc)).unwrap_err();
assert!(matches!(err, FetchError::InvalidInput(_)), "got {err:?}");
}
}