use crate::token_standards::{
EventParameter, EventSignature, FunctionSignature, Parameter, TokenStandard,
};
use crate::{Finding, Result, Severity};
use regex::Regex;
#[derive(Debug, Clone)]
pub struct StablecoinValidator;
impl StablecoinValidator {
pub fn new() -> Self {
Self
}
pub fn validate_stablecoin(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
findings.extend(self.check_collateralization(code)?);
findings.extend(self.check_oracle_security(code)?);
findings.extend(self.check_minting_controls(code)?);
findings.extend(self.check_emergency_mechanisms(code)?);
findings.extend(self.check_peg_stability(code)?);
findings.extend(self.check_flash_loan_protection(code)?);
findings.extend(self.check_reserve_management(code)?);
findings.extend(self.check_liquidation_logic(code)?);
Ok(findings)
}
fn check_collateralization(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if !code.contains("collateralRatio") && !code.contains("collateralization") {
findings.push(Finding {
id: "STABLE-COLLAT-001".to_string(),
severity: Severity::Critical,
category: "Stablecoin/Collateral".to_string(),
title: "Missing collateralization mechanism".to_string(),
description: "Stablecoin lacks collateral ratio tracking and validation. \
This is critical for maintaining the peg."
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement collateral ratio tracking with minimum thresholds".to_string(),
),
references: vec![
"https://docs.makerdao.com/smart-contract-modules/core-module".to_string(),
],
ai_consensus: None,
});
}
if !code.contains("minimumCollateral") && !code.contains("MIN_COLLATERAL") {
findings.push(Finding {
id: "STABLE-COLLAT-002".to_string(),
severity: Severity::High,
category: "Stablecoin/Collateral".to_string(),
title: "No minimum collateral threshold".to_string(),
description:
"Missing minimum collateral requirements could lead to under-collateralization"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Define and enforce minimum collateral ratios (e.g., 150%)".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
Ok(findings)
}
fn check_oracle_security(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
let oracle_patterns = vec!["oracle", "priceF", "getPrice", "chainlink"];
let has_oracle = oracle_patterns
.iter()
.any(|p| code.to_lowercase().contains(p));
if has_oracle {
if !code.contains("require") || !code.contains("timestamp") {
findings.push(Finding {
id: "STABLE-ORACLE-001".to_string(),
severity: Severity::Critical,
category: "Stablecoin/Oracle".to_string(),
title: "Unprotected oracle price feed".to_string(),
description: "Oracle price feeds lack freshness checks and could be manipulated".to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some("Add timestamp validation and price deviation checks for oracle feeds".to_string()),
references: vec!["https://blog.openzeppelin.com/secure-smart-contract-guidelines-the-dangers-of-price-oracles/".to_string()],
ai_consensus: None,
});
}
if !code.contains("aggregator") && !code.contains("multiple") {
findings.push(Finding {
id: "STABLE-ORACLE-002".to_string(),
severity: Severity::High,
category: "Stablecoin/Oracle".to_string(),
title: "Single oracle dependency".to_string(),
description: "Relying on a single oracle creates a single point of failure"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Use multiple oracle sources or aggregated price feeds".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
}
Ok(findings)
}
fn check_minting_controls(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if code.contains("mint") || code.contains("Mint") {
let has_mint_control = code.contains("onlyMinter")
|| code.contains("requireRole")
|| code.contains("require(msg.sender");
if !has_mint_control {
findings.push(Finding {
id: "STABLE-MINT-001".to_string(),
severity: Severity::Critical,
category: "Stablecoin/Minting".to_string(),
title: "Unrestricted minting capability".to_string(),
description: "Minting function lacks proper access control, allowing unauthorized token creation".to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some("Implement role-based access control for minting functions".to_string()),
references: vec![],
ai_consensus: None,
});
}
if !code.contains("maxSupply") && !code.contains("mintingCap") {
findings.push(Finding {
id: "STABLE-MINT-002".to_string(),
severity: Severity::High,
category: "Stablecoin/Minting".to_string(),
title: "No minting limits".to_string(),
description: "Unlimited minting could destabilize the token and break the peg"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement supply caps or collateral-based minting limits".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
}
Ok(findings)
}
fn check_emergency_mechanisms(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if !code.contains("pause") && !code.contains("Pause") && !code.contains("emergency") {
findings.push(Finding {
id: "STABLE-EMERGENCY-001".to_string(),
severity: Severity::High,
category: "Stablecoin/Emergency".to_string(),
title: "Missing emergency pause mechanism".to_string(),
description: "No ability to pause operations in case of critical issues or attacks"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement pausable functionality for emergency situations".to_string(),
),
references: vec![
"https://docs.openzeppelin.com/contracts/4.x/api/security#Pausable".to_string(),
],
ai_consensus: None,
});
}
if !code.contains("timelock") && !code.contains("governance") && !code.contains("delay") {
findings.push(Finding {
id: "STABLE-EMERGENCY-002".to_string(),
severity: Severity::Medium,
category: "Stablecoin/Governance".to_string(),
title: "No timelock for critical operations".to_string(),
description:
"Critical parameter changes should have time delays to allow users to react"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement timelock for parameter updates and critical functions".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
Ok(findings)
}
fn check_peg_stability(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if !code.contains("rebalance") && !code.contains("stabilize") && !code.contains("adjust") {
findings.push(Finding {
id: "STABLE-PEG-001".to_string(),
severity: Severity::High,
category: "Stablecoin/PegStability".to_string(),
title: "No automatic peg stabilization".to_string(),
description:
"Missing mechanisms to automatically maintain peg during market volatility"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement algorithmic stabilization or rebalancing mechanisms".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
if !code.contains("redeem") && !code.contains("burn") {
findings.push(Finding {
id: "STABLE-PEG-002".to_string(),
severity: Severity::High,
category: "Stablecoin/Redemption".to_string(),
title: "Missing redemption mechanism".to_string(),
description: "Users cannot redeem stablecoins for underlying collateral"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement redemption functionality to maintain peg confidence".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
Ok(findings)
}
fn check_flash_loan_protection(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if !code.contains("nonReentrant") && !code.contains("ReentrancyGuard") {
findings.push(Finding {
id: "STABLE-FLASH-001".to_string(),
severity: Severity::Critical,
category: "Stablecoin/FlashLoan".to_string(),
title: "Missing reentrancy protection".to_string(),
description: "Contract is vulnerable to reentrancy attacks via flash loans"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Add reentrancy guards to all state-changing functions".to_string(),
),
references: vec![
"https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/"
.to_string(),
],
ai_consensus: None,
});
}
if code.contains("getPrice") && !code.contains("block.number") {
findings.push(Finding {
id: "STABLE-FLASH-002".to_string(),
severity: Severity::High,
category: "Stablecoin/FlashLoan".to_string(),
title: "Vulnerable to flash loan price manipulation".to_string(),
description:
"Price feeds can be manipulated within the same block using flash loans"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement TWAP oracles or block-based delays for price updates".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
Ok(findings)
}
fn check_reserve_management(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if !code.contains("reserve") && !code.contains("treasury") {
findings.push(Finding {
id: "STABLE-RESERVE-001".to_string(),
severity: Severity::Medium,
category: "Stablecoin/Reserve".to_string(),
title: "No reserve management system".to_string(),
description: "Missing dedicated reserve tracking and management functionality"
.to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement transparent reserve tracking and reporting".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
if code.contains("withdraw") && !code.contains("multisig") && !code.contains("threshold") {
findings.push(Finding {
id: "STABLE-RESERVE-002".to_string(),
severity: Severity::High,
category: "Stablecoin/Reserve".to_string(),
title: "Single-signature reserve withdrawals".to_string(),
description: "Reserve funds can be withdrawn by a single address".to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Implement multi-signature requirements for reserve management".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
Ok(findings)
}
fn check_liquidation_logic(&self, code: &str) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
if code.contains("collateral") && !code.contains("liquidat") {
findings.push(Finding {
id: "STABLE-LIQUID-001".to_string(),
severity: Severity::High,
category: "Stablecoin/Liquidation".to_string(),
title: "Missing liquidation mechanism".to_string(),
description: "No system to liquidate under-collateralized positions".to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some("Implement liquidation logic with proper incentives".to_string()),
references: vec![],
ai_consensus: None,
});
}
if code.contains("liquidat") && !code.contains("bonus") && !code.contains("incentive") {
findings.push(Finding {
id: "STABLE-LIQUID-002".to_string(),
severity: Severity::Medium,
category: "Stablecoin/Liquidation".to_string(),
title: "No liquidation incentives".to_string(),
description: "Liquidators need incentives to maintain system health".to_string(),
file: "".to_string(),
line: 1,
column: 1,
code_snippet: None,
remediation: Some(
"Add liquidation bonuses to incentivize timely liquidations".to_string(),
),
references: vec![],
ai_consensus: None,
});
}
Ok(findings)
}
}
impl TokenStandard for StablecoinValidator {
fn validate(&self, code: &str, _language: &str) -> Result<Vec<Finding>> {
self.validate_stablecoin(code)
}
fn name(&self) -> &str {
"Stablecoin"
}
fn required_functions(&self) -> Vec<FunctionSignature> {
vec![
FunctionSignature {
name: "mint".to_string(),
parameters: vec![
Parameter {
name: "to".to_string(),
type_: "address".to_string(),
},
Parameter {
name: "amount".to_string(),
type_: "uint256".to_string(),
},
],
returns: vec![Parameter {
name: "success".to_string(),
type_: "bool".to_string(),
}],
visibility: "public".to_string(),
mutability: "nonpayable".to_string(),
},
FunctionSignature {
name: "burn".to_string(),
parameters: vec![Parameter {
name: "amount".to_string(),
type_: "uint256".to_string(),
}],
returns: vec![Parameter {
name: "success".to_string(),
type_: "bool".to_string(),
}],
visibility: "public".to_string(),
mutability: "nonpayable".to_string(),
},
FunctionSignature {
name: "updateCollateralPrice".to_string(),
parameters: vec![Parameter {
name: "newPrice".to_string(),
type_: "uint256".to_string(),
}],
returns: vec![],
visibility: "external".to_string(),
mutability: "nonpayable".to_string(),
},
]
}
fn required_events(&self) -> Vec<EventSignature> {
vec![
EventSignature {
name: "Mint".to_string(),
parameters: vec![
EventParameter {
name: "to".to_string(),
type_: "address".to_string(),
indexed: true,
},
EventParameter {
name: "amount".to_string(),
type_: "uint256".to_string(),
indexed: false,
},
],
},
EventSignature {
name: "Burn".to_string(),
parameters: vec![
EventParameter {
name: "from".to_string(),
type_: "address".to_string(),
indexed: true,
},
EventParameter {
name: "amount".to_string(),
type_: "uint256".to_string(),
indexed: false,
},
],
},
EventSignature {
name: "CollateralPriceUpdated".to_string(),
parameters: vec![
EventParameter {
name: "newPrice".to_string(),
type_: "uint256".to_string(),
indexed: false,
},
EventParameter {
name: "timestamp".to_string(),
type_: "uint256".to_string(),
indexed: false,
},
],
},
]
}
fn optional_functions(&self) -> Vec<FunctionSignature> {
vec![]
}
}