use crate::ast_types::*;
use crate::ast_walker::AstVisitor;
use crate::bytecode::Severity;
use tracing::debug;
#[derive(Debug, Clone)]
struct Violation;
pub struct FlashLoanDetector<'a> {
source: &'a str,
file_name: &'a str,
pub violations: Vec<Violation>,
balance_oracle_calls: Vec<(usize, String)>, }
impl<'a> FlashLoanDetector<'a> {
pub fn new(source: &'a str, file_name: &'a str) -> Self {
Self {
source,
file_name,
violations: Vec::new(),
balance_oracle_calls: Vec::new(),
}
}
fn is_balance_oracle_read(call: &FunctionCall) -> bool {
if let AstNode::MemberAccess(member) = call.expression.as_ref() {
return matches!(
member.member_name.as_str(),
"balanceOf" | "getReserves" | "totalSupply" | "reserve0" | "reserve1"
);
}
false
}
fn is_price_computation_call(call: &FunctionCall) -> bool {
if let AstNode::MemberAccess(member) = call.expression.as_ref() {
let name = member.member_name.to_lowercase();
name.contains("price") || name.contains("rate") || name.contains("oracle")
} else {
false
}
}
}
impl<'a> AstVisitor for FlashLoanDetector<'a> {
fn visit_function_call(
&mut self,
call: &FunctionCall,
func: &FunctionDefinition,
contract: &str,
) {
let (offset, _, _) = parse_src(&call.src);
let call_line_num = offset_to_line(self.source, offset);
if Self::is_balance_oracle_read(call) {
self.balance_oracle_calls
.push((call_line_num, func.name.clone()));
}
if Self::is_price_computation_call(call) && !self.balance_oracle_calls.is_empty() {
for &(balance_call_line, ref balance_func_name) in &self.balance_oracle_calls {
if balance_call_line < call_line_num && balance_func_name == &func.name {
let _lines: Vec<&str> = self.source.lines().collect();
debug!(
"Flash loan vulnerability detected in {}::{} at line {}",
contract, func.name, call_line_num
);
}
}
}
}
}
fn extract_context(lines: &[&str], line_num: usize) -> String {
let line_idx = line_num.saturating_sub(1);
let start = line_idx.saturating_sub(1);
let end = std::cmp::min(line_idx + 2, lines.len());
let mut context = String::new();
for i in start..end {
if i < lines.len() {
let marker = if i == line_idx { ">>> " } else { " " };
context.push_str(&format!("{}{:4} {}\n", marker, i + 1, lines[i]));
}
}
context
}