Skip to main content

huff_neo_utils/
push_value.rs

1//! EVM Push Value
2//!
3//! This module provides the `PushValue` type for representing values to be pushed onto the EVM stack.
4
5use crate::bytes_util::literal_gen;
6use crate::evm_version::EVMVersion;
7use alloy_primitives::{B256, hex};
8
9/// Represents a value to be pushed onto the EVM stack
10///
11/// This type encapsulates a 32-byte value and provides methods to:
12/// - Generate complete bytecode with optimal PUSH opcode (PUSH0-PUSH32)
13/// - Extract just the hex data without any opcode
14#[derive(Debug, Clone, PartialEq)]
15pub struct PushValue {
16    /// The 32-byte value to push
17    pub value: B256,
18}
19
20impl PushValue {
21    /// Creates a new PushValue from a B256 value
22    pub fn new(value: B256) -> PushValue {
23        PushValue { value }
24    }
25
26    /// Converts the PushValue to bytecode with the appropriate PUSH opcode
27    ///
28    /// This generates optimal bytecode by:
29    /// - Trimming leading zeros from the value
30    /// - Selecting the smallest PUSH opcode (PUSH0-PUSH32)
31    /// - PUSH0 if the EVM version supports it and the value is zero
32    /// - Encoding the result as a hex string
33    ///
34    /// Use this for normal macro/function bytecode generation.
35    ///
36    /// # Examples
37    /// - Value `0x01` → `"6001"` (PUSH1 0x01)
38    /// - Value `0x00` → `"5f"` (PUSH0, if supported by EVM version)
39    /// - Value `0x123456...` → `"7f123456..."` (PUSH32)
40    pub fn to_hex_with_opcode(&self, evm_version: &EVMVersion) -> String {
41        literal_gen(evm_version, &self.value.0)
42    }
43
44    /// Returns the hex-encoded data without any PUSH opcode
45    ///
46    /// This trims leading zeros and returns just the hex data.
47    /// Use this for padding operations or when you need just the raw data without leading zeros.
48    ///
49    /// # Examples
50    /// - Value `0x0000...0001` → `"01"`
51    /// - Value `0x0000...1234` → `"1234"`
52    /// - Value `0x0000...0000` → `""` (empty string)
53    pub fn to_hex_trimmed(&self) -> String {
54        // Find the first non-zero byte to trim leading zeros
55        let first_non_zero = self.value.0.iter().position(|&b| b != 0).unwrap_or(32);
56        let trimmed_bytes = &self.value.0[first_non_zero..];
57        hex::encode(trimmed_bytes)
58    }
59
60    /// Returns the full 32-byte hex-encoded data without any PUSH opcode
61    ///
62    /// This always returns the full 64-character hex string (32 bytes).
63    /// Use this for code tables where the full 32-byte value must be preserved.
64    ///
65    /// # Examples
66    /// - Value `0x0000...0001` → `"0000000000000000000000000000000000000000000000000000000000000001"`
67    /// - Value `0x0000...1234` → `"0000000000000000000000000000000000000000000000000000000000001234"`
68    /// - Value `0x0000...0000` → `"0000000000000000000000000000000000000000000000000000000000000000"`
69    pub fn to_hex_full(&self) -> String {
70        hex::encode(self.value.0)
71    }
72
73    /// Converts the full 32-byte value to hex string with PUSH32 opcode prefix (0x7f).
74    ///
75    /// Always generates a PUSH32 instruction (33 bytes total: 1 byte opcode + 32 bytes data),
76    /// regardless of leading zeros. This is useful for operations that require exact 32-byte
77    /// values on the stack, such as padding functions.
78    ///
79    /// # Examples
80    /// - Value `0x0000...0001` → `"7f0000000000000000000000000000000000000000000000000000000000000001"`
81    /// - Value `0x0000...1234` → `"7f0000000000000000000000000000000000000000000000000000000000001234"`
82    /// - Value `0xFFFF...FFFF` → `"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`
83    pub fn to_hex_full_with_opcode(&self) -> String {
84        format!("7f{}", self.to_hex_full())
85    }
86}
87
88/// Convert from a 32-byte array directly to PushValue
89impl From<[u8; 32]> for PushValue {
90    fn from(bytes: [u8; 32]) -> Self {
91        PushValue { value: B256::from(bytes) }
92    }
93}
94
95/// Convert from a reference to a 32-byte array to PushValue
96impl From<&[u8; 32]> for PushValue {
97    fn from(bytes: &[u8; 32]) -> Self {
98        PushValue { value: B256::from(*bytes) }
99    }
100}
101
102/// Convert from B256 to PushValue
103impl From<B256> for PushValue {
104    fn from(value: B256) -> Self {
105        PushValue { value }
106    }
107}
108
109/// Convert from a 4-byte array (function/error selector) to PushValue
110///
111/// This left-pads the 4 bytes to 32 bytes (zeros on the left, data on the right)
112impl From<[u8; 4]> for PushValue {
113    fn from(bytes: [u8; 4]) -> Self {
114        let mut padded = [0u8; 32];
115        padded[28..32].copy_from_slice(&bytes);
116        PushValue { value: B256::from(padded) }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::evm_version::SupportedEVMVersions;
124    use alloy_primitives::B256;
125
126    #[test]
127    fn test_to_hex_trimmed_single_byte() {
128        let mut bytes = [0u8; 32];
129        bytes[31] = 0x01;
130        let pv = PushValue::from(bytes);
131        assert_eq!(pv.to_hex_trimmed(), "01");
132    }
133
134    #[test]
135    fn test_to_hex_trimmed_multiple_bytes() {
136        let mut bytes = [0u8; 32];
137        bytes[30] = 0x12;
138        bytes[31] = 0x34;
139        let pv = PushValue::from(bytes);
140        assert_eq!(pv.to_hex_trimmed(), "1234");
141    }
142
143    #[test]
144    fn test_to_hex_trimmed_all_zeros() {
145        let bytes = [0u8; 32];
146        let pv = PushValue::from(bytes);
147        assert_eq!(pv.to_hex_trimmed(), "");
148    }
149
150    #[test]
151    fn test_to_hex_trimmed_full_32_bytes() {
152        let bytes = [0xff; 32];
153        let pv = PushValue::from(bytes);
154        assert_eq!(pv.to_hex_trimmed(), "f".repeat(64));
155    }
156
157    #[test]
158    fn test_to_hex_trimmed_leading_zeros() {
159        let mut bytes = [0u8; 32];
160        bytes[28] = 0xde;
161        bytes[29] = 0xad;
162        bytes[30] = 0xbe;
163        bytes[31] = 0xef;
164        let pv = PushValue::from(bytes);
165        assert_eq!(pv.to_hex_trimmed(), "deadbeef");
166    }
167
168    #[test]
169    fn test_to_hex_full_single_byte() {
170        let mut bytes = [0u8; 32];
171        bytes[31] = 0x01;
172        let pv = PushValue::from(bytes);
173        assert_eq!(pv.to_hex_full(), "0000000000000000000000000000000000000000000000000000000000000001");
174    }
175
176    #[test]
177    fn test_to_hex_full_multiple_bytes() {
178        let mut bytes = [0u8; 32];
179        bytes[30] = 0x12;
180        bytes[31] = 0x34;
181        let pv = PushValue::from(bytes);
182        assert_eq!(pv.to_hex_full(), "0000000000000000000000000000000000000000000000000000000000001234");
183    }
184
185    #[test]
186    fn test_to_hex_full_all_zeros() {
187        let bytes = [0u8; 32];
188        let pv = PushValue::from(bytes);
189        assert_eq!(pv.to_hex_full(), "0".repeat(64));
190    }
191
192    #[test]
193    fn test_to_hex_full_all_ones() {
194        let bytes = [0xff; 32];
195        let pv = PushValue::from(bytes);
196        assert_eq!(pv.to_hex_full(), "f".repeat(64));
197    }
198
199    #[test]
200    fn test_to_hex_full_preserves_leading_zeros() {
201        let mut bytes = [0u8; 32];
202        bytes[28] = 0xde;
203        bytes[29] = 0xad;
204        bytes[30] = 0xbe;
205        bytes[31] = 0xef;
206        let pv = PushValue::from(bytes);
207        assert_eq!(pv.to_hex_full(), "00000000000000000000000000000000000000000000000000000000deadbeef");
208    }
209
210    #[test]
211    fn test_to_hex_with_opcode_push1() {
212        let mut bytes = [0u8; 32];
213        bytes[31] = 0x42;
214        let pv = PushValue::from(bytes);
215        let evm = EVMVersion::default();
216        // PUSH1 = 0x60, so "6042" = PUSH1 0x42
217        assert_eq!(pv.to_hex_with_opcode(&evm), "6042");
218    }
219
220    #[test]
221    fn test_to_hex_with_opcode_push2() {
222        let mut bytes = [0u8; 32];
223        bytes[30] = 0x12;
224        bytes[31] = 0x34;
225        let pv = PushValue::from(bytes);
226        let evm = EVMVersion::default();
227        // PUSH2 = 0x61, so "611234" = PUSH2 0x1234
228        assert_eq!(pv.to_hex_with_opcode(&evm), "611234");
229    }
230
231    #[test]
232    fn test_to_hex_with_opcode_push0_shanghai() {
233        let pv = PushValue::from(B256::ZERO);
234        let evm = EVMVersion::new(SupportedEVMVersions::Shanghai);
235        // PUSH0 = 0x5f for Shanghai and later
236        assert_eq!(pv.to_hex_with_opcode(&evm), "5f");
237    }
238
239    #[test]
240    fn test_to_hex_with_opcode_push1_zero_pre_shanghai() {
241        let pv = PushValue::from(B256::ZERO);
242        let evm = EVMVersion::new(SupportedEVMVersions::Paris);
243        // Pre-Shanghai uses PUSH1 0x00 for zero
244        assert_eq!(pv.to_hex_with_opcode(&evm), "6000");
245    }
246
247    #[test]
248    fn test_to_hex_with_opcode_push4() {
249        let mut bytes = [0u8; 32];
250        bytes[28] = 0xde;
251        bytes[29] = 0xad;
252        bytes[30] = 0xbe;
253        bytes[31] = 0xef;
254        let pv = PushValue::from(bytes);
255        let evm = EVMVersion::default();
256        // PUSH4 = 0x63, so "63deadbeef" = PUSH4 0xdeadbeef
257        assert_eq!(pv.to_hex_with_opcode(&evm), "63deadbeef");
258    }
259
260    #[test]
261    fn test_to_hex_with_opcode_push32() {
262        let pv = PushValue::from(B256::repeat_byte(0xff));
263        let evm = EVMVersion::default();
264        // PUSH32 = 0x7f
265        assert_eq!(pv.to_hex_with_opcode(&evm), format!("7f{}", "f".repeat(64)));
266    }
267
268    #[test]
269    fn test_from_u8_array_32() {
270        let mut bytes = [0u8; 32];
271        bytes[31] = 0x42;
272        let pv = PushValue::from(bytes);
273        assert_eq!(pv.value, B256::from(bytes));
274    }
275
276    #[test]
277    fn test_from_ref_u8_array_32() {
278        let mut bytes = [0u8; 32];
279        bytes[31] = 0x42;
280        let pv = PushValue::from(&bytes);
281        assert_eq!(pv.value, B256::from(bytes));
282    }
283
284    #[test]
285    fn test_from_b256() {
286        let b256 = B256::repeat_byte(0xaa);
287        let pv = PushValue::from(b256);
288        assert_eq!(pv.value, b256);
289    }
290
291    #[test]
292    fn test_from_u8_array_4_selector() {
293        let selector = [0xde, 0xad, 0xbe, 0xef];
294        let pv = PushValue::from(selector);
295
296        // Should be left-padded to 32 bytes
297        let mut expected = [0u8; 32];
298        expected[28..32].copy_from_slice(&selector);
299        assert_eq!(pv.value, B256::from(expected));
300        assert_eq!(pv.to_hex_trimmed(), "deadbeef");
301    }
302
303    #[test]
304    fn test_into_conversion() {
305        let mut bytes = [0u8; 32];
306        bytes[31] = 0xff;
307        let pv: PushValue = bytes.into();
308        assert_eq!(pv.value, B256::from(bytes));
309    }
310
311    #[test]
312    fn test_from_b256_zero() {
313        let pv = PushValue::from(B256::ZERO);
314        assert_eq!(pv.value, B256::ZERO);
315        assert_eq!(pv.to_hex_trimmed(), "");
316    }
317}