Skip to content

Commit b8efd51

Browse files
authored
feat: support strings and accept a constant for __VERBATIM (#151)
1 parent 861575e commit b8efd51

33 files changed

+683
-401
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
## Unreleased
66
- Fix constants defined with builtin functions not working in code tables (fixes #149).
77
- Example: `#define constant C = __RIGHTPAD(0x)` can now be used in tables with `[C]`.
8+
- Add support for string literal constants with `__BYTES` builtin function.
9+
- String constants can be defined: `#define constant GREETING = "hello"`.
10+
- String constants must be used with `__BYTES`: `__BYTES([GREETING])` converts to UTF-8 bytes.
11+
- Example: `#define constant MSG = "hello"` then `__BYTES([MSG])` produces `0x68656c6c6f`.
12+
- Add support for constant references in `__VERBATIM` builtin function.
13+
- `__VERBATIM` can now accept constant references: `__VERBATIM([MY_CONSTANT])`.
14+
- Example: `#define constant SIG = __FUNC_SIG("transfer(address,uint256)")` then `__VERBATIM([SIG])`.
815

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

book/huff-language/builtin-functions.md

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,67 @@ Pushes the code size of the macro or function passed to the stack.
3838
### `__tablestart(<table>)` and `__tablesize(<table>)`
3939
These functions related to Jump Tables are described in the next section.
4040

41-
### `__VERBATIM(<hex>)`
41+
### `__VERBATIM(<hex>|<constant>)`
4242
This function is used to insert raw hex data into the compiled bytecode. It is useful for inserting raw opcodes or data into the compiled bytecode.
4343

44-
### `__BYTES(<string>)`
45-
This function allows to insert a string as UTF-8 encoded bytes into the compiled bytecode. The resulting bytecode is limited to 32 bytes.
44+
You can pass either:
45+
- A hex literal directly: `__VERBATIM(0x1234)`
46+
- A constant reference: `__VERBATIM([MY_CONSTANT])`
4647

47-
#### Example
48+
#### Examples
49+
50+
**Direct hex literal:**
51+
```javascript
52+
#define macro MAIN() = takes (0) returns (0) {
53+
__VERBATIM(0x1234) // Inserts raw bytes 0x1234
54+
}
55+
```
56+
57+
**Constant reference:**
4858
```javascript
59+
#define constant SIG = __FUNC_SIG("transfer(address,uint256)")
60+
61+
#define macro MAIN() = takes (0) returns (0) {
62+
__VERBATIM([SIG]) // Inserts the function selector bytes
63+
}
64+
```
65+
66+
### `__BYTES(<string>|<string constant>)`
67+
This function converts a string to UTF-8 encoded bytes and inserts them into the compiled bytecode. The resulting bytecode is limited to 32 bytes.
68+
69+
You can pass either:
70+
- A string literal directly: `__BYTES("hello")`
71+
- A string constant reference: `__BYTES([GREETING])` where `GREETING` is a string constant
72+
73+
**Note:** `__BYTES` only accepts string values. Hex constants are not allowed and will result in a compilation error.
74+
75+
#### Examples
76+
77+
**Direct string literal:**
78+
```javascript
79+
#define macro MAIN() = takes (0) returns (0) {
80+
__BYTES("hello") // Compiles to: PUSH5 0x68656c6c6f
81+
}
82+
```
83+
84+
**String constant reference:**
85+
```javascript
86+
#define constant GREETING = "hello"
87+
#define constant ERROR_MSG = "Unauthorized"
88+
89+
#define macro MAIN() = takes (0) returns (0) {
90+
__BYTES([GREETING]) // Compiles to: PUSH5 0x68656c6c6f
91+
__BYTES([ERROR_MSG]) // Compiles to: PUSH12 0x556e617574686f72697a6564
92+
}
93+
```
94+
95+
**Chained `__BYTES` constants:**
96+
```javascript
97+
#define constant MSG = "test"
98+
#define constant MSG_BYTES = __BYTES([MSG])
99+
49100
#define macro MAIN() = takes (0) returns (0) {
50-
__BYTES("hello") // Will push UTF-8 encoded string (PUSH5 0x68656c6c6f)
101+
[MSG_BYTES] // Uses the pre-computed bytes
51102
}
52103
```
53104

book/huff-language/code-table.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,16 @@ somewhere within the contract.
4040
}
4141
```
4242

43+
### Usage of builtin function constants
44+
```javascript
45+
#define constant SIG = __FUNC_SIG("transfer(address,uint256)")
46+
#define constant PADDED = __RIGHTPAD(0x42)
47+
48+
#define table CODE_TABLE {
49+
[SIG] // Function selector from constant
50+
[PADDED] // Padded value from constant
51+
}
52+
```
53+
54+
This is particularly useful when you want to reuse computed builtin values in multiple places, including both code and data tables.
55+

book/huff-language/constants.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Constants in Huff contracts are not included in the contract's storage; Instead,
44
they are able to be called within the contract at compile time. Constants
5-
can be bytes (32 max), `FREE_STORAGE_POINTER`, a built-in function, or an arithmetic expression.
5+
can be bytes (32 max), string literals, `FREE_STORAGE_POINTER`, a built-in function, or an arithmetic expression.
66
A `FREE_STORAGE_POINTER` constant will always represent an unused storage slot in the contract.
77

88
In order to push a constant to the stack, use bracket notation: `[CONSTANT]`
@@ -13,6 +13,7 @@ In order to push a constant to the stack, use bracket notation: `[CONSTANT]`
1313
```javascript
1414
#define constant NUM = 0x420
1515
#define constant HELLO_WORLD = 0x48656c6c6f2c20576f726c6421
16+
#define constant GREETING = "hello"
1617
#define constant FREE_STORAGE = FREE_STORAGE_POINTER()
1718
#define constant TEST = __FUNC_SIG("test(uint256)")
1819
```
@@ -131,4 +132,69 @@ Expressions are evaluated during compilation. The compiler replaces the expressi
131132
#define macro EXAMPLE() = takes(0) returns(0) {
132133
[C] // Compiles to: PUSH1 0x15
133134
}
135+
```
136+
137+
## String Literals
138+
139+
String literal constants can be defined using double quotes. String constants cannot be pushed to the stack directly - they must be converted to bytes using the `__BYTES` builtin function.
140+
141+
### String Constant Declaration
142+
143+
```javascript
144+
#define constant GREETING = "hello"
145+
#define constant MESSAGE = "transfer(address,uint256)"
146+
```
147+
148+
### String Constant Usage
149+
150+
String constants must be used with the `__BYTES` builtin function, which converts the string to UTF-8 encoded bytes:
151+
152+
```javascript
153+
#define constant GREETING = "hello"
154+
155+
#define macro MAIN() = takes(0) returns(0) {
156+
__BYTES([GREETING]) // Compiles to: PUSH5 0x68656c6c6f
157+
}
158+
```
159+
160+
**Direct usage is not allowed:**
161+
```javascript
162+
// ❌ ERROR: String constants cannot be pushed directly
163+
#define constant MSG = "hello"
164+
165+
#define macro EXAMPLE() = takes(0) returns(0) {
166+
[MSG] // Compilation error: use __BYTES([MSG]) instead
167+
}
168+
```
169+
170+
### Use Cases
171+
172+
**Function Signatures:**
173+
```javascript
174+
#define constant TRANSFER_SIG = "transfer(address,uint256)"
175+
176+
#define macro GET_SELECTOR() = takes(0) returns(1) {
177+
__BYTES([TRANSFER_SIG]) // Push signature as bytes
178+
}
179+
```
180+
181+
**Error Messages (limited to 32 bytes):**
182+
```javascript
183+
#define constant ERROR_MSG = "Insufficient balance"
184+
185+
#define macro REVERT_WITH_MSG() = takes(0) returns(0) {
186+
__BYTES([ERROR_MSG])
187+
0x00 mstore
188+
0x20 0x00 revert
189+
}
190+
```
191+
192+
**Combining with Other Builtins:**
193+
```javascript
194+
#define constant GREETING = "hi"
195+
196+
#define macro EXAMPLE() = takes(0) returns(0) {
197+
// String bytes can be padded
198+
__RIGHTPAD(__BYTES([GREETING])) // Right-pad the UTF-8 bytes
199+
}
134200
```

crates/codegen/src/irgen/arg_calls.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,8 @@ pub fn bubble_arg_call<'a>(
284284
// Original handling for other argument types
285285
tracing::info!(target: "codegen", "GOT \"{:?}\" ARG FROM MACRO INVOCATION", arg);
286286
match arg {
287-
MacroArg::Literal(l) => {
288-
tracing::info!(target: "codegen", "GOT LITERAL {} ARG FROM MACRO INVOCATION", bytes32_to_hex_string(l, false));
287+
MacroArg::HexLiteral(l) => {
288+
tracing::info!(target: "codegen", "GOT HEX LITERAL {} ARG FROM MACRO INVOCATION", bytes32_to_hex_string(l, false));
289289
let push_bytes = literal_gen(evm_version, l);
290290
*offset += push_bytes.len() / 2;
291291
bytes.push_with_offset(starting_offset, Bytes::Raw(push_bytes));
@@ -387,6 +387,16 @@ pub fn bubble_arg_call<'a>(
387387
let hex_literal: String = bytes.as_str().to_string();
388388
format!("{:02x}{hex_literal}", 95 + hex_literal.len() / 2)
389389
}
390+
ConstVal::String(_s) => {
391+
return Err(CodegenError {
392+
kind: CodegenErrorKind::StringConstantNotBytes(format!(
393+
"String constant [{}] cannot be pushed directly. Use __BYTES([{}]) to convert it to bytes.",
394+
iden, iden
395+
)),
396+
span: Box::new(target_macro_invoc.1.span.clone()),
397+
token: None,
398+
});
399+
}
390400
ConstVal::StoragePointer(sp) => {
391401
let hex_literal: String = bytes32_to_hex_string(sp, false);
392402
format!("{:02x}{hex_literal}", 95 + hex_literal.len() / 2)

0 commit comments

Comments
 (0)