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