revm_bytecode/legacy/
analyzed.rs

1use super::JumpTable;
2use primitives::Bytes;
3
4/// Legacy analyzed bytecode represents the original bytecode format used in Ethereum.
5///
6/// # Jump Table
7///
8/// A jump table maps valid jump destinations in the bytecode.
9///
10/// While other EVM implementations typically analyze bytecode and cache jump tables at runtime,
11/// Revm requires the jump table to be pre-computed and contained alongside the code,
12/// and present with the bytecode when executing.
13///
14/// # Bytecode Padding
15///
16/// Legacy bytecode can be padded with up to 33 zero bytes at the end. This padding ensures that:
17/// - the bytecode always ends with a valid STOP (0x00) opcode.
18/// - there aren't incomplete immediates, meaning we can skip bounds checks in `PUSH*` instructions.
19///
20/// The non-padded length is stored in order to be able to copy the original bytecode.
21///
22/// # Gas safety
23///
24/// When bytecode is created through CREATE, CREATE2, or contract creation transactions, it undergoes
25/// analysis to generate its jump table. This analysis is O(n) on side of bytecode that is expensive,
26/// but the high gas cost required to store bytecode in the database is high enough to cover the
27/// expense of doing analysis and generate the jump table.
28#[derive(Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct LegacyAnalyzedBytecode {
31    /// The potentially padded bytecode.
32    bytecode: Bytes,
33    /// The original bytecode length.
34    original_len: usize,
35    /// The jump table.
36    jump_table: JumpTable,
37}
38
39impl Default for LegacyAnalyzedBytecode {
40    #[inline]
41    fn default() -> Self {
42        Self {
43            bytecode: Bytes::from_static(&[0]),
44            original_len: 0,
45            jump_table: JumpTable::default(),
46        }
47    }
48}
49
50impl LegacyAnalyzedBytecode {
51    /// Analyzes the bytecode.
52    ///
53    /// See [`LegacyAnalyzedBytecode`] for more details.
54    pub fn analyze(bytecode: Bytes) -> Self {
55        let original_len = bytecode.len();
56        let (jump_table, padded_bytecode) = super::analysis::analyze_legacy(bytecode);
57        Self::new(padded_bytecode, original_len, jump_table)
58    }
59
60    /// Creates new analyzed bytecode.
61    ///
62    /// Prefer instantiating using [`analyze`](Self::analyze) instead.
63    ///
64    /// # Panics
65    ///
66    /// * If `original_len` is greater than `bytecode.len()`
67    /// * If jump table length is less than `original_len`.
68    /// * If bytecode is empty.
69    pub fn new(bytecode: Bytes, original_len: usize, jump_table: JumpTable) -> Self {
70        assert!(
71            original_len <= bytecode.len(),
72            "original_len is greater than bytecode length"
73        );
74        assert!(
75            original_len <= jump_table.len(),
76            "jump table length is less than original length"
77        );
78        assert!(!bytecode.is_empty(), "bytecode cannot be empty");
79        Self {
80            bytecode,
81            original_len,
82            jump_table,
83        }
84    }
85
86    /// Returns a reference to the bytecode.
87    ///
88    /// The bytecode is padded with 32 zero bytes.
89    pub fn bytecode(&self) -> &Bytes {
90        &self.bytecode
91    }
92
93    /// Returns original bytes length.
94    pub fn original_len(&self) -> usize {
95        self.original_len
96    }
97
98    /// Returns original bytes without padding.
99    pub fn original_bytes(&self) -> Bytes {
100        self.bytecode.slice(..self.original_len)
101    }
102
103    /// Returns original bytes without padding.
104    pub fn original_byte_slice(&self) -> &[u8] {
105        &self.bytecode[..self.original_len]
106    }
107
108    /// Returns [JumpTable] of analyzed bytes.
109    pub fn jump_table(&self) -> &JumpTable {
110        &self.jump_table
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117    use crate::{opcode, LegacyRawBytecode};
118    use bitvec::{bitvec, order::Lsb0};
119
120    #[test]
121    fn test_bytecode_new() {
122        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
123        let bytecode = LegacyRawBytecode(bytecode).into_analyzed();
124        let _ = LegacyAnalyzedBytecode::new(
125            bytecode.bytecode,
126            bytecode.original_len,
127            bytecode.jump_table,
128        );
129    }
130
131    #[test]
132    #[should_panic(expected = "original_len is greater than bytecode length")]
133    fn test_panic_on_large_original_len() {
134        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
135        let bytecode = LegacyRawBytecode(bytecode).into_analyzed();
136        let _ = LegacyAnalyzedBytecode::new(bytecode.bytecode, 100, bytecode.jump_table);
137    }
138
139    #[test]
140    #[should_panic(expected = "jump table length is less than original length")]
141    fn test_panic_on_short_jump_table() {
142        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
143        let bytecode = LegacyRawBytecode(bytecode).into_analyzed();
144        let jump_table = JumpTable::new(bitvec![u8, Lsb0; 0; 1]);
145        let _ = LegacyAnalyzedBytecode::new(bytecode.bytecode, bytecode.original_len, jump_table);
146    }
147
148    #[test]
149    #[should_panic(expected = "bytecode cannot be empty")]
150    fn test_panic_on_empty_bytecode() {
151        let bytecode = Bytes::from_static(&[]);
152        let jump_table = JumpTable::new(bitvec![u8, Lsb0; 0; 0]);
153        let _ = LegacyAnalyzedBytecode::new(bytecode, 0, jump_table);
154    }
155}