revm_bytecode/legacy/
analyzed.rs

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