Skip to main content

evm_lens_core/
lib.rs

1use revm::{bytecode::Bytecode, primitives::Bytes};
2
3pub mod stats;
4pub use stats::{Stats, StatsError, compute_stats};
5pub mod abi;
6pub mod storage;
7
8// Re-export OpCode for public use
9pub use revm::bytecode::OpCode;
10
11#[derive(Debug)]
12pub enum DisassemblyError {
13    InvalidBytecode(String),
14    EmptyBytecode,
15    MalformedInstruction { position: usize, byte: u8 },
16}
17
18impl std::fmt::Display for DisassemblyError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            DisassemblyError::InvalidBytecode(msg) => write!(f, "Invalid bytecode: {msg}"),
22            DisassemblyError::EmptyBytecode => write!(f, "Bytecode is empty"),
23            DisassemblyError::MalformedInstruction { position, byte } => {
24                write!(
25                    f,
26                    "Malformed instruction at position {position}: invalid opcode 0x{byte:02x}"
27                )
28            }
29        }
30    }
31}
32
33impl std::error::Error for DisassemblyError {}
34
35/// Disassembles EVM bytecode into a sequence of opcodes with their positions.
36///
37/// Takes a byte slice containing raw EVM bytecode and returns a vector of tuples,
38/// where each tuple contains:
39/// - The position of the opcode in the bytecode (usize)
40/// - The opcode itself (OpCode)
41///
42/// # Arguments
43///
44/// * `bytes` - A slice of bytes containing the raw EVM bytecode
45///
46/// # Returns
47///
48/// * `Ok(Vec<(usize, OpCode)>)` - A vector of tuples containing the position and opcode for each instruction
49/// * `Err(DisassemblyError)` - If the bytecode is invalid
50///
51/// # Example
52///
53/// ```
54/// use evm_lens_core::disassemble;
55/// use revm::bytecode::OpCode;
56///
57/// let bytecode = hex::decode("60FF").unwrap(); // PUSH1 0xFF
58/// let ops = disassemble(&bytecode).unwrap();
59/// assert_eq!(ops[0].0, 0); // Position 0
60/// assert_eq!(ops[0].1, OpCode::PUSH1); // PUSH1 opcode
61/// ```
62pub fn disassemble(bytes: &[u8]) -> Result<Vec<(usize, OpCode)>, DisassemblyError> {
63    if bytes.is_empty() {
64        return Err(DisassemblyError::EmptyBytecode);
65    }
66
67    let bytecode = match Bytecode::new_raw_checked(Bytes::from(bytes.to_vec())) {
68        Ok(bytecode) => bytecode,
69        Err(e) => return Err(DisassemblyError::InvalidBytecode(e.to_string())),
70    };
71
72    let mut result: Vec<(usize, OpCode)> = Vec::new();
73    let mut bytecode_iter = bytecode.iter_opcodes();
74
75    while let Some(opcode) = bytecode_iter.peek_opcode() {
76        result.push((bytecode_iter.position(), opcode));
77        bytecode_iter.next();
78    }
79
80    if result.is_empty() {
81        return Err(DisassemblyError::InvalidBytecode(
82            "No valid opcodes found".to_string(),
83        ));
84    }
85
86    Ok(result)
87}
88
89/// Computes and returns statistics for the given bytecode.
90///
91/// This function takes raw bytecode bytes and returns comprehensive statistics
92/// including byte length, opcode count, maximum stack depth, and other metrics.
93///
94/// # Arguments
95///
96/// * `bytes` - A slice of bytes containing the raw EVM bytecode
97///
98/// # Returns
99///
100/// * `Ok(Stats)` - Statistics about the bytecode
101/// * `Err(DisassemblyError)` - If the bytecode is invalid
102///
103/// # Example
104///
105/// ```
106/// use evm_lens_core::get_stats;
107///
108/// let bytecode = hex::decode("60FF600101").unwrap(); // PUSH1 0xFF, PUSH1 0x01, ADD
109/// let stats = get_stats(&bytecode).unwrap();
110/// println!("Bytecode length: {} bytes", stats.byte_len);
111/// println!("Number of opcodes: {}", stats.opcode_count);
112/// ```
113pub fn get_stats(bytes: &[u8]) -> Result<Stats, DisassemblyError> {
114    if bytes.is_empty() {
115        return Err(DisassemblyError::EmptyBytecode);
116    }
117
118    let bytecode = match Bytecode::new_raw_checked(Bytes::from(bytes.to_vec())) {
119        Ok(bytecode) => bytecode,
120        Err(e) => return Err(DisassemblyError::InvalidBytecode(e.to_string())),
121    };
122
123    compute_stats(&bytecode).map_err(|e| match e {
124        StatsError::UnknownOpcode(opcode) => DisassemblyError::MalformedInstruction {
125            position: 0, // We don't have position info from stats error
126            byte: opcode,
127        },
128    })
129}
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn push1_push2_stop() {
136        let bytes = hex::decode("60FF61ABCD00").unwrap(); // PUSH1 0xFF, PUSH2 0xABCD, STOP
137        let ops = disassemble(&bytes).unwrap();
138        assert_eq!(ops.len(), 3);
139        assert_eq!(ops[0].0, 0);
140        assert_eq!(ops[0].1, OpCode::PUSH1);
141        assert_eq!(ops[1].0, 2);
142        assert_eq!(ops[1].1, OpCode::PUSH2);
143        assert_eq!(ops[2].0, 5);
144        assert_eq!(ops[2].1, OpCode::STOP);
145    }
146
147    #[test]
148    fn memory_operations() {
149        // PUSH1 0x20, PUSH1 0x00, MSTORE, PUSH1 0x00, MLOAD, STOP
150        let bytes = hex::decode("602060005260005100").unwrap();
151        let ops = disassemble(&bytes).unwrap();
152        assert_eq!(ops.len(), 6);
153        assert_eq!(ops[0], (0, OpCode::PUSH1)); // PUSH1 32
154        assert_eq!(ops[1], (2, OpCode::PUSH1)); // PUSH1 0  
155        assert_eq!(ops[2], (4, OpCode::MSTORE)); // MSTORE (store to memory)
156        assert_eq!(ops[3], (5, OpCode::PUSH1)); // PUSH1 0
157        assert_eq!(ops[4], (7, OpCode::MLOAD)); // MLOAD (load from memory)
158        assert_eq!(ops[5], (8, OpCode::STOP)); // STOP
159    }
160
161    #[test]
162    fn stack_operations() {
163        // PUSH1 0x01, PUSH1 0x02, DUP1, SWAP1, ADD, STOP
164        let bytes = hex::decode("6001600280900100").unwrap();
165        let ops = disassemble(&bytes).unwrap();
166        assert_eq!(ops.len(), 6);
167        assert_eq!(ops[0], (0, OpCode::PUSH1)); // PUSH1 1
168        assert_eq!(ops[1], (2, OpCode::PUSH1)); // PUSH1 2
169        assert_eq!(ops[2], (4, OpCode::DUP1)); // DUP1 (duplicate top stack item)
170        assert_eq!(ops[3], (5, OpCode::SWAP1)); // SWAP1 (swap top 2 stack items)
171        assert_eq!(ops[4], (6, OpCode::ADD)); // ADD
172        assert_eq!(ops[5], (7, OpCode::STOP)); // STOP
173    }
174
175    #[test]
176    fn storage_and_crypto() {
177        // PUSH1 0x42, PUSH1 0x00, SSTORE, PUSH1 0x00, SLOAD, KECCAK256, STOP
178        let bytes = hex::decode("60426000556000542000").unwrap();
179        let ops = disassemble(&bytes).unwrap();
180        assert_eq!(ops.len(), 7);
181        assert_eq!(ops[0], (0, OpCode::PUSH1)); // PUSH1 0x42
182        assert_eq!(ops[1], (2, OpCode::PUSH1)); // PUSH1 0
183        assert_eq!(ops[2], (4, OpCode::SSTORE)); // SSTORE (store to storage)
184        assert_eq!(ops[3], (5, OpCode::PUSH1)); // PUSH1 0  
185        assert_eq!(ops[4], (7, OpCode::SLOAD)); // SLOAD (load from storage)
186        assert_eq!(ops[5], (8, OpCode::KECCAK256)); // KECCAK256 (hash function)
187        assert_eq!(ops[6], (9, OpCode::STOP)); // STOP
188    }
189
190    #[test]
191    fn empty_bytecode_error() {
192        let bytes = vec![];
193        let result = disassemble(&bytes);
194        assert!(result.is_err());
195        match result.unwrap_err() {
196            DisassemblyError::EmptyBytecode => {} // Expected
197            _ => panic!("Expected EmptyBytecode error"),
198        }
199    }
200
201    #[test]
202    fn test_get_stats() {
203        // PUSH1 0xFF, PUSH1 0x01, ADD, STOP
204        let bytes = hex::decode("60FF60010100").unwrap();
205        let stats = get_stats(&bytes).unwrap();
206
207        assert_eq!(stats.byte_len, 6);
208        assert_eq!(stats.opcode_count, 4);
209        assert!(stats.max_stack_depth > 0);
210    }
211
212    #[test]
213    fn test_stats_with_empty_bytecode() {
214        let bytes = vec![];
215        let result = get_stats(&bytes);
216        assert!(result.is_err());
217        match result.unwrap_err() {
218            DisassemblyError::EmptyBytecode => {} // Expected
219            _ => panic!("Expected EmptyBytecode error"),
220        }
221    }
222}