solana_account_decoder/
parse_bpf_loader.rs

1use {
2    crate::{
3        parse_account_data::{ParsableAccount, ParseAccountError},
4        UiAccountData, UiAccountEncoding,
5    },
6    base64::{prelude::BASE64_STANDARD, Engine},
7    bincode::{deserialize, serialized_size},
8    solana_sdk::{bpf_loader_upgradeable::UpgradeableLoaderState, pubkey::Pubkey},
9};
10
11pub fn parse_bpf_upgradeable_loader(
12    data: &[u8],
13) -> Result<BpfUpgradeableLoaderAccountType, ParseAccountError> {
14    let account_state: UpgradeableLoaderState = deserialize(data).map_err(|_| {
15        ParseAccountError::AccountNotParsable(ParsableAccount::BpfUpgradeableLoader)
16    })?;
17    let parsed_account = match account_state {
18        UpgradeableLoaderState::Uninitialized => BpfUpgradeableLoaderAccountType::Uninitialized,
19        UpgradeableLoaderState::Buffer { authority_address } => {
20            let offset = if authority_address.is_some() {
21                UpgradeableLoaderState::size_of_buffer_metadata()
22            } else {
23                // This case included for code completeness; in practice, a Buffer account will
24                // always have authority_address.is_some()
25                UpgradeableLoaderState::size_of_buffer_metadata()
26                    - serialized_size(&Pubkey::default()).unwrap() as usize
27            };
28            BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
29                authority: authority_address.map(|pubkey| pubkey.to_string()),
30                data: UiAccountData::Binary(
31                    BASE64_STANDARD.encode(&data[offset..]),
32                    UiAccountEncoding::Base64,
33                ),
34            })
35        }
36        UpgradeableLoaderState::Program {
37            programdata_address,
38        } => BpfUpgradeableLoaderAccountType::Program(UiProgram {
39            program_data: programdata_address.to_string(),
40        }),
41        UpgradeableLoaderState::ProgramData {
42            slot,
43            upgrade_authority_address,
44        } => {
45            let offset = if upgrade_authority_address.is_some() {
46                UpgradeableLoaderState::size_of_programdata_metadata()
47            } else {
48                UpgradeableLoaderState::size_of_programdata_metadata()
49                    - serialized_size(&Pubkey::default()).unwrap() as usize
50            };
51            BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
52                slot,
53                authority: upgrade_authority_address.map(|pubkey| pubkey.to_string()),
54                data: UiAccountData::Binary(
55                    BASE64_STANDARD.encode(&data[offset..]),
56                    UiAccountEncoding::Base64,
57                ),
58            })
59        }
60    };
61    Ok(parsed_account)
62}
63
64#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase", tag = "type", content = "info")]
66pub enum BpfUpgradeableLoaderAccountType {
67    Uninitialized,
68    Buffer(UiBuffer),
69    Program(UiProgram),
70    ProgramData(UiProgramData),
71}
72
73#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
74#[serde(rename_all = "camelCase")]
75pub struct UiBuffer {
76    pub authority: Option<String>,
77    pub data: UiAccountData,
78}
79
80#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
81#[serde(rename_all = "camelCase")]
82pub struct UiProgram {
83    pub program_data: String,
84}
85
86#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(rename_all = "camelCase")]
88pub struct UiProgramData {
89    pub slot: u64,
90    pub authority: Option<String>,
91    pub data: UiAccountData,
92}
93
94#[cfg(test)]
95mod test {
96    use {super::*, bincode::serialize, solana_sdk::pubkey::Pubkey};
97
98    #[test]
99    fn test_parse_bpf_upgradeable_loader_accounts() {
100        let bpf_loader_state = UpgradeableLoaderState::Uninitialized;
101        let account_data = serialize(&bpf_loader_state).unwrap();
102        assert_eq!(
103            parse_bpf_upgradeable_loader(&account_data).unwrap(),
104            BpfUpgradeableLoaderAccountType::Uninitialized
105        );
106
107        let program = vec![7u8; 64]; // Arbitrary program data
108
109        let authority = Pubkey::new_unique();
110        let bpf_loader_state = UpgradeableLoaderState::Buffer {
111            authority_address: Some(authority),
112        };
113        let mut account_data = serialize(&bpf_loader_state).unwrap();
114        account_data.extend_from_slice(&program);
115        assert_eq!(
116            parse_bpf_upgradeable_loader(&account_data).unwrap(),
117            BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
118                authority: Some(authority.to_string()),
119                data: UiAccountData::Binary(
120                    BASE64_STANDARD.encode(&program),
121                    UiAccountEncoding::Base64
122                ),
123            })
124        );
125
126        // This case included for code completeness; in practice, a Buffer account will always have
127        // authority_address.is_some()
128        let bpf_loader_state = UpgradeableLoaderState::Buffer {
129            authority_address: None,
130        };
131        let mut account_data = serialize(&bpf_loader_state).unwrap();
132        account_data.extend_from_slice(&program);
133        assert_eq!(
134            parse_bpf_upgradeable_loader(&account_data).unwrap(),
135            BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
136                authority: None,
137                data: UiAccountData::Binary(
138                    BASE64_STANDARD.encode(&program),
139                    UiAccountEncoding::Base64
140                ),
141            })
142        );
143
144        let programdata_address = Pubkey::new_unique();
145        let bpf_loader_state = UpgradeableLoaderState::Program {
146            programdata_address,
147        };
148        let account_data = serialize(&bpf_loader_state).unwrap();
149        assert_eq!(
150            parse_bpf_upgradeable_loader(&account_data).unwrap(),
151            BpfUpgradeableLoaderAccountType::Program(UiProgram {
152                program_data: programdata_address.to_string(),
153            })
154        );
155
156        let authority = Pubkey::new_unique();
157        let slot = 42;
158        let bpf_loader_state = UpgradeableLoaderState::ProgramData {
159            slot,
160            upgrade_authority_address: Some(authority),
161        };
162        let mut account_data = serialize(&bpf_loader_state).unwrap();
163        account_data.extend_from_slice(&program);
164        assert_eq!(
165            parse_bpf_upgradeable_loader(&account_data).unwrap(),
166            BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
167                slot,
168                authority: Some(authority.to_string()),
169                data: UiAccountData::Binary(
170                    BASE64_STANDARD.encode(&program),
171                    UiAccountEncoding::Base64
172                ),
173            })
174        );
175
176        let bpf_loader_state = UpgradeableLoaderState::ProgramData {
177            slot,
178            upgrade_authority_address: None,
179        };
180        let mut account_data = serialize(&bpf_loader_state).unwrap();
181        account_data.extend_from_slice(&program);
182        assert_eq!(
183            parse_bpf_upgradeable_loader(&account_data).unwrap(),
184            BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
185                slot,
186                authority: None,
187                data: UiAccountData::Binary(
188                    BASE64_STANDARD.encode(&program),
189                    UiAccountEncoding::Base64
190                ),
191            })
192        );
193    }
194}