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/// All legacy bytecode is padded with 33 zero bytes at the end. This padding ensures the
18/// bytecode always ends with a valid STOP (0x00) opcode. The reason for 33 bytes padding (and not one byte)
19/// is handling the edge cases  where a PUSH32 opcode appears at the end of the original
20/// bytecode without enough remaining bytes for its immediate data. Original bytecode length
21/// is stored in order to be able to copy 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    /// Bytecode with 33 zero bytes padding
33    bytecode: Bytes,
34    /// Original bytes length
35    original_len: usize,
36    /// 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    /// Creates new analyzed bytecode.
53    ///
54    /// # Panics
55    ///
56    /// * If `original_len` is greater than `bytecode.len()`
57    /// * If jump table length is less than `original_len`.
58    /// * If last bytecode byte is not `0x00` or if bytecode is empty.
59    pub fn new(bytecode: Bytes, original_len: usize, jump_table: JumpTable) -> Self {
60        if original_len > bytecode.len() {
61            panic!("original_len is greater than bytecode length");
62        }
63        if original_len > jump_table.0.len() {
64            panic!(
65                "jump table length {} is less than original length {}",
66                jump_table.0.len(),
67                original_len
68            );
69        }
70
71        if bytecode.is_empty() {
72            panic!("bytecode cannot be empty");
73        }
74
75        if bytecode.last() != Some(&opcode::STOP) {
76            panic!("last bytecode byte should be STOP (0x00)");
77        }
78
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    use std::sync::Arc;
120
121    #[test]
122    fn test_bytecode_new() {
123        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
124        let bytecode = LegacyRawBytecode(bytecode).into_analyzed();
125        let _ = LegacyAnalyzedBytecode::new(
126            bytecode.bytecode,
127            bytecode.original_len,
128            bytecode.jump_table,
129        );
130    }
131
132    #[test]
133    #[should_panic(expected = "original_len is greater than bytecode length")]
134    fn test_panic_on_large_original_len() {
135        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
136        let bytecode = LegacyRawBytecode(bytecode).into_analyzed();
137        let _ = LegacyAnalyzedBytecode::new(bytecode.bytecode, 100, bytecode.jump_table);
138    }
139
140    #[test]
141    #[should_panic(expected = "jump table length 1 is less than original length 2")]
142    fn test_panic_on_short_jump_table() {
143        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
144        let bytecode = LegacyRawBytecode(bytecode).into_analyzed();
145        let jump_table = JumpTable(Arc::new(bitvec![u8, Lsb0; 0; 1]));
146        let _ = LegacyAnalyzedBytecode::new(bytecode.bytecode, bytecode.original_len, jump_table);
147    }
148
149    #[test]
150    #[should_panic(expected = "last bytecode byte should be STOP (0x00)")]
151    fn test_panic_on_non_stop_bytecode() {
152        let bytecode = Bytes::from_static(&[opcode::PUSH1, 0x01]);
153        let jump_table = JumpTable(Arc::new(bitvec![u8, Lsb0; 0; 2]));
154        let _ = LegacyAnalyzedBytecode::new(bytecode, 2, jump_table);
155    }
156
157    #[test]
158    #[should_panic(expected = "bytecode cannot be empty")]
159    fn test_panic_on_empty_bytecode() {
160        let bytecode = Bytes::from_static(&[]);
161        let jump_table = JumpTable(Arc::new(bitvec![u8, Lsb0; 0; 0]));
162        let _ = LegacyAnalyzedBytecode::new(bytecode, 0, jump_table);
163    }
164}