Skip to main content

huff_neo_codegen/irgen/
constants.rs

1use crate::Codegen;
2use alloy_primitives::U256;
3use huff_neo_utils::ast::span::AstSpan;
4use huff_neo_utils::prelude::{CodegenError, CodegenErrorKind, ConstVal, ConstantDefinition, Contract, PushValue, str_to_bytes32};
5
6/// Transforms a constant definition into a PushValue
7/// Returns None for __NOOP constants (which generate no bytecode)
8pub fn constant_gen(name: &str, contract: &Contract, ir_byte_span: &AstSpan) -> Result<Option<PushValue>, CodegenError> {
9    let constant = lookup_constant(name, contract, ir_byte_span)?;
10
11    // Generate bytecode for the constant
12    // Should always be a `Literal` if storage pointers were derived in the AST
13    // prior to generating the IR bytes.
14    tracing::info!(target: "codegen", "FOUND CONSTANT DEFINITION: {}", constant.name);
15    let push_value = match &constant.value {
16        ConstVal::Bytes(bytes) => {
17            let hex_literal = str_to_bytes32(&bytes.as_str());
18            Some(PushValue::from(hex_literal))
19        }
20        ConstVal::String(_s) => {
21            // String constants cannot be used directly - they must be used with __BYTES
22            tracing::error!(target: "codegen", "STRING CONSTANT \"{}\" CANNOT BE USED DIRECTLY", constant.name);
23            return Err(CodegenError {
24                kind: CodegenErrorKind::StringConstantNotBytes(format!(
25                    "String constant [{}] cannot be pushed directly. Use __BYTES([{}]) to convert it to bytes.",
26                    name, name
27                )),
28                span: ir_byte_span.clone_box(),
29                token: None,
30            });
31        }
32        ConstVal::StoragePointer(sp) => Some(PushValue::from(sp)),
33        ConstVal::BuiltinFunctionCall(bf) => Some(Codegen::gen_builtin_bytecode(contract, bf, ir_byte_span.clone())?),
34        ConstVal::Expression(expr) => {
35            tracing::info!(target: "codegen", "EVALUATING CONSTANT EXPRESSION FOR \"{}\"", constant.name);
36            let evaluated = contract.evaluate_constant_expression(expr)?;
37            Some(PushValue::from(evaluated))
38        }
39        ConstVal::Noop => {
40            tracing::info!(target: "codegen", "CONSTANT \"{}\" IS __NOOP - GENERATING NO BYTECODE", constant.name);
41            None // Generate no bytecode
42        }
43        ConstVal::FreeStoragePointer(fsp) => {
44            // If this is reached in codegen stage, the `derive_storage_pointers`
45            // method was not called on the AST.
46            tracing::error!(target: "codegen", "STORAGE POINTERS INCORRECTLY DERIVED FOR \"{:?}\"", fsp);
47            return Err(CodegenError { kind: CodegenErrorKind::StoragePointersNotDerived, span: constant.span.clone_box(), token: None });
48        }
49    };
50
51    Ok(push_value)
52}
53
54/// Looks up a constant definition by name
55pub fn lookup_constant(name: &str, contract: &Contract, ir_byte_span: &AstSpan) -> Result<ConstantDefinition, CodegenError> {
56    // Get the first `ConstantDefinition` that matches the constant's name
57    let constants = contract.constants.lock().map_err(|_| CodegenError::new(CodegenErrorKind::LockingError, AstSpan(vec![]), None))?;
58    let constant = if let Some(m) = constants.iter().find(|const_def| const_def.name.eq(&name)) {
59        m.clone()
60    } else {
61        tracing::error!(target: "codegen", "MISSING CONSTANT DEFINITION \"{}\"", name);
62
63        return Err(CodegenError {
64            kind: CodegenErrorKind::MissingConstantDefinition(name.to_string()),
65            span: ir_byte_span.clone_box(),
66            token: None,
67        });
68    };
69    Ok(constant)
70}
71
72/// Evaluates a constant to its numeric value as a usize
73///
74/// This is useful for builtin functions that need numeric constant values
75/// (e.g., __ASSERT_PC) without generating bytecode.
76///
77/// Returns an error if:
78/// - The constant doesn't exist
79/// - The constant is __NOOP (has no numeric value)
80/// - The constant is a builtin function call
81/// - The constant value exceeds usize::MAX
82pub fn evaluate_constant_value(name: &str, contract: &Contract, span: &AstSpan) -> Result<usize, CodegenError> {
83    let constant = lookup_constant(name, contract, span)?;
84
85    let literal = match &constant.value {
86        ConstVal::Bytes(bytes) => str_to_bytes32(&bytes.as_str()),
87        ConstVal::Expression(expr) => {
88            tracing::info!(target: "codegen", "EVALUATING CONSTANT EXPRESSION \"{}\" FOR NUMERIC VALUE", constant.name);
89            contract.evaluate_constant_expression(expr)?
90        }
91        ConstVal::StoragePointer(lit) => *lit,
92        ConstVal::String(_) => {
93            return Err(CodegenError {
94                kind: CodegenErrorKind::InvalidArguments(format!(
95                    "Constant [{}] is a string which cannot be evaluated to a numeric value",
96                    name
97                )),
98                span: span.clone_box(),
99                token: None,
100            });
101        }
102        ConstVal::Noop => {
103            return Err(CodegenError {
104                kind: CodegenErrorKind::InvalidArguments(format!(
105                    "Constant [{}] is __NOOP which has no numeric value and cannot be used as a bytecode position",
106                    name
107                )),
108                span: span.clone_box(),
109                token: None,
110            });
111        }
112        ConstVal::BuiltinFunctionCall(bf) => {
113            return Err(CodegenError {
114                kind: CodegenErrorKind::InvalidArguments(format!(
115                    "Constant [{}] is a builtin function call ({}) which cannot be evaluated to a numeric position. \
116                     Only literal values, arithmetic expressions, and storage pointers are supported for __ASSERT_PC",
117                    name, bf.kind
118                )),
119                span: span.clone_box(),
120                token: None,
121            });
122        }
123        ConstVal::FreeStoragePointer(_) => {
124            return Err(CodegenError { kind: CodegenErrorKind::StoragePointersNotDerived, span: span.clone_box(), token: None });
125        }
126    };
127
128    // Convert [u8; 32] to U256, then to usize
129    let value = U256::from_be_bytes(literal);
130
131    // Check if value fits in usize
132    // Note: usize is sufficient for bytecode positions because in realistic scenarios,
133    // EVM contract bytecode size is limited by the block gas limit and the 24KB contract
134    // size limit (EIP-170).
135    if value > U256::from(usize::MAX) {
136        return Err(CodegenError {
137            kind: CodegenErrorKind::InvalidArguments(format!(
138                "Constant [{}] value ({}) exceeds maximum bytecode position size ({}). \
139                 Bytecode positions must fit within a usize for the target platform",
140                name,
141                value,
142                usize::MAX
143            )),
144            span: span.clone_box(),
145            token: None,
146        });
147    }
148
149    Ok(value.to::<usize>())
150}