use super::{DynamicGasCalculator, GasAnalysis};
use crate::{Fork, OpCode};
pub struct GasAnalyzer;
impl GasAnalyzer {
pub fn analyze_gas_usage(opcodes: &[u8], fork: Fork) -> GasAnalysis {
let calc = DynamicGasCalculator::new(fork);
let pairs: Vec<(u8, Vec<u64>)> = opcodes
.iter()
.map(|&op| (op, Self::estimate_operands(op)))
.collect();
match calc.analyze_sequence_gas(&pairs) {
Ok(a) => a,
Err(e) => {
let mut analysis = GasAnalysis::new();
analysis.warnings.push(format!("Gas analysis failed: {e}"));
for &op in opcodes {
let cost = OpCode::from_byte(op).gas_cost(fork);
analysis.total_gas += cost as u64;
analysis.breakdown.push((op, cost));
}
analysis
}
}
}
pub fn validate_opcode_sequence(opcodes: &[u8], fork: Fork) -> Result<(), String> {
let analysis = Self::analyze_gas_usage(opcodes, fork);
const BLOCK_LIMIT: u64 = 30_000_000;
if analysis.total_gas > BLOCK_LIMIT {
return Err(format!(
"Sequence uses {} gas, exceeding block limit {BLOCK_LIMIT}",
analysis.total_gas
));
}
for window in opcodes.windows(2) {
match (window[0], window[1]) {
(0x56, 0x56) => return Err("Consecutive JUMP instructions".into()),
(0x57, 0x55) => return Err("SSTORE after JUMPI — potential expensive loop".into()),
(0x80..=0x8f, 0x50) => return Err("DUP followed by POP — redundant".into()),
_ => {}
}
}
let bombs = analysis.find_gas_bombs();
if !bombs.is_empty() {
return Err(format!("Gas bombs: {}", bombs.join("; ")));
}
Ok(())
}
fn estimate_operands(opcode: u8) -> Vec<u64> {
match opcode {
0x54 => vec![0x0],
0x55 => vec![0x0, 0x1],
0x5c => vec![0x0],
0x5d => vec![0x0, 0x1],
0x51..=0x53 => vec![0x40],
0x5e => vec![0x40, 0x80, 0x20],
0xf1 | 0xf2 | 0xf4 | 0xfa => vec![100_000, 0x123, 0, 0, 0, 0, 0],
0x31 | 0x3b | 0x3c | 0x3f => vec![0x123],
0x37 | 0x39 | 0x3e => vec![0x40, 0x0, 0x20],
0xf0 | 0xf5 => vec![0, 0x40, 0x100],
0x20 => vec![0x40, 0x20],
0xa0..=0xa4 => vec![0x40, 0x20],
_ => vec![],
}
}
}
pub struct GasComparator;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpcodeChange {
pub opcode: u8,
pub change_type: ChangeType,
pub old_value: Option<u16>,
pub new_value: Option<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChangeType {
Added,
GasCostChanged,
}
impl GasComparator {
pub fn compare_gas_costs(opcode: u8, fork1: Fork, fork2: Fork) -> Option<(u16, u16)> {
let op = OpCode::new(opcode)?;
Some((op.gas_cost(fork1), op.gas_cost(fork2)))
}
pub fn get_changes_between_forks(fork1: Fork, fork2: Fork) -> Vec<OpcodeChange> {
let mut changes = Vec::new();
for op in OpCode::iter_all() {
let in1 = op.is_valid_in(fork1);
let in2 = op.is_valid_in(fork2);
if !in1 && in2 {
changes.push(OpcodeChange {
opcode: op.byte(),
change_type: ChangeType::Added,
old_value: None,
new_value: Some(op.gas_cost(fork2)),
});
} else if in1 && in2 {
let g1 = op.gas_cost(fork1);
let g2 = op.gas_cost(fork2);
if g1 != g2 {
changes.push(OpcodeChange {
opcode: op.byte(),
change_type: ChangeType::GasCostChanged,
old_value: Some(g1),
new_value: Some(g2),
});
}
}
}
changes
}
pub fn generate_comparison_report(fork1: Fork, fork2: Fork) -> GasComparisonReport {
let changes = Self::get_changes_between_forks(fork1, fork2);
let mut summary = GasChangeSummary::default();
for c in &changes {
match c.change_type {
ChangeType::Added => summary.opcodes_added += 1,
ChangeType::GasCostChanged => {
summary.gas_cost_changes += 1;
if let (Some(old), Some(new)) = (c.old_value, c.new_value) {
if new > old {
summary.gas_increases += 1;
summary.total_gas_increase += (new - old) as u32;
} else {
summary.gas_decreases += 1;
summary.total_gas_decrease += (old - new) as u32;
}
}
}
}
}
GasComparisonReport {
fork1,
fork2,
changes,
summary,
}
}
}
#[derive(Debug, Clone)]
pub struct GasComparisonReport {
pub fork1: Fork,
pub fork2: Fork,
pub changes: Vec<OpcodeChange>,
pub summary: GasChangeSummary,
}
impl GasComparisonReport {
pub fn print_report(&self) {
println!(
"=== Gas Comparison: {:?} -> {:?} ===",
self.fork1, self.fork2
);
println!(" Added: {}", self.summary.opcodes_added);
println!(" Gas changes: {}", self.summary.gas_cost_changes);
println!(
" Increases: {} (+{} total)",
self.summary.gas_increases, self.summary.total_gas_increase
);
println!(
" Decreases: {} (-{} total)",
self.summary.gas_decreases, self.summary.total_gas_decrease
);
for c in &self.changes {
match c.change_type {
ChangeType::Added => {
let name = OpCode::from_byte(c.opcode).name();
println!(
" + {name} (0x{:02x}): {} gas",
c.opcode,
c.new_value.unwrap_or(0)
);
}
ChangeType::GasCostChanged => {
let name = OpCode::from_byte(c.opcode).name();
println!(
" ~ {name} (0x{:02x}): {} -> {}",
c.opcode,
c.old_value.unwrap_or(0),
c.new_value.unwrap_or(0)
);
}
}
}
}
pub fn get_most_impactful_changes(&self, n: usize) -> Vec<&OpcodeChange> {
let mut gas_changes: Vec<_> = self
.changes
.iter()
.filter(|c| c.change_type == ChangeType::GasCostChanged)
.collect();
gas_changes.sort_by(|a, b| {
let da = a
.old_value
.zip(a.new_value)
.map(|(o, n)| (n as i32 - o as i32).unsigned_abs())
.unwrap_or(0);
let db = b
.old_value
.zip(b.new_value)
.map(|(o, n)| (n as i32 - o as i32).unsigned_abs())
.unwrap_or(0);
db.cmp(&da)
});
gas_changes.into_iter().take(n).collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct GasChangeSummary {
pub opcodes_added: u32,
pub gas_cost_changes: u32,
pub gas_increases: u32,
pub gas_decreases: u32,
pub total_gas_increase: u32,
pub total_gas_decrease: u32,
}
pub struct GasOptimizationAdvisor;
impl GasOptimizationAdvisor {
pub fn get_fork_optimizations(fork: Fork) -> Vec<String> {
let mut recs = Vec::new();
if fork >= Fork::Shanghai {
recs.push("Use PUSH0 instead of PUSH1 0x00 (saves 1 gas per use)".into());
}
if fork >= Fork::Cancun {
recs.push(
"Use TSTORE/TLOAD for temporary storage (100 gas vs 2100+ for SSTORE/SLOAD)".into(),
);
recs.push("Use MCOPY for memory copying (more efficient than manual loops)".into());
}
if fork >= Fork::Berlin {
recs.push("Pre-warm storage slots and addresses to reduce cold access costs".into());
}
recs.push("Pack storage variables to minimise SSTORE operations".into());
recs.push("Minimise external calls — they are expensive and can fail".into());
recs
}
pub fn analyze_pattern(opcodes: &[u8], fork: Fork) -> Vec<String> {
let mut suggestions = Vec::new();
let analysis = GasAnalyzer::analyze_gas_usage(opcodes, fork);
let mut consecutive_sloads = 0u32;
let mut push_zeros = 0u32;
for window in opcodes.windows(2) {
if window == [0x54, 0x54] {
consecutive_sloads += 1;
}
if window == [0x60, 0x00] && fork >= Fork::Shanghai {
push_zeros += 1;
}
}
if consecutive_sloads > 0 {
suggestions.push(format!(
"{consecutive_sloads} consecutive SLOADs — consider caching in memory"
));
}
if push_zeros > 0 {
suggestions.push(format!(
"{push_zeros} PUSH1 0x00 sequences — replace with PUSH0 (saves {push_zeros} gas)"
));
}
if analysis.efficiency_score() < 50 {
suggestions.push("Low gas efficiency — consider algorithmic improvements".into());
}
suggestions
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn analyze_simple() {
let a = GasAnalyzer::analyze_gas_usage(&[0x01, 0x02, 0x03], Fork::London);
assert!(a.total_gas >= crate::gas::TX_BASE);
assert_eq!(a.breakdown.len(), 3);
}
#[test]
fn fork_comparison() {
let changes = GasComparator::get_changes_between_forks(Fork::Istanbul, Fork::Berlin);
assert!(!changes.is_empty());
assert!(changes.iter().any(|c| c.opcode == 0x54)); assert!(changes.iter().any(|c| c.opcode == 0x31)); }
#[test]
fn optimization_advisor() {
let recs = GasOptimizationAdvisor::get_fork_optimizations(Fork::Shanghai);
assert!(recs.iter().any(|r| r.contains("PUSH0")));
}
#[test]
fn pattern_analysis() {
let ops = vec![0x60, 0x00, 0x54, 0x54, 0x55];
let suggestions = GasOptimizationAdvisor::analyze_pattern(&ops, Fork::Shanghai);
assert!(!suggestions.is_empty());
}
}