Skip to content

Commit 861575e

Browse files
authored
fix: allow constant defined with a builtin function assigned used in table (#150)
1 parent 2e5bdaf commit 861575e

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# Huff Neo Compiler changelog
44

55
## Unreleased
6+
- Fix constants defined with builtin functions not working in code tables (fixes #149).
7+
- Example: `#define constant C = __RIGHTPAD(0x)` can now be used in tables with `[C]`.
68

79
## [1.5.5] - 2025-11-08
810
- Fix `--relax-jumps` not updating label positions always correct during iterative optimization.

crates/codegen/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,15 @@ impl Codegen {
239239
let literal = contract.evaluate_constant_expression(expr)?;
240240
Bytes::Raw(bytes_util::bytes32_to_hex_string(&literal, false))
241241
}
242+
ConstVal::BuiltinFunctionCall(bf) => {
243+
let push_value = Codegen::gen_builtin_bytecode(contract, bf, statement.span.clone())?;
244+
// Use full hex for padding functions (always 32 bytes), trimmed for others
245+
let hex_data = match bf.kind {
246+
BuiltinFunctionKind::LeftPad | BuiltinFunctionKind::RightPad => push_value.to_hex_full(),
247+
_ => push_value.to_hex_trimmed(),
248+
};
249+
Bytes::Raw(hex_data)
250+
}
242251
_ => {
243252
return Err(CodegenError {
244253
kind: CodegenErrorKind::InvalidArguments("Constant must be a hex literal or expression".to_string()),

crates/core/tests/code_table.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,172 @@ fn test_reuse_code_table_multiple_times_macro() {
372372
// Plus the 0x1234
373373
assert_eq!(mbytes, "600261000a 600261000a 1234".replace(" ", ""));
374374
}
375+
376+
#[test]
377+
fn test_code_table_builtin_constant_rightpad() {
378+
let source: &str = r#"
379+
#define constant C = __RIGHTPAD(0x)
380+
381+
#define table T {
382+
[C]
383+
}
384+
385+
#define macro MAIN() = takes(0) returns (0) {
386+
__tablesize(T)
387+
__tablestart(T)
388+
}
389+
"#;
390+
391+
// Parse tokens
392+
let flattened_source = FullFileSource { source, file: None, spans: vec![] };
393+
let lexer = Lexer::new(flattened_source);
394+
let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
395+
let mut parser = Parser::new(tokens, None);
396+
397+
// Parse the AST
398+
let mut contract = parser.parse().unwrap();
399+
400+
// Derive storage pointers
401+
contract.derive_storage_pointers();
402+
403+
// Instantiate Codegen
404+
let cg = Codegen::new();
405+
406+
// The codegen instance should have no artifact
407+
assert!(cg.artifact.is_none());
408+
409+
// Have the Codegen create the constructor bytecode
410+
let mbytes = Codegen::generate_main_bytecode(&EVMVersion::default(), &contract, None, false).unwrap();
411+
412+
// 60 = PUSH1, 20 = 32 bytes table size, 61 = PUSH2, 0005 = 5 bytes table start
413+
// RIGHTPAD(0x) produces 32 bytes of zeros
414+
assert_eq!(mbytes, "60 20 61 0005 0000000000000000000000000000000000000000000000000000000000000000".replace(" ", ""));
415+
}
416+
417+
#[test]
418+
fn test_code_table_builtin_constant_leftpad() {
419+
let source: &str = r#"
420+
#define constant PADDED = __LEFTPAD(0x1234)
421+
422+
#define table T {
423+
[PADDED]
424+
}
425+
426+
#define macro MAIN() = takes(0) returns (0) {
427+
__tablesize(T)
428+
__tablestart(T)
429+
}
430+
"#;
431+
432+
// Parse tokens
433+
let flattened_source = FullFileSource { source, file: None, spans: vec![] };
434+
let lexer = Lexer::new(flattened_source);
435+
let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
436+
let mut parser = Parser::new(tokens, None);
437+
438+
// Parse the AST
439+
let mut contract = parser.parse().unwrap();
440+
441+
// Derive storage pointers
442+
contract.derive_storage_pointers();
443+
444+
// Instantiate Codegen
445+
let cg = Codegen::new();
446+
447+
// The codegen instance should have no artifact
448+
assert!(cg.artifact.is_none());
449+
450+
// Have the Codegen create the constructor bytecode
451+
let mbytes = Codegen::generate_main_bytecode(&EVMVersion::default(), &contract, None, false).unwrap();
452+
453+
// 60 = PUSH1, 20 = 32 bytes table size, 61 = PUSH2, 0005 = 5 bytes table start
454+
assert_eq!(mbytes, "60 20 61 0005 0000000000000000000000000000000000000000000000000000000000001234".replace(" ", ""));
455+
}
456+
457+
#[test]
458+
fn test_code_table_builtin_constant_func_sig() {
459+
let source: &str = r#"
460+
#define constant TRANSFER_SIG = __FUNC_SIG("transfer(address,uint256)")
461+
462+
#define table T {
463+
[TRANSFER_SIG]
464+
}
465+
466+
#define macro MAIN() = takes(0) returns (0) {
467+
__tablesize(T)
468+
__tablestart(T)
469+
}
470+
"#;
471+
472+
// Parse tokens
473+
let flattened_source = FullFileSource { source, file: None, spans: vec![] };
474+
let lexer = Lexer::new(flattened_source);
475+
let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
476+
let mut parser = Parser::new(tokens, None);
477+
478+
// Parse the AST
479+
let mut contract = parser.parse().unwrap();
480+
481+
// Derive storage pointers
482+
contract.derive_storage_pointers();
483+
484+
// Instantiate Codegen
485+
let cg = Codegen::new();
486+
487+
// The codegen instance should have no artifact
488+
assert!(cg.artifact.is_none());
489+
490+
// Have the Codegen create the constructor bytecode
491+
let mbytes = Codegen::generate_main_bytecode(&EVMVersion::default(), &contract, None, false).unwrap();
492+
493+
// 60 = PUSH1, 04 = 4 bytes table size, 61 = PUSH2, 0005 = 5 bytes table start
494+
// a9059cbb = transfer(address,uint256) function signature
495+
assert_eq!(mbytes, "60 04 61 0005 a9059cbb".replace(" ", ""));
496+
}
497+
498+
#[test]
499+
fn test_code_table_builtin_constant_mixed() {
500+
let source: &str = r#"
501+
#define constant PADDED = __RIGHTPAD(0xABCD)
502+
#define constant SIG = __FUNC_SIG("balanceOf(address)")
503+
#define constant LITERAL = 0xDEADBEEF
504+
505+
#define table T {
506+
[PADDED]
507+
[SIG]
508+
[LITERAL]
509+
}
510+
511+
#define macro MAIN() = takes(0) returns (0) {
512+
__tablesize(T)
513+
__tablestart(T)
514+
}
515+
"#;
516+
517+
// Parse tokens
518+
let flattened_source = FullFileSource { source, file: None, spans: vec![] };
519+
let lexer = Lexer::new(flattened_source);
520+
let tokens = lexer.into_iter().map(|x| x.unwrap()).collect::<Vec<Token>>();
521+
let mut parser = Parser::new(tokens, None);
522+
523+
// Parse the AST
524+
let mut contract = parser.parse().unwrap();
525+
526+
// Derive storage pointers
527+
contract.derive_storage_pointers();
528+
529+
// Instantiate Codegen
530+
let cg = Codegen::new();
531+
532+
// The codegen instance should have no artifact
533+
assert!(cg.artifact.is_none());
534+
535+
// Have the Codegen create the constructor bytecode
536+
let mbytes = Codegen::generate_main_bytecode(&EVMVersion::default(), &contract, None, false).unwrap();
537+
538+
// 60 = PUSH1, 28 = 40 bytes table size (32 + 4 + 4), 61 = PUSH2, 0005 = 5 bytes table start
539+
// RIGHTPAD(0xABCD) = 32 bytes with abcd at the start, padded right with zeros
540+
// balanceOf(address) = 70a08231
541+
// 0xDEADBEEF = deadbeef
542+
assert_eq!(mbytes, "60 28 61 0005 abcd00000000000000000000000000000000000000000000000000000000000070a08231 deadbeef".replace(" ", ""));
543+
}

0 commit comments

Comments
 (0)