bat-cli 0.8.6

Blockchain Auditor Toolkit (BAT)
use crate::batbelt::metadata::structs_source_code_metadata::StructMetadataType;
use crate::batbelt::metadata::{BatMetadata, BatMetadataParser};
use crate::batbelt::parser::{ParserError, ParserResult};
use crate::batbelt::sonar::SonarResult;
use error_stack::{IntoReport, Report, Result, ResultExt};
use regex::Regex;
use serde::{Deserialize, Serialize};

#[derive(
    Debug,
    PartialEq,
    Clone,
    Copy,
    strum_macros::Display,
    strum_macros::EnumIter,
    Serialize,
    Deserialize,
)]
pub enum SolanaAccountType {
    TokenAccount,
    Mint,
    Signer,
    UncheckedAccount,
    SystemAccount,
    ProgramStateAccount,
    Other,
}

impl SolanaAccountType {
    pub fn from_context_account_content(
        context_account_content: &str,
    ) -> Result<Self, ParserError> {
        let last_line = context_account_content.lines();
        let last_line = last_line.last().unwrap();

        if last_line.contains("Signer<") {
            return Ok(Self::Signer);
        }

        if last_line.contains(&Self::UncheckedAccount.to_string()) {
            return Ok(Self::UncheckedAccount);
        }

        if last_line.contains(&Self::SystemAccount.to_string()) {
            return Ok(Self::SystemAccount);
        }

        if last_line.contains(&Self::TokenAccount.to_string()) {
            return Ok(Self::TokenAccount);
        }

        if last_line.contains(&Self::Mint.to_string()) {
            return Ok(Self::Mint);
        }

        let mut solana_accounts_metadata = BatMetadata::read_metadata()
            .change_context(ParserError)?
            .source_code
            .structs_source_code
            .into_iter()
            .filter(|s_metda| s_metda.struct_type == StructMetadataType::SolanaAccount);
        if solana_accounts_metadata.any(|solana_account| last_line.contains(&solana_account.name)) {
            return Ok(Self::ProgramStateAccount);
        }

        Ok(Self::Other)
    }
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct SolanaAccountParserAccount {
    pub account_name: String,
    pub account_type: String,
}

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct SolanaAccountParser {
    pub solana_account_type: SolanaAccountType,
    pub account_struct_name: String,
    pub accounts: Vec<SolanaAccountParserAccount>,
}

impl SolanaAccountParser {
    pub fn new_from_struct_name_and_solana_account_type(
        account_struct_name: String,
        solana_account_type: SolanaAccountType,
    ) -> ParserResult<Self> {
        let mut new_solana_account_parser = Self {
            solana_account_type,
            account_struct_name,
            accounts: vec![],
        };
        match solana_account_type {
            SolanaAccountType::ProgramStateAccount => {
                new_solana_account_parser.parse_program_state_account()?;
            }
            _ => unimplemented!(),
        }
        Ok(new_solana_account_parser)
    }

    fn parse_program_state_account(&mut self) -> ParserResult<()> {
        let bat_metadata = BatMetadata::read_metadata().change_context(ParserError)?;
        return match bat_metadata
            .source_code
            .structs_source_code
            .into_iter()
            .find(|struct_sc| {
                struct_sc.struct_type == StructMetadataType::SolanaAccount
                    && struct_sc.name == self.account_struct_name
            }) {
            None => Err(Report::new(ParserError).attach_printable(format!(
                "No Solana Account was found with name {}",
                self.account_struct_name
            ))),
            Some(struct_metadata) => {
                let account_param_regex = Regex::new(r"pub [A-Za-z0-9_]+: [\w;\[\] ]+,")
                    .into_report()
                    .change_context(ParserError)?;
                let struct_metadata_content = struct_metadata
                    .to_source_code_parser(None)
                    .get_source_code_content();
                let account_vec = struct_metadata_content
                    .lines()
                    .filter_map(|line| {
                        if account_param_regex.is_match(line) {
                            let mut line_split = line
                                .trim()
                                .trim_end_matches(',')
                                .trim_start_matches("pub ")
                                .split(": ");
                            Some(SolanaAccountParserAccount {
                                account_name: line_split.next().unwrap().to_string(),
                                account_type: line_split.next().unwrap().to_string(),
                            })
                        } else {
                            None
                        }
                    })
                    .collect::<Vec<_>>();
                self.accounts = account_vec;
                Ok(())
            }
        };
    }
}