use {
crate::{Token2022Decoder, PROGRAM_ID},
solana_program_pack::Pack,
};
#[cfg(feature = "postgres")]
pub mod postgres;
#[cfg(feature = "graphql")]
pub mod graphql;
pub mod mint;
pub mod multisig;
pub mod token;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", content = "data"))]
pub enum Token2022Account {
Mint(Box<mint::Mint>),
Multisig(Box<multisig::Multisig>),
Token(Box<token::Token>),
}
impl<'a> carbon_core::account::AccountDecoder<'a> for Token2022Decoder {
type AccountType = Token2022Account;
fn decode_account(
&self,
account: &'a solana_account::Account,
) -> Option<carbon_core::account::DecodedAccount<Self::AccountType>> {
if account.owner != PROGRAM_ID {
return None;
}
if let Ok(data) = spl_token_2022::extension::StateWithExtensions::<
spl_token_2022::state::Mint,
>::unpack(&account.data)
{
return Some(carbon_core::account::DecodedAccount {
lamports: account.lamports,
data: Token2022Account::Mint(Box::new(mint::Mint::from(data))),
owner: account.owner,
executable: account.executable,
rent_epoch: account.rent_epoch,
});
}
if let Ok(data) = spl_token_2022::extension::StateWithExtensions::<
spl_token_2022::state::Account,
>::unpack(&account.data)
{
return Some(carbon_core::account::DecodedAccount {
lamports: account.lamports,
data: Token2022Account::Token(Box::new(token::Token::from(data))),
owner: account.owner,
executable: account.executable,
rent_epoch: account.rent_epoch,
});
}
if let Ok(data) = spl_token_2022::state::Multisig::unpack_unchecked(&account.data) {
return Some(carbon_core::account::DecodedAccount {
lamports: account.lamports,
data: Token2022Account::Multisig(Box::new(multisig::Multisig::from(data))),
owner: account.owner,
executable: account.executable,
rent_epoch: account.rent_epoch,
});
}
None
}
}
#[cfg(test)]
mod tests {
use {super::*, carbon_core::account::AccountDecoder, solana_pubkey::Pubkey};
const TOKEN_2022_PROGRAM_ID: Pubkey =
solana_pubkey::Pubkey::from_str_const("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
fn create_base_mint_data() -> Vec<u8> {
let mut data = vec![0u8; 82];
data[0] = 1; data[4..36].copy_from_slice(&[1u8; 32]);
data[36..44].copy_from_slice(&1_000_000_000u64.to_le_bytes()); data[44] = 9; data[45] = 1; data
}
fn create_base_token_account_data() -> Vec<u8> {
let mut data = vec![0u8; 165];
data[0..32].copy_from_slice(&[2u8; 32]); data[32..64].copy_from_slice(&[3u8; 32]); data[64..72].copy_from_slice(&500_000_000u64.to_le_bytes()); data[108] = 1; data
}
#[test]
fn test_decode_base_mint_account() {
let decoder = Token2022Decoder;
let account = solana_account::Account {
lamports: 1_000_000,
data: create_base_mint_data(),
owner: TOKEN_2022_PROGRAM_ID,
executable: false,
rent_epoch: 0,
};
let result = decoder.decode_account(&account);
assert!(result.is_some(), "Should decode base Mint account");
match result.unwrap().data {
Token2022Account::Mint(mint) => {
assert_eq!(mint.supply, 1_000_000_000);
assert_eq!(mint.decimals, 9);
}
_ => panic!("Expected Mint account"),
}
}
#[test]
fn test_decode_base_token_account() {
let decoder = Token2022Decoder;
let account = solana_account::Account {
lamports: 1_000_000,
data: create_base_token_account_data(),
owner: TOKEN_2022_PROGRAM_ID,
executable: false,
rent_epoch: 0,
};
let result = decoder.decode_account(&account);
assert!(result.is_some(), "Should decode base Token account");
match result.unwrap().data {
Token2022Account::Token(token) => {
assert_eq!(token.amount, 500_000_000);
}
_ => panic!("Expected Token account"),
}
}
#[test]
fn test_decode_multisig_account() {
let decoder = Token2022Decoder;
let mut data = vec![0u8; 355];
data[0] = 2; data[1] = 3; data[2] = 1;
let account = solana_account::Account {
lamports: 1_000_000,
data,
owner: TOKEN_2022_PROGRAM_ID,
executable: false,
rent_epoch: 0,
};
let result = decoder.decode_account(&account);
assert!(result.is_some(), "Should decode Multisig account");
match result.unwrap().data {
Token2022Account::Multisig(ms) => {
assert_eq!(ms.m, 2);
assert_eq!(ms.n, 3);
}
_ => panic!("Expected Multisig account"),
}
}
#[test]
fn test_wrong_owner_returns_none() {
let decoder = Token2022Decoder;
let account = solana_account::Account {
lamports: 1_000_000,
data: create_base_mint_data(),
owner: Pubkey::new_unique(),
executable: false,
rent_epoch: 0,
};
assert!(decoder.decode_account(&account).is_none());
}
#[test]
fn test_no_panic_on_various_sizes() {
let decoder = Token2022Decoder;
for size in [
0, 1, 50, 81, 82, 83, 100, 164, 165, 166, 200, 354, 355, 356, 500,
] {
let account = solana_account::Account {
lamports: 1_000_000,
data: vec![0u8; size],
owner: TOKEN_2022_PROGRAM_ID,
executable: false,
rent_epoch: 0,
};
let _ = decoder.decode_account(&account);
}
}
}