revm_bytecode/legacy/
analyzed.rs

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