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
71 changes: 71 additions & 0 deletions packages/rules/src/optimization/storage/multiple_storage_reads.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! Detect Multiple Storage Reads
//!
//! Flags repeated reads from the same storage variable within a function.
//! Suggests caching the value in a local variable to reduce gas costs.

use crate::rule_engine::{Rule, RuleViolation, ViolationSeverity};
use syn::{Item, Stmt, Expr};
use std::collections::HashMap;

pub struct MultipleStorageReadsRule;

impl Rule for MultipleStorageReadsRule {
fn name(&self) -> &str {
"multiple-storage-reads"
}

fn description(&self) -> &str {
"Detects repeated reads from the same storage slot. \
Cache the value in a local variable to save gas."
}

fn check(&self, ast: &[Item]) -> Vec<RuleViolation> {
let mut violations = Vec::new();
for item in ast {
if let Item::Fn(func) = item {
self.check_stmts(&func.block.stmts, &mut violations);
}
}
violations
}
}

impl MultipleStorageReadsRule {
fn check_stmts(&self, stmts: &[Stmt], violations: &mut Vec<RuleViolation>) {
let mut read_counts: HashMap<String, usize> = HashMap::new();
for stmt in stmts {
if let Stmt::Expr(Expr::MethodCall(call), _) = stmt {
let method = call.method.to_string();
if method == "get" || method == "read" {
let key = format!("{:?}", call.receiver);
*read_counts.entry(key.clone()).or_insert(0) += 1;
if read_counts[&key] == 2 {
violations.push(RuleViolation {
rule_name: self.name().to_string(),
description: "Storage slot read more than once in the same function."
.to_string(),
severity: ViolationSeverity::Medium,
line_number: 0,
column_number: 0,
variable_name: key,
suggestion: "Cache the storage value in a local `let` binding."
.to_string(),
});
}
}
}
}
}
}

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

#[test]
fn no_violation_single_read() {
let ast = parse_file("fn f() { let x = s.get(&k); }").expect("parse");
assert!(MultipleStorageReadsRule.check(&ast.items).is_empty());
}
}
2 changes: 2 additions & 0 deletions packages/rules/src/security/randomness/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod unsafe_randomness;
pub use unsafe_randomness::UnsafeRandomnessRule;
71 changes: 71 additions & 0 deletions packages/rules/src/security/randomness/unsafe_randomness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//! Detect Unsafe Randomness Patterns
//!
//! Flags use of blockhash or block.timestamp as a source of randomness,
//! which are predictable and manipulable by miners/validators.

use crate::rule_engine::{Rule, RuleViolation, ViolationSeverity};
use syn::Item;

pub struct UnsafeRandomnessRule;

impl Rule for UnsafeRandomnessRule {
fn name(&self) -> &str {
"unsafe-randomness"
}

fn description(&self) -> &str {
"Detects use of blockhash or block.timestamp as randomness sources. \
These values are predictable and can be manipulated."
}

fn check(&self, ast: &[Item]) -> Vec<RuleViolation> {
let mut violations = Vec::new();
for item in ast {
if let Item::Fn(func) = item {
let src = quote::quote!(#func).to_string();
for pattern in &["blockhash", "block.timestamp", "block_timestamp"] {
if src.contains(pattern) {
violations.push(RuleViolation {
rule_name: self.name().to_string(),
description: format!(
"Use of `{}` as randomness source is unsafe.",
pattern
),
severity: ViolationSeverity::High,
line_number: 0,
column_number: 0,
variable_name: pattern.to_string(),
suggestion: "Use a verifiable random function (VRF) or \
commit-reveal scheme instead."
.to_string(),
});
}
}
}
}
violations
}
}

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

fn check(code: &str) -> Vec<RuleViolation> {
let ast = parse_file(code).expect("parse failed");
UnsafeRandomnessRule.check(&ast.items)
}

#[test]
fn flags_block_timestamp() {
let code = r#"fn rand() -> u64 { let r = block_timestamp(); r }"#;
assert!(!check(code).is_empty());
}

#[test]
fn no_violation_for_safe_code() {
let code = r#"fn safe() -> u64 { 42 }"#;
assert!(check(code).is_empty());
}
}
Loading