Skip to main content

sbpf_debugger/
input.rs

1use {
2    crate::error::{DebuggerError, DebuggerResult},
3    serde::Deserialize,
4    solana_account::Account,
5    solana_address::Address,
6    solana_instruction::{AccountMeta, Instruction},
7    std::{fs, path::Path, str::FromStr},
8};
9
10#[derive(Deserialize)]
11struct DebuggerInput {
12    instruction: InstructionJson,
13    accounts: Vec<AccountJson>,
14    #[serde(default)]
15    programs: Vec<ProgramJson>,
16}
17
18#[derive(Deserialize)]
19struct InstructionJson {
20    program_id: String,
21    accounts: Vec<AccountMetaJson>,
22    #[serde(default)]
23    data: String,
24}
25
26#[derive(Deserialize)]
27struct AccountMetaJson {
28    pubkey: String,
29    is_signer: bool,
30    is_writable: bool,
31}
32
33#[derive(Deserialize)]
34struct AccountJson {
35    pubkey: String,
36    owner: String,
37    lamports: u64,
38    #[serde(default)]
39    data: String,
40    #[serde(default)]
41    executable: bool,
42}
43
44#[derive(Deserialize)]
45struct ProgramJson {
46    program_id: String,
47    elf: String,
48}
49
50pub struct ParsedInput {
51    pub instruction: Instruction,
52    pub accounts: Vec<(Address, Account)>,
53    pub programs: Vec<(Address, Vec<u8>)>,
54}
55
56pub fn parse_input(input: &str) -> DebuggerResult<ParsedInput> {
57    let input = input.trim();
58    if input.is_empty() {
59        let program_id = Address::new_unique();
60        return Ok(ParsedInput {
61            instruction: Instruction::new_with_bytes(program_id, &[], vec![]),
62            accounts: Vec::new(),
63            programs: Vec::new(),
64        });
65    }
66
67    // Handle both JSON file path or JSON string.
68    let input_path = Path::new(input);
69    let (json_str, base_dir) = if input_path.exists() {
70        let base = input_path.parent().unwrap_or(Path::new(".")).to_path_buf();
71        (fs::read_to_string(input)?, base)
72    } else {
73        (input.to_string(), Path::new(".").to_path_buf())
74    };
75
76    let debugger_input: DebuggerInput =
77        serde_json::from_str(&json_str).map_err(|e| DebuggerError::InvalidInput(e.to_string()))?;
78
79    let program_id = Address::from_str(&debugger_input.instruction.program_id)
80        .map_err(|e| DebuggerError::InvalidInput(format!("Invalid program_id: {}", e)))?;
81
82    let account_metas: Vec<AccountMeta> = debugger_input
83        .instruction
84        .accounts
85        .iter()
86        .map(|a| {
87            let pubkey = Address::from_str(&a.pubkey)
88                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid pubkey: {}", e)))?;
89            Ok(AccountMeta {
90                pubkey,
91                is_signer: a.is_signer,
92                is_writable: a.is_writable,
93            })
94        })
95        .collect::<DebuggerResult<Vec<_>>>()?;
96
97    let instruction_data = if debugger_input.instruction.data.is_empty() {
98        Vec::new()
99    } else {
100        bs58::decode(&debugger_input.instruction.data)
101            .into_vec()
102            .map_err(|e| {
103                DebuggerError::InvalidInput(format!("Invalid base58 instruction data: {}", e))
104            })?
105    };
106
107    let instruction = Instruction::new_with_bytes(program_id, &instruction_data, account_metas);
108
109    let accounts: Vec<(Address, Account)> = debugger_input
110        .accounts
111        .iter()
112        .map(|a| {
113            let pubkey = Address::from_str(&a.pubkey)
114                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid pubkey: {}", e)))?;
115            let owner = Address::from_str(&a.owner)
116                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid owner: {}", e)))?;
117            let data = if a.data.is_empty() {
118                Vec::new()
119            } else {
120                bs58::decode(&a.data).into_vec().map_err(|e| {
121                    DebuggerError::InvalidInput(format!("Invalid base58 account data: {}", e))
122                })?
123            };
124            Ok((
125                pubkey,
126                Account {
127                    lamports: a.lamports,
128                    data,
129                    owner,
130                    executable: a.executable,
131                    rent_epoch: 0,
132                },
133            ))
134        })
135        .collect::<DebuggerResult<Vec<_>>>()?;
136
137    let programs: Vec<(Address, Vec<u8>)> = debugger_input
138        .programs
139        .iter()
140        .map(|p| {
141            let program_id = Address::from_str(&p.program_id)
142                .map_err(|e| DebuggerError::InvalidInput(format!("Invalid program_id: {}", e)))?;
143            let elf_path = base_dir.join(&p.elf);
144            let elf_bytes = fs::read(&elf_path).map_err(|e| {
145                DebuggerError::InvalidInput(format!(
146                    "Failed to read ELF at {}: {}",
147                    elf_path.display(),
148                    e
149                ))
150            })?;
151            Ok((program_id, elf_bytes))
152        })
153        .collect::<DebuggerResult<Vec<_>>>()?;
154
155    Ok(ParsedInput {
156        instruction,
157        accounts,
158        programs,
159    })
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_parse_empty_input() {
168        let parsed = parse_input("").unwrap();
169        assert!(parsed.accounts.is_empty());
170    }
171
172    #[test]
173    fn test_parse_json_string() {
174        let program_id = Address::new_unique();
175        let account_pubkey = Address::new_unique();
176        let owner = Address::new_unique();
177
178        let json = format!(
179            r#"{{
180                "instruction": {{
181                    "program_id": "{}",
182                    "accounts": [
183                        {{ "pubkey": "{}", "is_signer": true, "is_writable": true }}
184                    ],
185                    "data": "q"
186                }},
187                "accounts": [
188                    {{
189                        "pubkey": "{}",
190                        "owner": "{}",
191                        "lamports": 1000000,
192                        "data": "",
193                        "executable": false
194                    }}
195                ]
196            }}"#,
197            program_id, account_pubkey, account_pubkey, owner
198        );
199
200        let parsed = parse_input(&json).unwrap();
201        assert_eq!(parsed.instruction.program_id, program_id);
202        assert_eq!(parsed.accounts.len(), 1);
203        assert_eq!(parsed.accounts[0].0, account_pubkey);
204    }
205}