carbon_pumpfun_decoder/accounts/
mod.rs

1use carbon_core::account::AccountDecoder;
2use carbon_core::deserialize::CarbonDeserialize;
3
4use crate::PROGRAM_ID;
5
6use super::PumpfunDecoder;
7pub mod bonding_curve;
8pub mod global;
9pub mod last_withdraw;
10
11#[allow(clippy::large_enum_variant)]
12pub enum PumpAccount {
13    BondingCurve(bonding_curve::BondingCurve),
14    Global(global::Global),
15    LastWithdraw(last_withdraw::LastWithdraw),
16}
17
18impl AccountDecoder<'_> for PumpfunDecoder {
19    type AccountType = PumpAccount;
20    fn decode_account(
21        &self,
22        account: &solana_account::Account,
23    ) -> Option<carbon_core::account::DecodedAccount<Self::AccountType>> {
24        if !account.owner.eq(&PROGRAM_ID) {
25            return None;
26        }
27
28        if let Some(decoded_account) =
29            bonding_curve::BondingCurve::deserialize(account.data.as_slice())
30        {
31            return Some(carbon_core::account::DecodedAccount {
32                lamports: account.lamports,
33                data: PumpAccount::BondingCurve(decoded_account),
34                owner: account.owner,
35                executable: account.executable,
36                rent_epoch: account.rent_epoch,
37            });
38        }
39
40        if let Some(decoded_account) = global::Global::deserialize(account.data.as_slice()) {
41            return Some(carbon_core::account::DecodedAccount {
42                lamports: account.lamports,
43                data: PumpAccount::Global(decoded_account),
44                owner: account.owner,
45                executable: account.executable,
46                rent_epoch: account.rent_epoch,
47            });
48        }
49
50        if let Some(decoded_account) =
51            last_withdraw::LastWithdraw::deserialize(account.data.as_slice())
52        {
53            return Some(carbon_core::account::DecodedAccount {
54                lamports: account.lamports,
55                data: PumpAccount::LastWithdraw(decoded_account),
56                owner: account.owner,
57                executable: account.executable,
58                rent_epoch: account.rent_epoch,
59            });
60        }
61
62        None
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use solana_pubkey::Pubkey;
69
70    use super::*;
71
72    #[test]
73    fn test_decode_bonding_curve_account() {
74        // Arrange
75        let expected_bonding_curve = bonding_curve::BondingCurve {
76            virtual_token_reserves: 1072906494066221,
77            virtual_sol_reserves: 30002615555,
78            real_token_reserves: 793006494066221,
79            real_sol_reserves: 2615555,
80            token_total_supply: 1000000000000000,
81            complete: false,
82        };
83
84        // Act
85        let decoder = PumpfunDecoder;
86        let account = carbon_test_utils::read_account("tests/fixtures/bonding_curve_account.json")
87            .expect("read fixture");
88        let decoded_account = decoder.decode_account(&account).expect("decode fixture");
89
90        // Assert
91        match decoded_account.data {
92            PumpAccount::BondingCurve(bonding_curve) => {
93                assert_eq!(
94                    expected_bonding_curve.virtual_token_reserves,
95                    bonding_curve.virtual_token_reserves
96                );
97                assert_eq!(
98                    expected_bonding_curve.virtual_sol_reserves,
99                    bonding_curve.virtual_sol_reserves
100                );
101                assert_eq!(
102                    expected_bonding_curve.real_token_reserves,
103                    bonding_curve.real_token_reserves
104                );
105                assert_eq!(
106                    expected_bonding_curve.real_sol_reserves,
107                    bonding_curve.real_sol_reserves
108                );
109                assert_eq!(
110                    expected_bonding_curve.token_total_supply,
111                    bonding_curve.token_total_supply
112                );
113                assert_eq!(expected_bonding_curve.complete, bonding_curve.complete);
114            }
115            _ => panic!("Expected BondingCurve"),
116        }
117    }
118
119    #[test]
120    fn test_decode_global_account() {
121        // Arrange
122        let expected_global_account = global::Global {
123            initialized: true,
124            authority: Pubkey::from_str_const("DCpJReAfonSrgohiQbTmKKbjbqVofspFRHz9yQikzooP"),
125            fee_recipient: Pubkey::from_str_const("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"),
126            initial_virtual_token_reserves: 1073000000000000,
127            initial_virtual_sol_reserves: 30000000000,
128            initial_real_token_reserves: 793100000000000,
129            token_total_supply: 1000000000000000,
130            fee_basis_points: 100,
131            ..Default::default()
132        };
133
134        // Act
135        let decoder = PumpfunDecoder;
136        let account = carbon_test_utils::read_account("tests/fixtures/global_account.json")
137            .expect("read fixture");
138        let decoded_account = decoder.decode_account(&account).expect("decode fixture");
139
140        // Assert
141        match decoded_account.data {
142            PumpAccount::Global(global_account) => {
143                assert_eq!(
144                    expected_global_account.initialized,
145                    global_account.initialized
146                );
147                assert_eq!(expected_global_account.authority, global_account.authority);
148                assert_eq!(
149                    expected_global_account.fee_recipient,
150                    global_account.fee_recipient
151                );
152                assert_eq!(
153                    expected_global_account.initial_virtual_token_reserves,
154                    global_account.initial_virtual_token_reserves
155                );
156                assert_eq!(
157                    expected_global_account.initial_virtual_sol_reserves,
158                    global_account.initial_virtual_sol_reserves
159                );
160                assert_eq!(
161                    expected_global_account.initial_real_token_reserves,
162                    global_account.initial_real_token_reserves
163                );
164                assert_eq!(
165                    expected_global_account.token_total_supply,
166                    global_account.token_total_supply
167                );
168                assert_eq!(
169                    expected_global_account.fee_basis_points,
170                    global_account.fee_basis_points
171                );
172            }
173            _ => panic!("Expected Global"),
174        }
175    }
176
177    #[test]
178    fn test_decode_last_withdraw_account() {
179        // Arrange
180        let expected_last_withdraw_account = last_withdraw::LastWithdraw {
181            last_withdraw_timestamp: 1741550682,
182        };
183
184        // Act
185        let decoder = PumpfunDecoder;
186        let account = carbon_test_utils::read_account("tests/fixtures/last_withdraw_account.json")
187            .expect("read fixture");
188        let decoded_account = decoder.decode_account(&account).expect("decode fixture");
189
190        // Assert
191        match decoded_account.data {
192            PumpAccount::LastWithdraw(last_withdraw) => {
193                assert_eq!(
194                    expected_last_withdraw_account.last_withdraw_timestamp,
195                    last_withdraw.last_withdraw_timestamp
196                );
197            }
198            _ => panic!("Expected LastWithdraw"),
199        }
200    }
201}