carbon_token_2022_decoder/accounts/
mod.rs1use {
3 crate::{Token2022Decoder, PROGRAM_ID},
4 solana_program_pack::Pack,
5};
6
7#[cfg(feature = "postgres")]
8pub mod postgres;
9
10#[cfg(feature = "graphql")]
11pub mod graphql;
12
13pub mod mint;
14pub mod multisig;
15pub mod token;
16
17#[derive(Debug, Clone, PartialEq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(feature = "serde", serde(tag = "type", content = "data"))]
20pub enum Token2022Account {
21 Mint(Box<mint::Mint>),
22 Multisig(Box<multisig::Multisig>),
23 Token(Box<token::Token>),
24}
25
26impl<'a> carbon_core::account::AccountDecoder<'a> for Token2022Decoder {
27 type AccountType = Token2022Account;
28
29 fn decode_account(
30 &self,
31 account: &'a solana_account::Account,
32 ) -> Option<carbon_core::account::DecodedAccount<Self::AccountType>> {
33 if account.owner != PROGRAM_ID {
34 return None;
35 }
36
37 if let Ok(data) = spl_token_2022::extension::StateWithExtensions::<
39 spl_token_2022::state::Mint,
40 >::unpack(&account.data)
41 {
42 return Some(carbon_core::account::DecodedAccount {
43 lamports: account.lamports,
44 data: Token2022Account::Mint(Box::new(mint::Mint::from(data))),
45 owner: account.owner,
46 executable: account.executable,
47 rent_epoch: account.rent_epoch,
48 });
49 }
50 if let Ok(data) = spl_token_2022::extension::StateWithExtensions::<
52 spl_token_2022::state::Account,
53 >::unpack(&account.data)
54 {
55 return Some(carbon_core::account::DecodedAccount {
56 lamports: account.lamports,
57 data: Token2022Account::Token(Box::new(token::Token::from(data))),
58 owner: account.owner,
59 executable: account.executable,
60 rent_epoch: account.rent_epoch,
61 });
62 }
63 if let Ok(data) = spl_token_2022::state::Multisig::unpack_unchecked(&account.data) {
65 return Some(carbon_core::account::DecodedAccount {
66 lamports: account.lamports,
67 data: Token2022Account::Multisig(Box::new(multisig::Multisig::from(data))),
68 owner: account.owner,
69 executable: account.executable,
70 rent_epoch: account.rent_epoch,
71 });
72 }
73
74 None
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use {super::*, carbon_core::account::AccountDecoder, solana_pubkey::Pubkey};
81
82 const TOKEN_2022_PROGRAM_ID: Pubkey =
83 solana_pubkey::Pubkey::from_str_const("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
84
85 fn create_base_mint_data() -> Vec<u8> {
86 let mut data = vec![0u8; 82];
87 data[0] = 1; data[4..36].copy_from_slice(&[1u8; 32]);
89 data[36..44].copy_from_slice(&1_000_000_000u64.to_le_bytes()); data[44] = 9; data[45] = 1; data
93 }
94
95 fn create_base_token_account_data() -> Vec<u8> {
96 let mut data = vec![0u8; 165];
97 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
102 }
103
104 #[test]
105 fn test_decode_base_mint_account() {
106 let decoder = Token2022Decoder;
107 let account = solana_account::Account {
108 lamports: 1_000_000,
109 data: create_base_mint_data(),
110 owner: TOKEN_2022_PROGRAM_ID,
111 executable: false,
112 rent_epoch: 0,
113 };
114
115 let result = decoder.decode_account(&account);
116 assert!(result.is_some(), "Should decode base Mint account");
117 match result.unwrap().data {
118 Token2022Account::Mint(mint) => {
119 assert_eq!(mint.supply, 1_000_000_000);
120 assert_eq!(mint.decimals, 9);
121 }
122 _ => panic!("Expected Mint account"),
123 }
124 }
125
126 #[test]
127 fn test_decode_base_token_account() {
128 let decoder = Token2022Decoder;
129 let account = solana_account::Account {
130 lamports: 1_000_000,
131 data: create_base_token_account_data(),
132 owner: TOKEN_2022_PROGRAM_ID,
133 executable: false,
134 rent_epoch: 0,
135 };
136
137 let result = decoder.decode_account(&account);
138 assert!(result.is_some(), "Should decode base Token account");
139 match result.unwrap().data {
140 Token2022Account::Token(token) => {
141 assert_eq!(token.amount, 500_000_000);
142 }
143 _ => panic!("Expected Token account"),
144 }
145 }
146
147 #[test]
148 fn test_decode_multisig_account() {
149 let decoder = Token2022Decoder;
150 let mut data = vec![0u8; 355];
151 data[0] = 2; data[1] = 3; data[2] = 1; let account = solana_account::Account {
156 lamports: 1_000_000,
157 data,
158 owner: TOKEN_2022_PROGRAM_ID,
159 executable: false,
160 rent_epoch: 0,
161 };
162
163 let result = decoder.decode_account(&account);
164 assert!(result.is_some(), "Should decode Multisig account");
165 match result.unwrap().data {
166 Token2022Account::Multisig(ms) => {
167 assert_eq!(ms.m, 2);
168 assert_eq!(ms.n, 3);
169 }
170 _ => panic!("Expected Multisig account"),
171 }
172 }
173
174 #[test]
175 fn test_wrong_owner_returns_none() {
176 let decoder = Token2022Decoder;
177 let account = solana_account::Account {
178 lamports: 1_000_000,
179 data: create_base_mint_data(),
180 owner: Pubkey::new_unique(),
181 executable: false,
182 rent_epoch: 0,
183 };
184 assert!(decoder.decode_account(&account).is_none());
185 }
186
187 #[test]
190 fn test_no_panic_on_various_sizes() {
191 let decoder = Token2022Decoder;
192
193 for size in [
194 0, 1, 50, 81, 82, 83, 100, 164, 165, 166, 200, 354, 355, 356, 500,
195 ] {
196 let account = solana_account::Account {
197 lamports: 1_000_000,
198 data: vec![0u8; size],
199 owner: TOKEN_2022_PROGRAM_ID,
200 executable: false,
201 rent_epoch: 0,
202 };
203 let _ = decoder.decode_account(&account);
205 }
206 }
207}