1
use std::fs::File;
2
use std::path::PathBuf;
3
use std::process::Command;
4
use std::str::FromStr;
5

            
6
use anyhow::{Context, Result};
7
use bonsol_prover::input_resolver::{ProgramInput, ResolvedInput};
8
use bonsol_sdk::instructions::CallbackConfig;
9
use bonsol_sdk::{InputT, InputType, ProgramInputType};
10
use clap::Args;
11
use rand::distributions::Alphanumeric;
12
use rand::Rng;
13
use serde::{Deserialize, Serialize};
14
use solana_rpc_client::nonblocking::rpc_client;
15
use solana_sdk::instruction::AccountMeta;
16
use solana_sdk::pubkey::Pubkey;
17

            
18
use crate::error::{BonsolCliError, ParseConfigError};
19

            
20
pub(crate) const MANIFEST_JSON: &str = "manifest.json";
21
pub(crate) const CARGO_COMMAND: &str = "cargo";
22
pub(crate) const CARGO_TOML: &str = "Cargo.toml";
23
pub(crate) const TARGET_DIR: &str = "target";
24
pub(crate) const CARGO_RISCZERO_VERSION: &str = "1.2.1";
25

            
26
pub fn cargo_has_plugin(plugin_name: &str) -> bool {
27
    Command::new("cargo")
28
        .args(["--list"])
29
        .output()
30
        .map(|output| {
31
            String::from_utf8_lossy(&output.stdout)
32
                .lines()
33
                .any(|line| line.trim().starts_with(plugin_name))
34
        })
35
        .unwrap_or(false)
36
}
37

            
38
pub fn has_executable(executable: &str) -> bool {
39
    Command::new("which")
40
        .arg(executable)
41
        .output()
42
        .map(|output| output.status.success())
43
        .unwrap_or(false)
44
}
45

            
46
#[derive(Debug, Deserialize, Serialize)]
47
#[serde(rename_all = "camelCase")]
48
pub struct ZkProgramManifest {
49
    pub name: String,
50
    pub binary_path: String,
51
    pub image_id: String,
52
    pub input_order: Vec<String>,
53
    pub signature: String,
54
    pub size: u64,
55
}
56

            
57
#[derive(Debug, Deserialize, Serialize, Clone, Args)]
58
#[serde(rename_all = "camelCase")]
59
pub struct CliInput {
60
    pub input_type: String,
61
    pub data: String, // hex encoded if binary with hex: prefix
62
}
63

            
64
#[derive(Debug, Clone)]
65
pub struct CliInputType(InputType);
66
impl ToString for CliInputType {
67
    fn to_string(&self) -> String {
68
        match self.0 {
69
            InputType::PublicData => "PublicData".to_string(),
70
            InputType::PublicAccountData => "PublicAccountData".to_string(),
71
            InputType::PublicUrl => "PublicUrl".to_string(),
72
            InputType::Private => "Private".to_string(),
73
            InputType::PublicProof => "PublicProof".to_string(),
74
            InputType::PrivateLocal => "PrivateUrl".to_string(),
75
            _ => "InvalidInputType".to_string(),
76
        }
77
    }
78
}
79

            
80
impl FromStr for CliInputType {
81
    type Err = anyhow::Error;
82

            
83
14
    fn from_str(s: &str) -> Result<Self, Self::Err> {
84
14
        match s {
85
14
            "PublicData" => Ok(CliInputType(InputType::PublicData)),
86
2
            "PublicAccountData" => Ok(CliInputType(InputType::PublicAccountData)),
87
2
            "PublicUrl" => Ok(CliInputType(InputType::PublicUrl)),
88
            "Private" => Ok(CliInputType(InputType::Private)),
89
            "PublicProof" => Ok(CliInputType(InputType::PublicProof)),
90
            "PrivateUrl" => Ok(CliInputType(InputType::PrivateLocal)),
91
            _ => Err(anyhow::anyhow!("Invalid input type")),
92
        }
93
14
    }
94
}
95

            
96
#[derive(Debug, Clone, Serialize, Deserialize)]
97
#[serde(rename_all = "camelCase")]
98
pub struct ExecutionRequestFile {
99
    pub image_id: Option<String>,
100
    pub execution_config: CliExecutionConfig,
101
    pub execution_id: Option<String>,
102
    pub tip: Option<u64>,
103
    pub expiry: Option<u64>,
104
    pub inputs: Option<Vec<CliInput>>,
105
    pub callback_config: Option<CliCallbackConfig>,
106
}
107

            
108
#[derive(Debug, Clone, Serialize, Deserialize)]
109
#[serde(rename_all = "camelCase")]
110
pub struct CliExecutionConfig {
111
    pub verify_input_hash: Option<bool>,
112
    pub input_hash: Option<String>,
113
    pub forward_output: Option<bool>,
114
}
115

            
116
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
117
#[serde(rename_all = "camelCase")]
118
pub struct CliCallbackConfig {
119
    #[serde(with = "bonsol_sdk::instructions::serde_helpers::optpubkey")]
120
    pub program_id: Option<Pubkey>,
121
    pub instruction_prefix: Option<Vec<u8>>,
122
    pub extra_accounts: Option<Vec<CliAccountMeta>>,
123
}
124

            
125
impl From<CliCallbackConfig> for CallbackConfig {
126
    fn from(val: CliCallbackConfig) -> Self {
127
        CallbackConfig {
128
            program_id: val.program_id.unwrap_or_default(),
129
            instruction_prefix: val.instruction_prefix.unwrap_or_default(),
130
            extra_accounts: val
131
                .extra_accounts
132
                .map(|v| v.into_iter().map(|a| a.into()).collect())
133
                .unwrap_or_default(),
134
        }
135
    }
136
}
137

            
138
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
139
#[serde(rename_all = "camelCase")]
140
pub struct CliAccountMeta {
141
    #[serde(default, with = "bonsol_sdk::instructions::serde_helpers::pubkey")]
142
    pub pubkey: Pubkey,
143
    pub is_signer: bool,
144
    pub is_writable: bool,
145
}
146

            
147
impl From<CliAccountMeta> for AccountMeta {
148
    fn from(val: CliAccountMeta) -> Self {
149
        AccountMeta {
150
            pubkey: val.pubkey,
151
            is_signer: val.is_signer,
152
            is_writable: val.is_writable,
153
        }
154
    }
155
}
156

            
157
#[derive(Debug, Clone, Serialize, Deserialize)]
158
#[serde(rename_all = "camelCase")]
159
pub struct InputFile {
160
    pub inputs: Vec<CliInput>,
161
}
162

            
163
/// Attempt to load the RPC URL and keypair file from a solana `config.yaml`.
164
pub(crate) fn try_load_from_config(config: Option<String>) -> anyhow::Result<(String, String)> {
165
    let whoami = String::from_utf8_lossy(&std::process::Command::new("whoami").output()?.stdout)
166
        .trim_end()
167
        .to_string();
168
    let default_config_path = solana_cli_config::CONFIG_FILE.as_ref();
169

            
170
    let config_file = config.as_ref().map_or_else(
171
        || -> anyhow::Result<&String> {
172
            let inner_err = ParseConfigError::DefaultConfigNotFound {
173
                whoami: whoami.clone(),
174
            };
175
            let context = inner_err.context(None);
176

            
177
            // If no config is given, try to find it at the default location.
178
            default_config_path
179
                .and_then(|s| PathBuf::from_str(s).is_ok_and(|p| p.exists()).then_some(s))
180
                .ok_or(BonsolCliError::ParseConfigError(inner_err))
181
                .context(context)
182
        },
183
        |config| -> anyhow::Result<&String> {
184
            // Here we throw an error if the user provided a path to a config that does not exist.
185
            // Instead of using the default location, it's better to show the user the path they
186
            // expected to use was not valid.
187
            if !PathBuf::from_str(config)?.exists() {
188
                let inner_err = ParseConfigError::ConfigNotFound {
189
                    path: config.into(),
190
                };
191
                let context = inner_err.context(None);
192
                let err: anyhow::Error = BonsolCliError::ParseConfigError(inner_err).into();
193
                return Err(err.context(context));
194
            }
195
            Ok(config)
196
        },
197
    )?;
198
    let config = {
199
        let mut inner_err = ParseConfigError::Uninitialized;
200

            
201
        let mut maybe_config = solana_cli_config::Config::load(config_file).map_err(|err| {
202
            let err = ParseConfigError::FailedToLoad {
203
                path: config.unwrap_or(default_config_path.cloned().unwrap()),
204
                err: format!("{err:?}"),
205
            };
206
            inner_err = err.clone();
207
            BonsolCliError::ParseConfigError(err).into()
208
        });
209
        if maybe_config.is_err() {
210
            maybe_config = maybe_config.context(inner_err.context(Some(whoami)));
211
        }
212
        maybe_config
213
    }?;
214
    Ok((config.json_rpc_url, config.keypair_path))
215
}
216

            
217
pub(crate) fn load_solana_config(
218
    config: Option<String>,
219
    rpc_url: Option<String>,
220
    keypair: Option<String>,
221
) -> anyhow::Result<(String, solana_sdk::signer::keypair::Keypair)> {
222
    let (rpc_url, keypair_file) = match rpc_url.zip(keypair) {
223
        Some(config) => config,
224
        None => try_load_from_config(config)?,
225
    };
226
    Ok((
227
        rpc_url,
228
        solana_sdk::signature::read_keypair_file(std::path::Path::new(&keypair_file)).map_err(
229
            |err| BonsolCliError::FailedToReadKeypair {
230
                file: keypair_file,
231
                err: format!("{err:?}"),
232
            },
233
        )?,
234
    ))
235
}
236

            
237
pub async fn sol_check(rpc_client: String, pubkey: Pubkey) -> bool {
238
    let rpc_client = rpc_client::RpcClient::new(rpc_client);
239
    if let Ok(account) = rpc_client.get_account(&pubkey).await {
240
        return account.lamports > 0;
241
    }
242
    false
243
}
244

            
245
pub fn execute_get_inputs(
246
    inputs_file: Option<String>,
247
    stdin: Option<String>,
248
) -> Result<Vec<CliInput>> {
249
    if let Some(std) = stdin {
250
        let parsed = serde_json::from_str::<InputFile>(&std)
251
            .map_err(|e| anyhow::anyhow!("Error parsing stdin: {:?}", e))?;
252
        return Ok(parsed.inputs);
253
    }
254

            
255
    if let Some(istr) = inputs_file {
256
        let ifile = File::open(istr)?;
257
        let parsed: InputFile = serde_json::from_reader(&ifile)
258
            .map_err(|e| anyhow::anyhow!("Error parsing inputs file: {:?}", e))?;
259
        return Ok(parsed.inputs);
260
    }
261

            
262
    Err(anyhow::anyhow!("No inputs provided"))
263
}
264

            
265
pub fn proof_get_inputs(
266
    inputs_file: Option<String>,
267
    stdin: Option<String>,
268
) -> Result<Vec<ProgramInput>> {
269
    if let Some(std) = stdin {
270
        return proof_parse_stdin(&std);
271
    }
272
    if let Some(istr) = inputs_file {
273
        return proof_parse_input_file(&istr);
274
    }
275
    Err(anyhow::anyhow!("No inputs provided"))
276
}
277

            
278
12
pub fn execute_transform_cli_inputs(inputs: Vec<CliInput>) -> Result<Vec<InputT>> {
279
12
    let mut res = vec![];
280
14
    for input in inputs.into_iter() {
281
14
        let input_type = CliInputType::from_str(&input.input_type)?.0;
282
14
        match input_type {
283
            InputType::PublicData => {
284
12
                if input.data.starts_with("0x") {
285
3
                    let (is_valid, data) = is_valid_hex(&input.data[2..]);
286
3
                    if is_valid {
287
2
                        res.push(InputT::public(data));
288
2
                    } else {
289
1
                        return Err(anyhow::anyhow!(
290
1
                            "Invalid hex string for PublicData: {}",
291
1
                            input.data
292
1
                        ));
293
                    }
294
9
                } else if let Some(n) = is_valid_number(&input.data) {
295
7
                    let data = n.into_bytes();
296
7
                    res.push(InputT::public(data));
297
7
                } else {
298
2
                    return Err(anyhow::anyhow!(
299
2
                        "PublicData input \'{}\' is not a valid number (i64, u64, f64) or 0x-prefixed hex string",
300
2
                        input.data
301
2
                    ));
302
                }
303
            }
304
2
            _ => {
305
2
                res.push(InputT::new(input_type, Some(input.data.into_bytes())));
306
2
            }
307
        }
308
    }
309
9
    Ok(res)
310
12
}
311

            
312
4
fn is_valid_hex(s: &str) -> (bool, Vec<u8>) {
313
4
    if s.len() % 2 != 0 {
314
1
        return (false, vec![]);
315
3
    }
316
68
    let is_hex_char = |c: char| c.is_ascii_hexdigit();
317
3
    if !s.chars().all(is_hex_char) {
318
        return (false, vec![]);
319
3
    }
320
3
    let out = hex::decode(s);
321
3
    (out.is_ok(), out.unwrap_or_default())
322
4
}
323

            
324
#[derive(Debug, PartialEq)]
325
pub enum NumberType {
326
    Float(f64),
327
    Unsigned(u64),
328
    Integer(i64),
329
    // TODO: add BigInt
330
}
331

            
332
impl NumberType {
333
7
    fn into_bytes(&self) -> Vec<u8> {
334
7
        match self {
335
1
            NumberType::Float(f) => f.to_le_bytes().to_vec(),
336
3
            NumberType::Unsigned(u) => u.to_le_bytes().to_vec(),
337
3
            NumberType::Integer(i) => i.to_le_bytes().to_vec(),
338
        }
339
7
    }
340
}
341

            
342
14
fn is_valid_number(s: &str) -> Option<NumberType> {
343
14
    if let Ok(num) = s.parse::<u64>() {
344
4
        return Some(NumberType::Unsigned(num));
345
10
    }
346
10
    if let Ok(num) = s.parse::<i64>() {
347
4
        return Some(NumberType::Integer(num));
348
6
    }
349
6
    if let Ok(num) = s.parse::<f64>() {
350
2
        return Some(NumberType::Float(num));
351
4
    }
352
4
    None
353
14
}
354

            
355
6
fn proof_parse_entry(index: u8, s: &str, input_type_str: &str) -> Result<ProgramInput> {
356
6
    let program_input_type = match input_type_str.to_lowercase().as_str() {
357
6
        "public" | "publicdata" => ProgramInputType::Public,
358
        "private" | "privatedata" => ProgramInputType::Private,
359
        _ => ProgramInputType::Public,
360
    };
361

            
362
6
    if let Ok(num) = s.parse::<i64>() {
363
2
        return Ok(ProgramInput::Resolved(ResolvedInput {
364
2
            index,
365
2
            data: num.to_le_bytes().to_vec(),
366
2
            input_type: program_input_type,
367
2
        }));
368
4
    }
369
4
    if let Ok(num) = s.parse::<f64>() {
370
1
        return Ok(ProgramInput::Resolved(ResolvedInput {
371
1
            index,
372
1
            data: num.to_le_bytes().to_vec(),
373
1
            input_type: program_input_type,
374
1
        }));
375
3
    }
376
3
    if let Ok(num) = s.parse::<u64>() {
377
        return Ok(ProgramInput::Resolved(ResolvedInput {
378
            index,
379
            data: num.to_le_bytes().to_vec(),
380
            input_type: program_input_type,
381
        }));
382
3
    }
383
3
    let has_hex_prefix = s.starts_with("0x");
384
3
    if has_hex_prefix {
385
1
        let (is_valid, data) = is_valid_hex(&s[2..]);
386
1
        if is_valid {
387
1
            return Ok(ProgramInput::Resolved(ResolvedInput {
388
1
                index,
389
1
                data,
390
1
                input_type: program_input_type,
391
1
            }));
392
        } else {
393
            return Err(anyhow::anyhow!("Invalid hex data"));
394
        }
395
2
    }
396
2
    return Ok(ProgramInput::Resolved(ResolvedInput {
397
2
        index,
398
2
        data: s.as_bytes().to_vec(),
399
2
        input_type: program_input_type,
400
2
    }));
401
6
}
402

            
403
fn proof_parse_input_file(input_file_path: &str) -> Result<Vec<ProgramInput>> {
404
    println!("[BONSOL_DEBUG] proof_parse_input_file: Attempting to read input file: '{}'", input_file_path);
405
    let file_content_str = match std::fs::read_to_string(input_file_path) {
406
        Ok(content) => {
407
            println!("[BONSOL_DEBUG] proof_parse_input_file: Successfully read file. Content length: {}. First 100 chars: {:?}", content.len(), content.chars().take(100).collect::<String>());
408
            // To see all bytes, which can reveal BOMs or other non-printable chars:
409
            // println!("[BONSOL_DEBUG] proof_parse_input_file: File content as bytes: {:?}", content.as_bytes());
410
            content
411
        }
412
        Err(e) => {
413
            println!("[BONSOL_DEBUG] proof_parse_input_file: Failed to read file: {:?}", e);
414
            return Err(e).with_context(|| format!("Failed to read input file at path: {}", input_file_path));
415
        }
416
    };
417

            
418
    println!("[BONSOL_DEBUG] proof_parse_input_file: Attempting to deserialize JSON from file content...");
419
    match serde_json::from_str::<InputFile>(&file_content_str) {
420
        Ok(ifile) => {
421
            println!("[BONSOL_DEBUG] proof_parse_input_file: Successfully deserialized JSON into InputFile struct.");
422
            let len = ifile.inputs.len();
423
            println!("[BONSOL_DEBUG] proof_parse_input_file: Number of input entries in JSON: {}", len);
424
            
425
            let mut parsed_inputs_accumulator: Vec<ProgramInput> = Vec::new();
426
            for (index, cli_input_item) in ifile.inputs.into_iter().enumerate() {
427
                println!("[BONSOL_DEBUG] proof_parse_input_file: Processing entry {}: inputType='{}', data='{}'", index, cli_input_item.input_type, cli_input_item.data);
428
                match proof_parse_entry(index as u8, &cli_input_item.data, &cli_input_item.input_type) {
429
                    Ok(program_input) => {
430
                        println!("[BONSOL_DEBUG] proof_parse_input_file: Successfully parsed entry {}", index);
431
                        parsed_inputs_accumulator.push(program_input);
432
                    }
433
                    Err(e) => {
434
                        println!("[BONSOL_DEBUG] proof_parse_input_file: Failed to parse entry {}: {:?}", index, e);
435
                        // Return a more specific error including which entry failed if possible
436
                        return Err(anyhow::anyhow!("Invalid input file (entry {} failed to parse: {})", index, e));
437
                    }
438
                }
439
            }
440
            
441
            // This check is essentially done by the loop returning an error on first failure.
442
            // if parsed_inputs_accumulator.len() != len {
443
            //     println!("[BONSOL_DEBUG] proof_parse_input_file: Mismatch in parsed entries count. Expected: {}, Got: {}", len, parsed_inputs_accumulator.len());
444
            //     return Err(anyhow::anyhow!("Invalid input file (an entry failed to parse - count mismatch)"));
445
            // }
446
            println!("[BONSOL_DEBUG] proof_parse_input_file: All entries processed successfully.");
447
            return Ok(parsed_inputs_accumulator);
448
        }
449
        Err(e) => {
450
            println!("[BONSOL_DEBUG] proof_parse_input_file: JSON deserialization failed: {:?}", e);
451
            let snippet_len = std::cmp::min(file_content_str.len(), 200); // Show a snippet of the problematic string
452
            println!("[BONSOL_DEBUG] proof_parse_input_file: JSON parsing failed on (first {} chars): <{}>", snippet_len, &file_content_str[..snippet_len]);
453
            // Return a more specific error including the serde error
454
            return Err(anyhow::anyhow!("Invalid input file (JSON deserialization failed: {})", e));
455
        }
456
    }
457
}
458

            
459
1
fn proof_parse_stdin(input: &str) -> Result<Vec<ProgramInput>> {
460
1
    let mut entries = Vec::new();
461
1
    let mut current_entry = String::new();
462
1
    let mut in_quotes = false;
463
1
    let mut in_brackets = 0;
464
93
    for c in input.chars() {
465
2
        match c {
466
2
            '"' if !in_quotes => in_quotes = true,
467
2
            '"' if in_quotes => in_quotes = false,
468
1
            '{' | '[' if !in_quotes => in_brackets += 1,
469
1
            '}' | ']' if !in_quotes => in_brackets -= 1,
470
5
            ' ' if !in_quotes && in_brackets == 0 && !current_entry.is_empty() => {
471
5
                let index = entries.len() as u8;
472
5
                entries.push(proof_parse_entry(index, &current_entry, "PublicData")?);
473
5
                current_entry.clear();
474
5
                continue;
475
            }
476
82
            _ => {}
477
        }
478
88
        current_entry.push(c);
479
    }
480
1
    if !current_entry.is_empty() {
481
1
        entries.push(proof_parse_entry(entries.len() as u8, &current_entry, "PublicData")?);
482
    }
483
1
    Ok(entries)
484
1
}
485

            
486
pub fn rand_id(chars: usize) -> String {
487
    let mut rng = rand::thread_rng();
488
    (&mut rng)
489
        .sample_iter(Alphanumeric)
490
        .take(chars)
491
        .map(char::from)
492
        .collect()
493
}
494

            
495
#[cfg(test)]
496
mod test {
497
    use super::*;
498
    use hex;
499

            
500
    #[test]
501
1
    fn test_proof_parse_stdin() {
502
1
        let inputs = r#"1234567890abcdef 0x313233343536373839313061626364656667 2.1 2000 -2000 {"attestation":"test"}"#;
503
1
        let inputs_parsed = proof_parse_stdin(inputs).unwrap();
504
1

            
505
1
        let expected_inputs = vec![
506
1
            ProgramInput::Resolved(ResolvedInput {
507
1
                index: 0,
508
1
                data: "1234567890abcdef".as_bytes().to_vec(),
509
1
                input_type: ProgramInputType::Public,
510
1
            }),
511
1
            ProgramInput::Resolved(ResolvedInput {
512
1
                index: 1,
513
1
                data: hex::decode("313233343536373839313061626364656667").unwrap(),
514
1
                input_type: ProgramInputType::Public,
515
1
            }),
516
1
            ProgramInput::Resolved(ResolvedInput {
517
1
                index: 2,
518
1
                data: 2.1f64.to_le_bytes().to_vec(),
519
1
                input_type: ProgramInputType::Public,
520
1
            }),
521
1
            ProgramInput::Resolved(ResolvedInput {
522
1
                index: 3,
523
1
                data: 2000u64.to_le_bytes().to_vec(),
524
1
                input_type: ProgramInputType::Public,
525
1
            }),
526
1
            ProgramInput::Resolved(ResolvedInput {
527
1
                index: 4,
528
1
                data: (-2000i64).to_le_bytes().to_vec(),
529
1
                input_type: ProgramInputType::Public,
530
1
            }),
531
1
            ProgramInput::Resolved(ResolvedInput {
532
1
                index: 5,
533
1
                data: "{\"attestation\":\"test\"}".as_bytes().to_vec(),
534
1
                input_type: ProgramInputType::Public,
535
1
            }),
536
1
        ];
537
1
        assert_eq!(inputs_parsed, expected_inputs);
538
1
    }
539

            
540
    #[test]
541
1
    fn test_is_valid_number() {
542
1
        let num = is_valid_number("1234567890abcdef");
543
1
        assert!(num.is_none());
544
1
        let num = is_valid_number("1234567890abcdefg");
545
1
        assert!(num.is_none());
546
1
        let num = is_valid_number("2.1");
547
1
        assert!(num.is_some());
548
1
        assert_eq!(num.unwrap(), NumberType::Float(2.1));
549
1
        let num = is_valid_number("2000");
550
1
        assert!(num.is_some());
551
1
        assert_eq!(num.unwrap(), NumberType::Unsigned(2000));
552
1
        let num = is_valid_number("-2000");
553
1
        assert!(num.is_some());
554
1
        assert_eq!(num.unwrap(), NumberType::Integer(-2000));
555
1
    }
556

            
557
    #[test]
558
1
    fn test_execute_transform_cli_inputs() {
559
1
        // Case 1: Invalid PublicData (non-numeric, non-hex string) - Should now be an error
560
1
        let invalid_public_data_str = CliInput {
561
1
            input_type: "PublicData".to_string(),
562
1
            data: "1234567890abcdef".to_string(), // This was the old first test case
563
1
        };
564
1
        assert!(execute_transform_cli_inputs(vec![invalid_public_data_str]).is_err());
565

            
566
        // Case 2: Valid hex PublicData
567
1
        let hex_input = CliInput {
568
1
            input_type: "PublicData".to_string(),
569
1
            data: "0x0102030405060708".to_string(), // Using a clear 8-byte hex for i64 tests later
570
1
        };
571
1
        let expected_hex_bytes = hex::decode("0102030405060708").unwrap();
572
1
        assert_eq!(
573
1
            execute_transform_cli_inputs(vec![hex_input]).unwrap(),
574
1
            vec![InputT::public(expected_hex_bytes)]
575
1
        );
576

            
577
        // Case 3: PublicData with f64 string
578
1
        let float_input = CliInput {
579
1
            input_type: "PublicData".to_string(),
580
1
            data: "2.1".to_string(),
581
1
        };
582
1
        assert_eq!(
583
1
            execute_transform_cli_inputs(vec![float_input]).unwrap(),
584
1
            vec![InputT::public(2.1f64.to_le_bytes().to_vec())]
585
1
        );
586

            
587
        // Case 4: PublicData with u64 string
588
1
        let u64_input = CliInput {
589
1
            input_type: "PublicData".to_string(),
590
1
            data: "2000".to_string(),
591
1
        };
592
1
        assert_eq!(
593
1
            execute_transform_cli_inputs(vec![u64_input]).unwrap(),
594
1
            vec![InputT::public(2000u64.to_le_bytes().to_vec())]
595
1
        );
596

            
597
        // Case 5: PublicData with i64 string (negative)
598
1
        let i64_neg_input = CliInput {
599
1
            input_type: "PublicData".to_string(),
600
1
            data: "-2000".to_string(),
601
1
        };
602
1
        assert_eq!(
603
1
            execute_transform_cli_inputs(vec![i64_neg_input]).unwrap(),
604
1
            vec![InputT::public((-2000i64).to_le_bytes().to_vec())]
605
1
        );
606

            
607
        // Case 6: New - PublicData with positive i64 string
608
1
        let i64_pos_input = CliInput {
609
1
            input_type: "PublicData".to_string(),
610
1
            data: "123".to_string(),
611
1
        };
612
1
        assert_eq!(
613
1
            execute_transform_cli_inputs(vec![i64_pos_input]).unwrap(),
614
1
            vec![InputT::public(123i64.to_le_bytes().to_vec())]
615
1
        );
616

            
617
        // Case 7: New - PublicData with another negative i64 string
618
1
        let i64_neg2_input = CliInput {
619
1
            input_type: "PublicData".to_string(),
620
1
            data: "-456".to_string(),
621
1
        };
622
1
        assert_eq!(
623
1
            execute_transform_cli_inputs(vec![i64_neg2_input]).unwrap(),
624
1
            vec![InputT::public((-456i64).to_le_bytes().to_vec())]
625
1
        );
626

            
627
        // Case 8: New - PublicData with invalid string (non-numeric, non-hex) - explicit test
628
1
        let invalid_str_input = CliInput {
629
1
            input_type: "PublicData".to_string(),
630
1
            data: "hello".to_string(),
631
1
        };
632
1
        assert!(execute_transform_cli_inputs(vec![invalid_str_input]).is_err());
633

            
634
        // Case 9: New - PublicData with invalid hex string
635
1
        let invalid_hex_input = CliInput {
636
1
            input_type: "PublicData".to_string(),
637
1
            data: "0xNOTAHEX".to_string(),
638
1
        };
639
1
        assert!(execute_transform_cli_inputs(vec![invalid_hex_input]).is_err());
640

            
641
        // Case 10: New - Non-PublicData type (e.g., PublicUrl) - should pass through string as bytes
642
1
        let public_url_input = CliInput {
643
1
            input_type: "PublicUrl".to_string(), // Assuming PublicUrl is a valid CliInputType string
644
1
            data: "mytesturl".to_string(),
645
1
        };
646
1
        // Need to ensure InputT::new for PublicUrl results in the correct InputType enum variant
647
1
        // For this, we might need to know the mapping or have CliInputType also in scope for direct construction.
648
1
        // Assuming CliInputType::from_str("PublicUrl").unwrap().0 gives InputType::PublicUrl
649
1
        assert_eq!(
650
1
            execute_transform_cli_inputs(vec![public_url_input]).unwrap(),
651
1
            vec![InputT::new(InputType::PublicUrl, Some("mytesturl".as_bytes().to_vec()))]
652
1
        );
653
        
654
        // Test with multiple inputs
655
1
        let inputs_multiple = vec![
656
1
            CliInput { input_type: "PublicData".to_string(), data: "3".to_string() },
657
1
            CliInput { input_type: "PublicData".to_string(), data: "0x0a00000000000000".to_string() }, // 10 as hex i64 LE
658
1
            CliInput { input_type: "PublicUrl".to_string(), data: "test.com".to_string() },
659
1
            CliInput { input_type: "PublicData".to_string(), data: "-5".to_string() },
660
1
        ];
661
1
        let parsed_multiple = execute_transform_cli_inputs(inputs_multiple).unwrap();
662
1
        assert_eq!(
663
1
            parsed_multiple,
664
1
            vec![
665
1
                InputT::public(3i64.to_le_bytes().to_vec()),
666
1
                InputT::public(hex::decode("0a00000000000000").unwrap()),
667
1
                InputT::new(InputType::PublicUrl, Some("test.com".as_bytes().to_vec())),
668
1
                InputT::public((-5i64).to_le_bytes().to_vec()),
669
1
            ]
670
1
        );
671

            
672
        // Test with an empty input vector
673
1
        let empty_inputs: Vec<CliInput> = Vec::new();
674
1
        assert_eq!(execute_transform_cli_inputs(empty_inputs).unwrap(), Vec::new());
675
1
    }
676
}