smolder_core/
bytecode.rs

1//! Bytecode handling utilities
2//!
3//! Provides type-safe bytecode operations including parsing, validation,
4//! and hash computation.
5
6use crate::error::{Error, Result};
7use alloy::primitives::keccak256;
8
9/// Represents compiled contract bytecode
10#[derive(Debug, Clone)]
11pub struct Bytecode {
12    bytes: Vec<u8>,
13}
14
15impl Bytecode {
16    /// Create bytecode from a hex string (with or without 0x prefix)
17    pub fn from_hex(hex: &str) -> Result<Self> {
18        let clean = hex.trim_start_matches("0x");
19        if clean.is_empty() {
20            return Ok(Self { bytes: Vec::new() });
21        }
22        let bytes = hex::decode(clean)?;
23        Ok(Self { bytes })
24    }
25
26    /// Create bytecode from raw bytes
27    pub fn from_bytes(bytes: Vec<u8>) -> Self {
28        Self { bytes }
29    }
30
31    /// Compute the keccak256 hash of the bytecode
32    pub fn hash(&self) -> String {
33        if self.bytes.is_empty() {
34            return String::new();
35        }
36        format!("{:x}", keccak256(&self.bytes))
37    }
38
39    /// Check if the bytecode is empty or invalid
40    pub fn is_empty(&self) -> bool {
41        self.bytes.is_empty()
42    }
43
44    /// Get the bytecode length in bytes
45    pub fn len(&self) -> usize {
46        self.bytes.len()
47    }
48
49    /// Get the raw bytes
50    pub fn as_bytes(&self) -> &[u8] {
51        &self.bytes
52    }
53
54    /// Convert to hex string (with 0x prefix)
55    pub fn to_hex(&self) -> String {
56        if self.bytes.is_empty() {
57            return "0x".to_string();
58        }
59        format!("0x{}", hex::encode(&self.bytes))
60    }
61}
62
63/// Check if a hex string represents valid bytecode (non-empty and decodable)
64pub fn is_valid_bytecode(hex: &str) -> bool {
65    let clean = hex.trim_start_matches("0x");
66    !clean.is_empty() && hex::decode(clean).is_ok()
67}
68
69/// Compute keccak256 hash of bytecode hex string
70pub fn compute_bytecode_hash(hex: &str) -> Result<String> {
71    let bytecode = Bytecode::from_hex(hex)?;
72    Ok(bytecode.hash())
73}
74
75/// Parse a hex block number (e.g., "0x1a4" -> 420)
76pub fn parse_hex_block_number(hex: &str) -> Result<i64> {
77    let clean = hex.trim_start_matches("0x");
78    i64::from_str_radix(clean, 16)
79        .map_err(|_| Error::Validation(format!("Invalid hex block number: {}", hex)))
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn test_bytecode_from_hex() {
88        let bytecode = Bytecode::from_hex("0x6080604052").unwrap();
89        assert!(!bytecode.is_empty());
90        assert_eq!(bytecode.len(), 5);
91    }
92
93    #[test]
94    fn test_bytecode_from_hex_no_prefix() {
95        let bytecode = Bytecode::from_hex("6080604052").unwrap();
96        assert!(!bytecode.is_empty());
97        assert_eq!(bytecode.len(), 5);
98    }
99
100    #[test]
101    fn test_bytecode_empty() {
102        let bytecode = Bytecode::from_hex("").unwrap();
103        assert!(bytecode.is_empty());
104        assert_eq!(bytecode.hash(), "");
105    }
106
107    #[test]
108    fn test_bytecode_hash() {
109        let bytecode = Bytecode::from_hex("0x6080604052").unwrap();
110        let hash = bytecode.hash();
111        assert!(!hash.is_empty());
112        assert_eq!(hash.len(), 64); // 32 bytes = 64 hex chars
113    }
114
115    #[test]
116    fn test_bytecode_to_hex() {
117        let bytecode = Bytecode::from_hex("6080604052").unwrap();
118        assert_eq!(bytecode.to_hex(), "0x6080604052");
119    }
120
121    #[test]
122    fn test_is_valid_bytecode() {
123        assert!(is_valid_bytecode("0x6080604052"));
124        assert!(is_valid_bytecode("6080604052"));
125        assert!(!is_valid_bytecode(""));
126        assert!(!is_valid_bytecode("0x"));
127        assert!(!is_valid_bytecode("not_hex"));
128    }
129
130    #[test]
131    fn test_compute_bytecode_hash() {
132        let hash = compute_bytecode_hash("0x6080604052").unwrap();
133        assert_eq!(hash.len(), 64);
134    }
135
136    #[test]
137    fn test_parse_hex_block_number() {
138        assert_eq!(parse_hex_block_number("0x1a4").unwrap(), 420);
139        assert_eq!(parse_hex_block_number("1a4").unwrap(), 420);
140        assert_eq!(parse_hex_block_number("0x0").unwrap(), 0);
141        assert_eq!(parse_hex_block_number("0xff").unwrap(), 255);
142    }
143
144    #[test]
145    fn test_parse_hex_block_number_invalid() {
146        assert!(parse_hex_block_number("not_hex").is_err());
147    }
148}