1pub mod config;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
7#[serde(rename_all = "PascalCase")]
8pub enum GasCategory {
9 StorageWrite,
11 StorageRead,
13 Memory,
15 Crypto,
17 Call,
19 Execution,
21 Precompile,
23 Root,
25 #[default]
26 Other,
27}
28
29impl GasCategory {
30 pub fn from_step(op: &str, vm: VmKind) -> Self {
31 let op = op.trim();
32 match vm {
33 VmKind::Evm => Self::from_evm(op),
34 VmKind::Stylus => Self::from_stylus(op),
35 }
36 }
37
38 fn from_evm(op: &str) -> Self {
39 match op {
40 "SSTORE" | "TSTORE" => Self::StorageWrite,
41 "SLOAD" | "TLOAD" => Self::StorageRead,
42 "MLOAD" | "MSTORE" | "MSTORE8" | "MCOPY" | "MSIZE" => Self::Memory,
43 "KECCAK256" | "SHA3" => Self::Crypto,
44 "CALL" | "STATICCALL" | "DELEGATECALL" | "CALLCODE" | "CREATE" | "CREATE2"
45 | "RETURN" | "REVERT" | "STOP" | "INVALID" | "SELFDESTRUCT" => Self::Call,
46 "ADD" | "SUB" | "MUL" | "DIV" | "SDIV" | "MOD" | "SMOD" | "ADDMOD" | "MULMOD"
48 | "EXP" | "SIGNEXTEND" | "LT" | "GT" | "SLT" | "SGT" | "EQ" | "ISZERO" | "AND"
49 | "OR" | "XOR" | "NOT" | "BYTE" | "SHL" | "SHR" | "SAR" | "POP" | "PUSH1" | "PUSH2"
50 | "PUSH3" | "PUSH4" | "PUSH5" | "PUSH6" | "PUSH7" | "PUSH8" | "PUSH9" | "PUSH10"
51 | "PUSH11" | "PUSH12" | "PUSH13" | "PUSH14" | "PUSH15" | "PUSH16" | "PUSH17"
52 | "PUSH18" | "PUSH19" | "PUSH20" | "PUSH21" | "PUSH22" | "PUSH23" | "PUSH24"
53 | "PUSH25" | "PUSH26" | "PUSH27" | "PUSH28" | "PUSH29" | "PUSH30" | "PUSH31"
54 | "PUSH32" | "DUP1" | "DUP2" | "DUP3" | "DUP4" | "DUP5" | "DUP6" | "DUP7" | "DUP8"
55 | "DUP9" | "DUP10" | "DUP11" | "DUP12" | "DUP13" | "DUP14" | "DUP15" | "DUP16"
56 | "SWAP1" | "SWAP2" | "SWAP3" | "SWAP4" | "SWAP5" | "SWAP6" | "SWAP7" | "SWAP8"
57 | "SWAP9" | "SWAP10" | "SWAP11" | "SWAP12" | "SWAP13" | "SWAP14" | "SWAP15"
58 | "SWAP16" | "JUMP" | "JUMPI" | "PC" | "GAS" | "JUMPDEST" => Self::Execution,
59 _ => Self::Other,
60 }
61 }
62
63 fn from_stylus(hostio: &str) -> Self {
64 let n = hostio.to_lowercase();
65 if n.contains("flush") || n.contains("storage_store") {
67 Self::StorageWrite
68 } else if n.contains("storage_load") || n.contains("storage_cache") {
69 Self::StorageRead
70 } else if n.contains("keccak") || n.contains("sha2") {
71 Self::Crypto
72 } else if n.contains("call") || n.contains("create") {
73 Self::Call
74 } else if n.contains("memory") || n.contains("args") || n.contains("return") {
75 Self::Memory
76 } else if n.contains("msg")
77 || n.contains("block")
78 || n.contains("tx")
79 || n.contains("evm")
80 || n.contains("user")
81 {
82 Self::Execution
83 } else {
84 Self::Other
85 }
86 }
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
91pub struct TraceStep {
92 pub pc: u64,
93 pub op: String,
94 pub gas: u64,
95 pub gas_cost: u64,
96 pub depth: u16,
97 pub stack: Option<Vec<String>>,
98 pub memory: Option<Vec<String>>,
99 #[serde(default)]
100 pub error: Option<String>,
101 #[serde(default)]
102 pub reverted: bool,
103 #[serde(default)]
104 pub vm_kind: VmKind,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
109pub enum VmKind {
110 #[default]
111 Evm,
112 Stylus,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct CollapsedStack {
118 pub stack: String,
119 pub weight: u64,
120 pub last_pc: Option<u64>,
121 #[serde(default)]
123 pub depth: u16,
124 #[serde(default)]
126 pub vm_kind: VmKind,
127 #[serde(default)]
128 pub target_address: Option<String>,
129 #[serde(default)]
130 pub resolved_label: Option<String>,
131 #[serde(default)]
132 pub reverted: bool,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct HotPath {
138 pub stack: String,
139 pub gas: u64,
140 pub percentage: f64,
141 pub category: GasCategory,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct Profile {
147 pub version: String,
148 pub transaction_hash: String,
149 pub total_gas: u64,
150 pub categories: HashMap<GasCategory, u64>,
151 pub hot_paths: Vec<HotPath>,
152 pub generated_at: String,
153}
154
155impl Profile {
156 pub fn new(tx_hash: String) -> Self {
157 Self {
158 version: env!("CARGO_PKG_VERSION").to_string(),
159 transaction_hash: tx_hash,
160 total_gas: 0,
161 categories: HashMap::new(),
162 hot_paths: Vec::new(),
163 generated_at: chrono::Utc::now().to_rfc3339(),
164 }
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct ProtocolDiffReport {
173 pub protocol: String,
174 pub rows: Vec<DiffRow>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct DiffRow {
180 pub metric: String,
181 pub base: f64,
182 pub target: f64,
183 pub delta: f64,
184 pub pct: f64,
185 pub higher_is_worse: bool,
187}
188
189impl DiffRow {
190 pub fn new(metric: &str, base: f64, target: f64, higher_is_worse: bool) -> Self {
191 let delta = target - base;
192 let pct = if base > 0.0 {
193 delta / base * 100.0
194 } else {
195 0.0
196 };
197 Self {
198 metric: metric.to_string(),
199 base,
200 target,
201 delta,
202 pct,
203 higher_is_worse,
204 }
205 }
206}