Skip to main content

sbpf_debugger/
input.rs

1use {
2    crate::error::{DebuggerError, DebuggerResult},
3    serde::Deserialize,
4    solana_account::Account as SolAccount,
5    solana_address::Address,
6    solana_instruction::{AccountMeta, Instruction},
7    std::{collections::HashMap, fs, mem::size_of, path::Path, str::FromStr},
8};
9
10const BPF_ALIGN_OF_U128: usize = 16;
11const MAX_PERMITTED_DATA_INCREASE: usize = 10240;
12const NON_DUP_MARKER: u8 = 0xff;
13
14#[derive(Deserialize)]
15struct DebuggerInput {
16    instruction: InstructionJson,
17    accounts: Vec<AccountJson>,
18}
19
20#[derive(Deserialize)]
21struct InstructionJson {
22    program_id: String,
23    accounts: Vec<AccountMetaJson>,
24    #[serde(default)]
25    data: String,
26}
27
28#[derive(Deserialize)]
29struct AccountMetaJson {
30    pubkey: String,
31    is_signer: bool,
32    is_writable: bool,
33}
34
35#[derive(Deserialize)]
36struct AccountJson {
37    pubkey: String,
38    owner: String,
39    lamports: u64,
40    #[serde(default)]
41    data: String,
42    #[serde(default)]
43    executable: bool,
44}
45
46struct Serializer {
47    buffer: Vec<u8>,
48}
49
50impl Serializer {
51    fn new() -> Self {
52        Self { buffer: Vec::new() }
53    }
54
55    fn write<T>(&mut self, value: T) {
56        let bytes =
57            unsafe { std::slice::from_raw_parts(&value as *const T as *const u8, size_of::<T>()) };
58        self.buffer.extend_from_slice(bytes);
59    }
60
61    fn write_all(&mut self, data: &[u8]) {
62        self.buffer.extend_from_slice(data);
63    }
64
65    fn write_account_data(&mut self, data: &[u8]) {
66        self.write_all(data);
67        self.buffer
68            .extend(std::iter::repeat_n(0u8, MAX_PERMITTED_DATA_INCREASE));
69        let current_len = self.buffer.len();
70        let alignment_needed =
71            (BPF_ALIGN_OF_U128 - (current_len % BPF_ALIGN_OF_U128)) % BPF_ALIGN_OF_U128;
72        self.buffer
73            .extend(std::iter::repeat_n(0u8, alignment_needed));
74    }
75
76    fn finish(self) -> Vec<u8> {
77        self.buffer
78    }
79}
80
81enum SerializeAccount {
82    Account(Address, SolAccount, bool, bool),
83    Duplicate(u8),
84}
85
86fn serialize_parameters(
87    accounts: Vec<SerializeAccount>,
88    instruction_data: &[u8],
89    program_id: &Address,
90) -> Vec<u8> {
91    let mut s = Serializer::new();
92
93    s.write::<u64>((accounts.len() as u64).to_le());
94
95    for account in accounts {
96        match account {
97            SerializeAccount::Account(pubkey, acct, is_signer, is_writable) => {
98                s.write::<u8>(NON_DUP_MARKER);
99                s.write::<u8>(is_signer as u8);
100                s.write::<u8>(is_writable as u8);
101                s.write::<u8>(acct.executable as u8);
102                s.write_all(&[0u8; 4]); // padding
103                s.write_all(pubkey.as_ref());
104                s.write_all(acct.owner.as_ref());
105                s.write::<u64>(acct.lamports.to_le());
106                s.write::<u64>((acct.data.len() as u64).to_le());
107                s.write_account_data(&acct.data);
108                s.write::<u64>(acct.rent_epoch.to_le());
109            }
110            SerializeAccount::Duplicate(position) => {
111                s.write::<u8>(position);
112                s.write_all(&[0u8; 7]); // padding
113            }
114        }
115    }
116
117    s.write::<u64>((instruction_data.len() as u64).to_le());
118    s.write_all(instruction_data);
119    s.write_all(program_id.as_ref());
120
121    s.finish()
122}
123
124/// Parse input JSON into serialized VM input bytes and program_id.
125/// Returns empty input bytes and random program_id if input is empty.
126pub fn parse_input(input: &str) -> DebuggerResult<(Vec<u8>, Address)> {
127    let input = input.trim();
128    if input.is_empty() {
129        return Ok((Vec::new(), Address::new_unique()));
130    }
131
132    // Handle both JSON file path or JSON string.
133    let json_str = if Path::new(input).exists() {
134        fs::read_to_string(input)?
135    } else {
136        input.to_string()
137    };
138
139    let debugger_input: DebuggerInput =
140        serde_json::from_str(&json_str).map_err(|e| DebuggerError::InvalidInput(e.to_string()))?;
141
142    let program_id = Address::from_str(&debugger_input.instruction.program_id)
143        .map_err(|e| DebuggerError::InvalidInput(format!("Invalid program_id: {}", e)))?;
144
145    let account_metas: Vec<AccountMeta> = debugger_input
146        .instruction
147        .accounts
148        .iter()
149        .map(|a| {
150            let pubkey = Address::from_str(&a.pubkey)
151                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid pubkey: {}", e)))?;
152            Ok(AccountMeta {
153                pubkey,
154                is_signer: a.is_signer,
155                is_writable: a.is_writable,
156            })
157        })
158        .collect::<DebuggerResult<Vec<_>>>()?;
159
160    let instruction_data = if debugger_input.instruction.data.is_empty() {
161        Vec::new()
162    } else {
163        bs58::decode(&debugger_input.instruction.data)
164            .into_vec()
165            .map_err(|e| {
166                DebuggerError::InvalidInput(format!("Invalid base58 instruction data: {}", e))
167            })?
168    };
169
170    let instruction = Instruction::new_with_bytes(program_id, &instruction_data, account_metas);
171
172    let account_map: HashMap<Address, SolAccount> = debugger_input
173        .accounts
174        .iter()
175        .map(|a| {
176            let pubkey = Address::from_str(&a.pubkey)
177                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid pubkey: {}", e)))?;
178            let owner = Address::from_str(&a.owner)
179                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid owner: {}", e)))?;
180            let data = if a.data.is_empty() {
181                Vec::new()
182            } else {
183                bs58::decode(&a.data).into_vec().map_err(|e| {
184                    DebuggerError::InvalidInput(format!("Invalid base58 account data: {}", e))
185                })?
186            };
187            Ok((
188                pubkey,
189                SolAccount {
190                    lamports: a.lamports,
191                    data,
192                    owner,
193                    executable: a.executable,
194                    rent_epoch: 0,
195                },
196            ))
197        })
198        .collect::<DebuggerResult<HashMap<_, _>>>()?;
199
200    let mut serialized_accounts = Vec::new();
201    let mut seen: HashMap<Address, usize> = HashMap::new();
202
203    for (i, meta) in instruction.accounts.iter().enumerate() {
204        if let Some(&first_idx) = seen.get(&meta.pubkey) {
205            serialized_accounts.push(SerializeAccount::Duplicate(first_idx as u8));
206        } else {
207            seen.insert(meta.pubkey, i);
208            let acct = account_map.get(&meta.pubkey).ok_or_else(|| {
209                DebuggerError::InvalidInput(format!("Missing account data for {}", meta.pubkey))
210            })?;
211            serialized_accounts.push(SerializeAccount::Account(
212                meta.pubkey,
213                acct.clone(),
214                meta.is_signer,
215                meta.is_writable,
216            ));
217        }
218    }
219
220    let bytes = serialize_parameters(
221        serialized_accounts,
222        &instruction.data,
223        &instruction.program_id,
224    );
225
226    Ok((bytes, program_id))
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_parse_empty_input() {
235        let (bytes, _) = parse_input("").unwrap();
236        assert!(bytes.is_empty());
237    }
238
239    #[test]
240    fn test_parse_json_string() {
241        let program_id = Address::new_unique();
242        let account_pubkey = Address::new_unique();
243        let owner = Address::new_unique();
244
245        let json = format!(
246            r#"{{
247                "instruction": {{
248                    "program_id": "{}",
249                    "accounts": [
250                        {{ "pubkey": "{}", "is_signer": true, "is_writable": true }}
251                    ],
252                    "data": "q"
253                }},
254                "accounts": [
255                    {{
256                        "pubkey": "{}",
257                        "owner": "{}",
258                        "lamports": 1000000,
259                        "data": "",
260                        "executable": false
261                    }}
262                ]
263            }}"#,
264            program_id, account_pubkey, account_pubkey, owner
265        );
266
267        let (bytes, pid) = parse_input(&json).unwrap();
268        assert!(!bytes.is_empty());
269        assert_eq!(pid, program_id);
270    }
271}