Skip to main content

huff_neo_utils/
bytes_util.rs

1use crate::{evm_version::EVMVersion, opcodes::Opcode};
2use std::num::ParseIntError;
3
4/// Convert a string slice to a `[u8; 32]`
5/// Pads zeros to the left of significant bytes in the `[u8; 32]` slice.
6/// i.e. 0xa57b becomes `[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
7/// 0, 0, 0, 0, 0, 165, 123]`
8pub fn str_to_bytes32(s: &str) -> [u8; 32] {
9    let s = format_even_bytes(String::from(s));
10
11    let bytes: Vec<u8> = (0..s.len()).step_by(2).map(|c| u8::from_str_radix(&s[c..c + 2], 16).unwrap()).collect();
12
13    let mut padded = [0u8; 32];
14
15    for i in 32 - bytes.len()..32 {
16        padded[i] = bytes[bytes.len() - (32 - i)];
17    }
18
19    padded
20}
21
22/// Convert a `[u8; 32]` to a bytes string.
23pub fn bytes32_to_hex_string(bytes: &[u8; 32], prefixed: bool) -> String {
24    let mut s = String::default();
25    let start = bytes.iter().position(|b| *b != 0).unwrap_or(bytes.len() - 1);
26    for b in &bytes[start..bytes.len()] {
27        s = format!("{s}{:02x}", *b);
28    }
29    format!("{}{s}", if prefixed { "0x" } else { "" })
30}
31
32/// Wrapper to convert a hex string to a usize.
33pub fn hex_to_usize(s: &str) -> Result<usize, ParseIntError> {
34    usize::from_str_radix(s, 16)
35}
36
37/// Pad a hex string with n 0 bytes to the left. Will not pad a hex string that has a length
38/// greater than or equal to `num_bytes * 2`
39pub fn pad_n_bytes(hex: &str, num_bytes: usize) -> String {
40    let mut hex = hex.to_owned();
41    while hex.len() < num_bytes * 2 {
42        hex = format!("0{hex}");
43    }
44    hex
45}
46
47/// Hex-encodes `value` as the smallest PUSHn (n >= 1) that holds it
48fn push_min_width(value: usize) -> String {
49    let width = ((usize::BITS - value.leading_zeros()) as usize).div_ceil(8).max(1);
50    format!("{:02x}{:0digits$x}", 0x5f + width, value, digits = width * 2)
51}
52
53/// Builds the deployment bootstrap that copies the runtime code into memory and returns it
54///
55/// The runtime code is expected directly after the constructor body and the bootstrap itself.
56/// Push widths grow with the values, so runtimes of 64 KiB and more (allowed from Amsterdam) are
57/// encoded correctly. Returns the bootstrap hex string and its length in bytes.
58pub fn deploy_bootstrap(runtime_len: usize, ctor_body_len: usize) -> (String, usize) {
59    let size_push = push_min_width(runtime_len);
60    // The runtime offset includes the bootstrap, whose length depends on the offset's push width
61    let mut offset_width = 1;
62    loop {
63        // PUSHn <len>, DUP1, PUSHn <offset>, RETURNDATASIZE, CODECOPY, RETURNDATASIZE, RETURN
64        let bootstrap_len = size_push.len() / 2 + 1 + (1 + offset_width) + 4;
65        let offset_push = push_min_width(ctor_body_len + bootstrap_len);
66        if offset_push.len() / 2 == 1 + offset_width {
67            // 80 = DUP1; 3d = RETURNDATASIZE; 39 = CODECOPY; 3d = RETURNDATASIZE; f3 = RETURN
68            return (format!("{size_push}80{offset_push}3d393df3"), bootstrap_len);
69        }
70        offset_width += 1;
71    }
72}
73
74/// Pad odd-length byte string with a leading 0
75pub fn format_even_bytes(hex: String) -> String {
76    if hex.len() % 2 == 1 { format!("0{hex}") } else { hex }
77}
78
79/// Convert string slice to `Vec<u8>`, size not capped
80pub fn str_to_vec(s: &str) -> Result<Vec<u8>, std::num::ParseIntError> {
81    let bytes: Result<Vec<u8>, _> = (0..s.len()).step_by(2).map(|c| u8::from_str_radix(&s[c..c + 2], 16)).collect();
82    bytes
83}
84
85/// Converts a value literal to its smallest equivalent `PUSHX` bytecode. Leading zeros are removed.
86pub fn literal_gen(evm_version: &EVMVersion, l: &[u8; 32]) -> String {
87    let hex_literal: String = bytes32_to_hex_string(l, false);
88    match hex_literal.as_str() {
89        "00" => format_push0(evm_version, hex_literal),
90        _ => format_literal(hex_literal),
91    }
92}
93
94/// Formats a `PUSH0` opcode if supported by the EVM version, otherwise falls back to `PUSH1 0x00`
95fn format_push0(evm_version: &EVMVersion, hex_literal: String) -> String {
96    if evm_version.has_push0() { Opcode::Push0.to_string() } else { format_literal(hex_literal) }
97}
98
99/// Converts a literal into its bytecode string representation
100pub fn format_literal(hex_literal: String) -> String {
101    format!("{:02x}{hex_literal}", 95 + hex_literal.len() / 2)
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_deploy_bootstrap_push_widths() {
110        // Empty runtime, no constructor: PUSH1 0x00, DUP1, PUSH1 0x09, ...
111        assert_eq!(deploy_bootstrap(0, 0), ("60008060093d393df3".to_string(), 9));
112        // Runtime needs PUSH2
113        assert_eq!(deploy_bootstrap(300, 0), ("61012c80600a3d393df3".to_string(), 10));
114        // Offset crosses 0xff only once the bootstrap's own length is included
115        assert_eq!(deploy_bootstrap(1, 250), ("6001806101043d393df3".to_string(), 10));
116        // Runtime of exactly 64 KiB needs PUSH3
117        assert_eq!(deploy_bootstrap(0x10000, 0), ("6201000080600b3d393df3".to_string(), 11));
118        // Offset crossing 0xffff widens to PUSH3 as well
119        assert_eq!(deploy_bootstrap(1, 0xfff5), ("60018061ffff3d393df3".to_string(), 10));
120        assert_eq!(deploy_bootstrap(1, 0xfff6), ("600180620100013d393df3".to_string(), 11));
121    }
122}