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