pub mod analysis;
pub mod calculator;
pub mod context;
pub use analysis::*;
pub use calculator::*;
pub use context::*;
pub const ZERO: u64 = 0;
pub const JUMPDEST: u64 = 1;
pub const VERYLOW: u64 = 3;
pub const LOW: u64 = 5;
pub const MID: u64 = 8;
pub const HIGH: u64 = 8;
pub const WARM_STORAGE_READ_COST: u64 = 100;
pub const COLD_SLOAD_COST: u64 = 2100;
pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600;
pub const SSTORE_SET: u64 = 20000;
pub const SSTORE_RESET: u64 = 2900;
pub const TX_BASE: u64 = 21000;
pub const TX_DATA_NON_ZERO: u64 = 16;
pub const TX_DATA_ZERO: u64 = 4;
pub const CREATE_GAS: u64 = 32000;
pub const KECCAK256_WORD: u64 = 6;
pub const COPY_WORD: u64 = 3;
pub const LOG_TOPIC: u64 = 375;
pub const LOG_DATA: u64 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GasCostCategory {
VeryLow,
Low,
Medium,
High,
VeryHigh,
Unknown,
}
impl GasCostCategory {
pub fn classify(opcode: u8) -> Self {
match opcode {
0x01..=0x0b | 0x10..=0x1d | 0x50 | 0x58 | 0x80..=0x9f => Self::VeryLow,
0x51..=0x53 | 0x56..=0x57 | 0x5a..=0x5b => Self::Low,
0x20 | 0x30 | 0x32..=0x3a | 0x40..=0x48 => Self::Medium,
0x54 | 0x31 | 0x3b | 0x3c | 0x3d | 0x3e | 0x3f => Self::High,
0x55 | 0xf0..=0xff => Self::VeryHigh,
_ => Self::Unknown,
}
}
pub fn gas_range(&self) -> (u64, u64) {
match self {
Self::VeryLow => (1, 3),
Self::Low => (3, 8),
Self::Medium => (8, 100),
Self::High => (100, 2600),
Self::VeryHigh => (2600, u64::MAX),
Self::Unknown => (0, 0),
}
}
}
#[derive(Debug, Clone)]
pub struct GasAnalysis {
pub total_gas: u64,
pub breakdown: Vec<(u8, u16)>,
pub optimizations: Vec<String>,
pub warnings: Vec<String>,
}
impl GasAnalysis {
pub fn new() -> Self {
Self {
total_gas: TX_BASE,
breakdown: Vec::new(),
optimizations: Vec::new(),
warnings: Vec::new(),
}
}
pub fn efficiency_score(&self) -> u8 {
if self.breakdown.is_empty() {
return 0;
}
let opcode_gas = self.total_gas.saturating_sub(TX_BASE);
let avg = opcode_gas / self.breakdown.len() as u64;
match avg {
0..=10 => 100,
11..=50 => 80,
51..=200 => 60,
201..=1000 => 40,
1001..=5000 => 20,
_ => 0,
}
}
pub fn get_optimization_recommendations(&self) -> Vec<String> {
let mut recs = self.optimizations.clone();
let mut counts = std::collections::HashMap::new();
for &(op, _) in &self.breakdown {
*counts.entry(op).or_insert(0u32) += 1;
}
for (op, count) in counts {
if count > 5 && matches!(op, 0x54 | 0x55 | 0xf1 | 0xf4) {
recs.push(format!(
"Opcode 0x{op:02x} used {count} times — consider batching or caching"
));
}
}
recs
}
pub fn is_optimized(&self) -> bool {
self.efficiency_score() > 70 && self.warnings.is_empty()
}
pub fn gas_by_category(&self) -> std::collections::HashMap<GasCostCategory, u64> {
let mut map = std::collections::HashMap::new();
for &(op, cost) in &self.breakdown {
*map.entry(GasCostCategory::classify(op)).or_insert(0) += cost as u64;
}
map
}
pub fn find_gas_bombs(&self) -> Vec<String> {
let mut bombs = Vec::new();
for &(op, cost) in &self.breakdown {
match op {
0x55 if cost > 5000 => {
bombs.push("SSTORE with high gas cost — could cause out-of-gas".into());
}
0xf1 | 0xf2 | 0xf4 | 0xfa if cost > 10000 => {
bombs.push("Call operation with high gas cost — ensure sufficient gas".into());
}
0xf0 | 0xf5 if cost > 50000 => {
bombs.push("Create operation with very high gas cost".into());
}
_ => {}
}
}
bombs
}
pub fn estimate_optimization_savings(&self) -> u64 {
let mut savings = 0u64;
let mut sload_count = 0u32;
let mut prev = None;
for &(op, cost) in &self.breakdown {
if op == 0x54 {
sload_count += 1;
}
if matches!(prev, Some(0x80..=0x8f)) && op == 0x50 {
savings += cost as u64;
}
prev = Some(op);
}
if sload_count > 2 {
savings += ((sload_count - 1) / 2) as u64 * COLD_SLOAD_COST;
}
savings
}
}
impl Default for GasAnalysis {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn category_classification() {
assert_eq!(GasCostCategory::classify(0x01), GasCostCategory::VeryLow);
assert_eq!(GasCostCategory::classify(0x54), GasCostCategory::High);
assert_eq!(GasCostCategory::classify(0x55), GasCostCategory::VeryHigh);
}
#[test]
fn efficiency_score() {
let a = GasAnalysis {
total_gas: TX_BASE + 9,
breakdown: vec![(0x01, 3), (0x02, 3), (0x03, 3)],
warnings: vec![],
optimizations: vec![],
};
assert!(a.efficiency_score() >= 80);
}
}