Skip to main content

atupa_core/
lib.rs

1pub mod config;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// Standard EVM Gas Categories for logical grouping of execution costs.
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
7#[serde(rename_all = "PascalCase")]
8pub enum GasCategory {
9    /// Opcodes like SSTORE
10    StorageWrite,
11    /// Opcodes like SLOAD
12    StorageRead,
13    /// Memory operations (MLOAD, MSTORE, etc.)
14    Memory,
15    /// Cryptographic operations (KECCAK256)
16    Crypto,
17    /// External calls (CALL, DELEGATECALL, etc.)
18    Call,
19    /// Logic and arithmetic
20    Execution,
21    /// Precompiled contract calls
22    Precompile,
23    /// Root execution frame
24    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            // Arithmetic, Logic, Stack, Flow
47            "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        // Specific checks for flush (it's a write operation)
66        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/// A single step in the EVM execution trace (equivalent to structLog).
90#[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/// Which virtual machine produced these execution steps.
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
109pub enum VmKind {
110    #[default]
111    Evm,
112    Stylus,
113}
114
115/// A single collapsed stack entry for aggregation.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct CollapsedStack {
118    pub stack: String,
119    pub weight: u64,
120    pub last_pc: Option<u64>,
121    /// Maximum call depth seen for steps in this stack.
122    #[serde(default)]
123    pub depth: u16,
124    /// The VM that produced this collapsed stack.
125    #[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/// A collapsed execution path with aggregated gas costs.
136#[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/// The final report generated by Atupa.
145#[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// ─── Protocol Diff Structures ────────────────────────────────────────────────
169
170/// A field-by-field delta between two protocol executions.
171#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct ProtocolDiffReport {
173    pub protocol: String,
174    pub rows: Vec<DiffRow>,
175}
176
177/// A single comparable metric row for protocol-level diffing.
178#[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    /// true = a larger value is bad (gas, reads, calls), false = larger is better
186    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}