pub mod config;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "PascalCase")]
pub enum GasCategory {
StorageWrite,
StorageRead,
Memory,
Crypto,
Call,
Execution,
Precompile,
Root,
#[default]
Other,
}
impl GasCategory {
pub fn from_step(op: &str, vm: VmKind) -> Self {
let op = op.trim();
match vm {
VmKind::Evm => Self::from_evm(op),
VmKind::Stylus => Self::from_stylus(op),
}
}
fn from_evm(op: &str) -> Self {
match op {
"SSTORE" | "TSTORE" => Self::StorageWrite,
"SLOAD" | "TLOAD" => Self::StorageRead,
"MLOAD" | "MSTORE" | "MSTORE8" | "MCOPY" | "MSIZE" => Self::Memory,
"KECCAK256" | "SHA3" => Self::Crypto,
"CALL" | "STATICCALL" | "DELEGATECALL" | "CALLCODE" | "CREATE" | "CREATE2"
| "RETURN" | "REVERT" | "STOP" | "INVALID" | "SELFDESTRUCT" => Self::Call,
"ADD" | "SUB" | "MUL" | "DIV" | "SDIV" | "MOD" | "SMOD" | "ADDMOD" | "MULMOD"
| "EXP" | "SIGNEXTEND" | "LT" | "GT" | "SLT" | "SGT" | "EQ" | "ISZERO" | "AND"
| "OR" | "XOR" | "NOT" | "BYTE" | "SHL" | "SHR" | "SAR" | "POP" | "PUSH1" | "PUSH2"
| "PUSH3" | "PUSH4" | "PUSH5" | "PUSH6" | "PUSH7" | "PUSH8" | "PUSH9" | "PUSH10"
| "PUSH11" | "PUSH12" | "PUSH13" | "PUSH14" | "PUSH15" | "PUSH16" | "PUSH17"
| "PUSH18" | "PUSH19" | "PUSH20" | "PUSH21" | "PUSH22" | "PUSH23" | "PUSH24"
| "PUSH25" | "PUSH26" | "PUSH27" | "PUSH28" | "PUSH29" | "PUSH30" | "PUSH31"
| "PUSH32" | "DUP1" | "DUP2" | "DUP3" | "DUP4" | "DUP5" | "DUP6" | "DUP7" | "DUP8"
| "DUP9" | "DUP10" | "DUP11" | "DUP12" | "DUP13" | "DUP14" | "DUP15" | "DUP16"
| "SWAP1" | "SWAP2" | "SWAP3" | "SWAP4" | "SWAP5" | "SWAP6" | "SWAP7" | "SWAP8"
| "SWAP9" | "SWAP10" | "SWAP11" | "SWAP12" | "SWAP13" | "SWAP14" | "SWAP15"
| "SWAP16" | "JUMP" | "JUMPI" | "PC" | "GAS" | "JUMPDEST" => Self::Execution,
_ => Self::Other,
}
}
fn from_stylus(hostio: &str) -> Self {
let n = hostio.to_lowercase();
if n.contains("flush") || n.contains("storage_store") {
Self::StorageWrite
} else if n.contains("storage_load") || n.contains("storage_cache") {
Self::StorageRead
} else if n.contains("keccak") || n.contains("sha2") {
Self::Crypto
} else if n.contains("call") || n.contains("create") {
Self::Call
} else if n.contains("memory") || n.contains("args") || n.contains("return") {
Self::Memory
} else if n.contains("msg")
|| n.contains("block")
|| n.contains("tx")
|| n.contains("evm")
|| n.contains("user")
{
Self::Execution
} else {
Self::Other
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TraceStep {
pub pc: u64,
pub op: String,
pub gas: u64,
pub gas_cost: u64,
pub depth: u16,
pub stack: Option<Vec<String>>,
pub memory: Option<Vec<String>>,
#[serde(default)]
pub error: Option<String>,
#[serde(default)]
pub reverted: bool,
#[serde(default)]
pub vm_kind: VmKind,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum VmKind {
#[default]
Evm,
Stylus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollapsedStack {
pub stack: String,
pub weight: u64,
pub last_pc: Option<u64>,
#[serde(default)]
pub depth: u16,
#[serde(default)]
pub vm_kind: VmKind,
#[serde(default)]
pub target_address: Option<String>,
#[serde(default)]
pub resolved_label: Option<String>,
#[serde(default)]
pub reverted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HotPath {
pub stack: String,
pub gas: u64,
pub percentage: f64,
pub category: GasCategory,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Profile {
pub version: String,
pub transaction_hash: String,
pub total_gas: u64,
pub categories: HashMap<GasCategory, u64>,
pub hot_paths: Vec<HotPath>,
pub generated_at: String,
}
impl Profile {
pub fn new(tx_hash: String) -> Self {
Self {
version: env!("CARGO_PKG_VERSION").to_string(),
transaction_hash: tx_hash,
total_gas: 0,
categories: HashMap::new(),
hot_paths: Vec::new(),
generated_at: chrono::Utc::now().to_rfc3339(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProtocolDiffReport {
pub protocol: String,
pub rows: Vec<DiffRow>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiffRow {
pub metric: String,
pub base: f64,
pub target: f64,
pub delta: f64,
pub pct: f64,
pub higher_is_worse: bool,
}
impl DiffRow {
pub fn new(metric: &str, base: f64, target: f64, higher_is_worse: bool) -> Self {
let delta = target - base;
let pct = if base > 0.0 {
delta / base * 100.0
} else {
0.0
};
Self {
metric: metric.to_string(),
base,
target,
delta,
pct,
higher_is_worse,
}
}
}