Skip to main content

huff_neo_utils/
builtin_eval.rs

1//! Builtin Function Evaluation
2//!
3//! This module provides compile-time evaluation of Huff builtin functions.
4//! These functions compute constant values that can be used in constant expressions.
5
6use crate::ast::span::AstSpan;
7use crate::error::{CodegenError, CodegenErrorKind};
8use crate::prelude::{BuiltinFunctionArg, BuiltinFunctionCall, Contract, PushValue};
9use alloy_primitives::{B256, hex, keccak256};
10
11/// Creates a CodegenError for invalid arguments
12fn invalid_arguments_error(msg: impl Into<String>, span: &AstSpan) -> CodegenError {
13    CodegenError { kind: CodegenErrorKind::InvalidArguments(msg.into()), span: span.clone_box(), token: None }
14}
15
16/// Creates a CodegenError for invalid hex strings
17fn invalid_hex_error(hex_str: impl Into<String>, span: &AstSpan) -> CodegenError {
18    CodegenError { kind: CodegenErrorKind::InvalidHex(hex_str.into()), span: span.clone_box(), token: None }
19}
20
21/// Validates that the builtin function call has exactly the expected number of arguments
22fn validate_arg_count(bf: &BuiltinFunctionCall, expected: usize, fn_name: &str) -> Result<(), CodegenError> {
23    if bf.args.len() != expected {
24        return Err(invalid_arguments_error(
25            format!("Incorrect number of arguments passed to {}, should be {}: {}", fn_name, expected, bf.args.len()),
26            &bf.span,
27        ));
28    }
29    Ok(())
30}
31
32/// Evaluates __EVENT_HASH builtin function
33pub fn eval_event_hash(contract: &Contract, bf: &BuiltinFunctionCall) -> Result<PushValue, CodegenError> {
34    validate_arg_count(bf, 1, "__EVENT_HASH")?;
35
36    let arg_str = match &bf.args[0] {
37        BuiltinFunctionArg::StringLiteral(s, _) => s.clone(),
38        BuiltinFunctionArg::Identifier(name, _) => name.clone(),
39        _ => {
40            return Err(invalid_arguments_error("Expected string literal or identifier as argument to __EVENT_HASH", &bf.span));
41        }
42    };
43
44    let hash = if let Some(event) = contract.events.iter().find(|e| e.name.eq(&arg_str)) { event.hash } else { keccak256(&arg_str).0 };
45    Ok(PushValue::from(hash))
46}
47
48/// Evaluates __FUNC_SIG builtin function
49pub fn eval_function_signature(contract: &Contract, bf: &BuiltinFunctionCall) -> Result<PushValue, CodegenError> {
50    validate_arg_count(bf, 1, "__FUNC_SIG")?;
51
52    let arg_str = match &bf.args[0] {
53        BuiltinFunctionArg::StringLiteral(s, _) => s.clone(),
54        BuiltinFunctionArg::Identifier(name, _) => name.clone(),
55        _ => {
56            return Err(invalid_arguments_error("Expected string literal or identifier as argument to __FUNC_SIG", &bf.span));
57        }
58    };
59
60    let selector = if let Some(func) = contract.functions.iter().find(|f| f.name.eq(&arg_str)) {
61        func.signature
62    } else if let Some(error) = contract.errors.iter().find(|e| e.name.eq(&arg_str)) {
63        error.selector
64    } else {
65        keccak256(&arg_str)[..4].try_into().unwrap()
66    };
67
68    // Left-pad the 4-byte selector to 32 bytes for B256
69    let mut bytes = [0u8; 32];
70    bytes[28..32].copy_from_slice(&selector);
71    Ok(PushValue::from(bytes))
72}
73
74/// Evaluates __BYTES builtin function
75pub fn eval_builtin_bytes(bf: &BuiltinFunctionCall) -> Result<PushValue, CodegenError> {
76    validate_arg_count(bf, 1, "__BYTES")?;
77    let first_arg = match &bf.args[0] {
78        BuiltinFunctionArg::StringLiteral(s, _) => s.clone(),
79        _ => {
80            return Err(invalid_arguments_error("Expected string literal as argument to __BYTES", &bf.span));
81        }
82    };
83
84    if first_arg.is_empty() {
85        return Err(invalid_arguments_error("Empty string passed to __BYTES", &bf.span));
86    }
87
88    let bytes = first_arg.as_bytes();
89    if bytes.len() > 32 {
90        return Err(invalid_arguments_error("Encoded bytes length exceeds 32 bytes", &bf.span));
91    }
92
93    let mut bytes_array = [0u8; 32];
94    bytes_array[32 - bytes.len()..].copy_from_slice(bytes);
95    Ok(PushValue::from(bytes_array))
96}
97
98/// Direction for padding operations
99#[derive(Debug, Clone, Copy, PartialEq)]
100pub enum PadDirection {
101    /// Left padding: zeros on the left, data on the right
102    Left,
103    /// Right padding: data on the left, zeros on the right
104    Right,
105}
106
107impl std::fmt::Display for PadDirection {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        match self {
110            PadDirection::Left => write!(f, "__LEFTPAD"),
111            PadDirection::Right => write!(f, "__RIGHTPAD"),
112        }
113    }
114}
115
116/// Evaluates __LEFTPAD or __RIGHTPAD builtin function
117///
118/// This is a simplified version that only handles literal arguments and nested builtin calls,
119/// but not constant references. For constant references, the caller should resolve them first.
120pub fn eval_builtin_pad_simple(bf: &BuiltinFunctionCall, direction: PadDirection) -> Result<PushValue, CodegenError> {
121    validate_arg_count(bf, 1, &direction.to_string())?;
122
123    let first_arg = match &bf.args[0] {
124        BuiltinFunctionArg::HexLiteral(hex, _) => hex.clone(),
125        BuiltinFunctionArg::BuiltinFunctionCall(inner_call) => {
126            // Recursively evaluate nested builtin calls
127            let push_value = match inner_call.kind {
128                crate::prelude::BuiltinFunctionKind::FunctionSignature => eval_function_signature(&Contract::default(), inner_call)?,
129                crate::prelude::BuiltinFunctionKind::Bytes => eval_builtin_bytes(inner_call)?,
130                crate::prelude::BuiltinFunctionKind::EventHash => eval_event_hash(&Contract::default(), inner_call)?,
131                _ => {
132                    return Err(invalid_arguments_error(format!("Invalid function call argument type passed to {direction}"), &bf.span));
133                }
134            };
135            push_value.to_hex_trimmed()
136        }
137        _ => {
138            return Err(invalid_arguments_error(
139                format!("Expected hex literal or builtin function call as argument to {direction}"),
140                &bf.span,
141            ));
142        }
143    };
144
145    // Ensure hex string has even length for decoding
146    // For RIGHTPAD with odd-length hex, append "0" on the right to preserve visual appearance
147    // For LEFTPAD with odd-length hex, prepend "0" on the left
148    let hex = if first_arg.len() % 2 == 1 {
149        if direction == PadDirection::Right { format!("{first_arg}0") } else { format!("0{first_arg}") }
150    } else {
151        first_arg
152    };
153
154    // Parse hex string to bytes
155    let hex_bytes = hex::decode(&hex).map_err(|_| invalid_hex_error(&hex, &bf.span))?;
156
157    if hex_bytes.len() > 32 {
158        return Err(invalid_arguments_error("Hex string exceeds 32 bytes", &bf.span));
159    }
160
161    let mut bytes_array = [0u8; 32];
162    if direction == PadDirection::Left {
163        // Left pad: zeros on the left, data on the right
164        let start_idx = 32 - hex_bytes.len();
165        bytes_array[start_idx..].copy_from_slice(&hex_bytes);
166    } else {
167        // Right pad: data on the left, zeros on the right
168        bytes_array[..hex_bytes.len()].copy_from_slice(&hex_bytes);
169    }
170
171    Ok(PushValue::new(B256::from(bytes_array)))
172}