1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use std::{env, fs::File, path::Path};

use anyhow::{anyhow, Result};
use console::style;
use lazy_static::lazy_static;
use regex::Regex;

use crate::{config::data::*, program_errors::*};

pub fn parse_solana_config() -> Option<SolanaConfig> {
    let home = if cfg!(unix) {
        env::var_os("HOME").expect("Couldn't find UNIX home key.")
    } else if cfg!(windows) {
        let drive = env::var_os("HOMEDRIVE").expect("Couldn't find Windows home drive key.");
        let path = env::var_os("HOMEPATH").expect("Couldn't find Windows home path key.");
        Path::new(&drive).join(&path).as_os_str().to_owned()
    } else if cfg!(target_os = "macos") {
        env::var_os("HOME").expect("Couldn't find MacOS home key.")
    } else {
        panic!("Unsupported OS!");
    };

    let config_path = Path::new(&home)
        .join(".config")
        .join("solana")
        .join("cli")
        .join("config.yml");

    let conf_file = match File::open(config_path) {
        Ok(f) => f,
        Err(e) => {
            println!(
                "{} {}",
                style("Failed to open Solana config file:").bold().red(),
                style(e).bold().red(),
            );
            std::process::exit(1);
        }
    };
    serde_yaml::from_reader(&conf_file).ok()
}

pub fn path_to_string(path: &Path) -> Result<String> {
    match path.to_str() {
        Some(s) => Ok(s.to_string()),
        None => Err(anyhow!("Couldn't convert path to string.")),
    }
}

pub fn parse_sugar_errors(msg: &str) -> String {
    lazy_static! {
        static ref RE: Regex =
            Regex::new(r"(0x[A-Za-z0-9]+)").expect("Failed to compile parse_client_error regex.");
    }

    let mat = RE.find(msg);

    // If there's an RPC error code match in the message, try to parse it, otherwise return the message back.
    match mat {
        Some(m) => {
            let code = msg[m.start()..m.end()].to_string();
            find_external_program_error(code)
        }
        None => msg.to_owned(),
    }
}

fn find_external_program_error(code: String) -> String {
    let code = code.to_uppercase();

    let parsed_code = if code.contains("0X") {
        code.replace("0X", "")
    } else {
        format!("{:X}", code.parse::<i64>().unwrap())
    };

    if let Some(e) = ANCHOR_ERROR.get(&parsed_code) {
        format!("Anchor Error: {e}")
    } else if let Some(e) = METADATA_ERROR.get(&parsed_code) {
        format!("Token Metadata Error: {e}")
    } else if let Some(e) = CANDY_ERROR.get(&parsed_code) {
        format!("Candy Machine Error: {e}")
    } else {
        format!("Unknown error. Code: {code}")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_sugar_errors() {
        let expected_error = String::from("Candy Machine Error: NoWithdrawWithFrozenFunds");
        let msg = String::from("Error: RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x179e");
        let parsed = parse_sugar_errors(&msg);
        assert_eq!(parsed, expected_error);
    }
}