Skip to main content

carbon_token_2022_decoder/accounts/
mod.rs

1//! This code was AUTOGENERATED using the Codama library.
2use {
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        // Try Mint (base or with extensions) - StateWithExtensions::unpack handles both
38        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        // Try Token Account (base or with extensions)
51        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        // Try Multisig (no extensions support)
64        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; // mint_authority: Some
88        data[4..36].copy_from_slice(&[1u8; 32]);
89        data[36..44].copy_from_slice(&1_000_000_000u64.to_le_bytes()); // supply
90        data[44] = 9; // decimals
91        data[45] = 1; // is_initialized
92        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]); // mint
98        data[32..64].copy_from_slice(&[3u8; 32]); // owner
99        data[64..72].copy_from_slice(&500_000_000u64.to_le_bytes()); // amount
100        data[108] = 1; // state: Initialized
101        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; // m
152        data[1] = 3; // n
153        data[2] = 1; // is_initialized
154
155        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    /// Regression test: decoder must handle various account sizes without
188    /// panic.
189    #[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            // Must not panic
204            let _ = decoder.decode_account(&account);
205        }
206    }
207}