Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/rules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use optimization::storage::{
pub use rule_engine::{
extract_struct_fields, find_variable_usage, Rule, RuleEngine, RuleViolation, ViolationSeverity,
};
pub use security::{HardcodedAddressesRule, MissingDomainSeparationRule};
pub use security::{HardcodedAddressesRule, MissingDomainSeparationRule, defi::MissingSlippageValidationRule};
pub use solidity::{StateVariablePackingRule, MappingIterationRule};
pub use optimization::storage::detect_mapping_iteration;
pub use unused_state_variables::UnusedStateVariablesRule;
Expand Down
201 changes: 201 additions & 0 deletions packages/rules/src/security/defi/missing_slippage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Detect Missing Slippage Validation
//!
//! # Overview
//! This module implements the `MissingSlippageValidationRule`, which flags Decentralized Finance (DeFi)
//! operations that lack slippage protections. In Automated Market Makers (AMMs) and DEX environments,
//! operations such as token swaps or liquidity modifications are vulnerable to price volatility.
//!
//! # Rationale
//! Without parameters specifying an acceptable limit (e.g., a minimum amount received), a user's transaction
//! could be manipulated by MEV searchers (e.g., via sandwich attacks or front-running), resulting in a
//! substantial loss of funds. This rule ensures that developers implement proper parameter-level checks for limits.

use crate::rule_engine::{Rule, RuleViolation, ViolationSeverity};
use syn::{FnArg, ImplItem, Item, Pat, PatType, Signature};

/// Rule for detecting missing slippage protection in DeFi contracts.
///
/// This rule iterates over Abstract Syntax Tree (AST) items (both standalone functions and methods in `impl` blocks).
/// It looks for common DeFi terminology in function names and validates that their signatures contain
/// parameters explicitly intended to manage slippage (e.g., `min_out`, `limit`).
pub struct MissingSlippageValidationRule;

impl MissingSlippageValidationRule {
/// Determines whether a given function name signifies a DeFi trading or liquidity operation.
///
/// # Logic
/// We use a heuristic-based lexical analysis, checking if the function name (in lowercase)
/// contains predefined keywords indicative of DeFi interactions.
///
/// # Justification
/// Standardizing naming conventions allows static analysis to catch most typical DeFi patterns
/// (`swap`, `add_liquidity`, etc.) without requiring complex behavioral or flow analysis.
fn is_defi_operation(name: &str) -> bool {
let name = name.to_lowercase();
name.contains("swap")
|| name.contains("trade")
|| name.contains("exchange")
|| name.contains("add_liquidity")
|| name.contains("remove_liquidity")
|| name.contains("buy")
|| name.contains("sell")
}

/// Determines whether the function's parameter list includes slippage protection limits.
///
/// # Logic
/// Iterates through each argument in the function signature. If an argument is a standard typed pattern
/// (`FnArg::Typed`), it extracts the identifier name and checks it against a list of known slippage keywords
/// (e.g., `min_amount`, `min_out`, `slippage`).
///
/// # Justification
/// Slippage must be passed as an argument by the caller. If none of the function's parameters imply a limit,
/// the function body cannot securely validate slippage against user-defined constraints.
fn has_slippage_parameter(sig: &Signature) -> bool {
for arg in &sig.inputs {
if let FnArg::Typed(PatType { pat, .. }) = arg {
if let Pat::Ident(pat_ident) = &**pat {
let param_name = pat_ident.ident.to_string().to_lowercase();
if param_name.contains("min_amount")
|| param_name.contains("min_out")
|| param_name.contains("limit")
|| param_name.contains("slippage")
|| param_name.contains("min_recv")
|| param_name.contains("amount_out_min")
{
return true;
}
}
}
}
false
}
}

impl Rule for MissingSlippageValidationRule {
fn name(&self) -> &str {
"missing-slippage-validation"
}

fn description(&self) -> &str {
"Detects DeFi operations (e.g., swaps or liquidity provision) lacking slippage limit parameters. \
Without slippage limits, transactions are vulnerable to front-running and sandwich attacks, \
potentially resulting in user fund loss."
}

fn check(&self, ast: &[Item]) -> Vec<RuleViolation> {
let mut violations = Vec::new();

for item in ast {
match item {
Item::Fn(item_fn) => {
let fn_name = item_fn.sig.ident.to_string();
if Self::is_defi_operation(&fn_name) && !Self::has_slippage_parameter(&item_fn.sig) {
violations.push(RuleViolation {
rule_name: self.name().to_string(),
description: format!("Function '{}' lacks a slippage protection parameter (e.g., min_amount_out).", fn_name),
severity: ViolationSeverity::High,
line_number: 0,
column_number: 0,
variable_name: fn_name.clone(),
suggestion: "Add a slippage limit parameter to protect against front-running and sandwich attacks.".to_string(),
});
}
}
Item::Impl(item_impl) => {
for impl_item in &item_impl.items {
if let ImplItem::Fn(impl_fn) = impl_item {
let fn_name = impl_fn.sig.ident.to_string();
if Self::is_defi_operation(&fn_name) && !Self::has_slippage_parameter(&impl_fn.sig) {
violations.push(RuleViolation {
rule_name: self.name().to_string(),
description: format!("Function '{}' lacks a slippage protection parameter (e.g., min_amount_out).", fn_name),
severity: ViolationSeverity::High,
line_number: 0,
column_number: 0,
variable_name: fn_name.clone(),
suggestion: "Add a slippage limit parameter to protect against front-running and sandwich attacks.".to_string(),
});
}
}
}
}
_ => {}
}
}

violations
}
}

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

#[test]
fn test_missing_slippage_in_swap() {
let code = r#"
pub struct Dex;
impl Dex {
pub fn swap(amount_in: u64, path: Vec<Address>) {
// swap logic without slippage check
}
}
"#;
let ast = parse_file(code).unwrap();
let rule = MissingSlippageValidationRule;
let violations = rule.check(&ast.items);

assert_eq!(violations.len(), 1);
assert_eq!(violations[0].severity, ViolationSeverity::High);
assert_eq!(violations[0].variable_name, "swap");
assert!(violations[0].description.contains("lacks a slippage protection parameter"));
}

#[test]
fn test_has_slippage_in_swap() {
let code = r#"
pub struct Dex;
impl Dex {
pub fn swap(amount_in: u64, min_amount_out: u64, path: Vec<Address>) {
// swap logic with slippage check
}
}
"#;
let ast = parse_file(code).unwrap();
let rule = MissingSlippageValidationRule;
let violations = rule.check(&ast.items);

assert_eq!(violations.len(), 0);
}

#[test]
fn test_missing_slippage_in_add_liquidity() {
let code = r#"
pub fn add_liquidity(token_a: Address, token_b: Address, amount_a: u64, amount_b: u64) {
// add liquidity logic
}
"#;
let ast = parse_file(code).unwrap();
let rule = MissingSlippageValidationRule;
let violations = rule.check(&ast.items);

assert_eq!(violations.len(), 1);
assert_eq!(violations[0].variable_name, "add_liquidity");
}

#[test]
fn test_not_a_defi_operation() {
let code = r#"
pub fn transfer(to: Address, amount: u64) {
// simple transfer
}
"#;
let ast = parse_file(code).unwrap();
let rule = MissingSlippageValidationRule;
let violations = rule.check(&ast.items);

assert_eq!(violations.len(), 0);
}
}
3 changes: 3 additions & 0 deletions packages/rules/src/security/defi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod missing_slippage;

pub use missing_slippage::MissingSlippageValidationRule;
2 changes: 2 additions & 0 deletions packages/rules/src/security/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pub mod configuration;
pub mod signatures;
pub mod emergency;
pub mod defi;

pub use configuration::HardcodedAddressesRule;
pub use signatures::MissingDomainSeparationRule;
pub use emergency::MissingCircuitBreakerRule;
pub use defi::MissingSlippageValidationRule;
Loading