Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Builtin Functions

Several builtin functions are provided by the Huff compiler. The functions can be used inside a macro or function definition to generate the desired bytecode. __RIGHTPAD, __LEFTPAD, __FUNC_SIG and __BYTES can also be used in a code table. The builtin functions are evaluated at compile time and substituted with the corresponding bytecode.

__FUNC_SIG(<string>|<function definition>)

At compile time, the invocation of __FUNC_SIG is substituted with PUSH4 function_selector, where function_selector is the 4 byte function selector of the passed function definition or string. If a string is passed, it must represent a valid function signature i.e. "test(address, uint256)"

__EVENT_HASH(<string>|<event definition>)

At compile time, the invocation of __EVENT_HASH is substituted with PUSH32 event_hash, where event_hash is the selector hash of the passed event definition or string. If a string is passed, it must represent a valid event signature i.e. "TestEvent(uint256, address indexed)"

__ERROR(<error definition>)

At compile time, the invocation of __ERROR is substituted with PUSH32 error_selector, where error_selector is the left-padded 4 byte error selector of the passed error definition.

__LEFTPAD(<hex>|<constant>|<builtin function>)

At compile time, the invocation of __LEFTPAD is substituted with padded_literal, where padded_literal is the left padded version of the passed input. This function is only available as constant assignment or in a code table.

Example

__LEFTPAD(0x123)
// will result in 32 bytes:
0x0000000000000000000000000000000000000000000000000000000000000123

__RIGHTPAD(<hex>|<constant>|<builtin function>)

At compile time, the invocation of __RIGHTPAD is substituted with PUSH32 padded_literal, where padded_literal is the right padded version of the passed input. In a code table, only the 32 padded bytes are emitted.

Example

__RIGHTPAD(0x123)
// will result in 32 bytes:
0x1230000000000000000000000000000000000000000000000000000000000000

__codesize(<macro>|<function>)

Pushes the code size of the macro or function passed to the stack.

Used inside the macro it names (or inside a macro that macro invokes), it pushes the size of that enclosing invocation as emitted, including expanded macros and resized jumps.

Two macros can push each other’s size, for example a constructor that copies __codesize(MAIN) bytes while MAIN uses __codesize(CONSTRUCTOR). Each value is the exact size, also when the width of one push changes the size of the other macro.

__codesize(RUNTIME)

RUNTIME is a reserved argument that resolves at compile time to the byte length of the runtime section as the compiler emits it — MAIN’s body plus its appended runtime tables. Usable in both MAIN and CONSTRUCTOR (directly or via a derived #define constant).

The runtime size is self-referential when used from MAIN (the embedded value contributes to the size it measures). The compiler resolves this by iterating MAIN codegen to a fixed point — typically converges in 2–3 passes. The contract size limit bounds the value at PUSH3 (64 KiB from Amsterdam needs three bytes), so widths stay small.

Not allowed inside a code table — code tables contribute to the runtime size, so embedding the runtime size inside one would create a circular dependency in table sizing. RUNTIME is also reserved as a macro name; #define macro RUNTIME() = { ... } is rejected.

This enables Solidity-style immutables without on-chain arithmetic. CONSTRUCTOR copies MAIN to memory, writes immutable values past it, and returns the combined region. MAIN reads each immutable at a compile-time literal offset:

#define constant IMMUTABLE_1 = __codesize(RUNTIME)
#define constant IMMUTABLE_2 = [IMMUTABLE_1] + 0x20
#define constant RETURN_LEN  = [IMMUTABLE_2] + 0x20

#define macro CONSTRUCTOR() = {
    // Copy MAIN to memory at offset 0.
    [IMMUTABLE_1] __codesize(CONSTRUCTOR) 0x00 codecopy

    // Append two immutables past it.
    0x11 [IMMUTABLE_1] mstore
    0x22 [IMMUTABLE_2] mstore

    // Return runtime + immutables.
    [RETURN_LEN] 0x00 return
}

#define macro MAIN() = {
    // Read the second immutable at a compile-time literal offset.
    0x20 [IMMUTABLE_2] 0x00 codecopy
    0x20 0x00 return
}

__tablestart(<table>) and __tablesize(<table>)

These functions related to Jump Tables are described in the next section.

__EMBED_TABLE(<code table>)

Code tables are normally appended after the bytecode. __EMBED_TABLE inserts the table’s bytes at the current position instead, and the table is not appended again. __tablestart returns the embedded position, also when it is used before the embedding. A table can only be embedded once, and only code tables can be embedded.

Example

#define table DATA { 0xdeadbeef }

#define macro MAIN() = takes (0) returns (0) {
    __tablestart(DATA) pop  // PUSH2 0x0005 POP
    stop
    __EMBED_TABLE(DATA)     // deadbeef at offset 0x05
    0x01                    // code continues after the table
}

__argsstart()

Pushes the offset in the deployment bytecode where the constructor arguments start. A deployer, such as Foundry, cast or hnc -i, appends the arguments to the deployment bytecode as Solidity’s abi.encode does, so the constructor copies them from there and reads them like calldata. A Solidity constructor does the same with an offset that solc fills in.

The arguments are the code from this offset to the end, so codesize minus the offset is their length, 0 when there are none. The offset is not __codesize(CONSTRUCTOR), which counts only the constructor macro, while the arguments also follow the bootstrap, the runtime and the tables only the constructor uses.

The offset is pushed with two bytes, so the arguments must start within the first 64 KiB of the deployment bytecode. Constructor arguments are not part of the deployed code, so __argsstart() can only be used in the CONSTRUCTOR and the macros it invokes. In MAIN, in tests and in code tables it is a compile error.

Example

#define function constructor(string,uint256) nonpayable returns ()

#define macro CONSTRUCTOR() = takes (0) returns (0) {
    __argsstart() dup1 codesize sub     // [len, start]
    swap1 0x00 codecopy                 // abi.encode(name, decimals) at memory 0x00

    0x20 mload 0x00 sstore              // slot 0: decimals, the second head word
    0x00 mload dup1 mload 0x01 sstore   // slot 1: the name's length, at the offset in the first head word
    0x20 add mload 0x02 sstore          // slot 2: the first 32 bytes of the name
}

#define macro MAIN() = takes (0) returns (0) {
    0x00 0x00 return
}
hnc ./src/Token.huff -b -i "TestToken" 18

__VERBATIM(<hex>|<constant>|<builtin function>)

This function is used to insert raw hex data into the compiled bytecode without a PUSH opcode. It is useful for inserting raw opcodes or data into the compiled bytecode.

You can pass:

  • A hex literal directly: __VERBATIM(0x1234)
  • A constant reference: __VERBATIM([MY_CONSTANT])
  • A builtin function that normally generates PUSH + value

When wrapping a builtin function, __VERBATIM strips the PUSH opcode and emits only the raw value bytes.

Supported nested builtins:

  • __FUNC_SIG - emits 4-byte selector (without PUSH4)
  • __EVENT_HASH - emits 32-byte hash (without PUSH32)
  • __BYTES - emits N-byte UTF-8 (without PUSHX)
  • __ERROR - emits error selector (without PUSH)
  • __RIGHTPAD - emits 32-byte right-padded value (without PUSH32)
  • __LEFTPAD - emits 32-byte left-padded value (without PUSH32)

Examples

Direct hex literal:

#define macro MAIN() = takes (0) returns (0) {
    __VERBATIM(0x1234)  // Inserts raw bytes 0x1234
}

Constant reference:

#define constant SIG = __FUNC_SIG("transfer(address,uint256)")

#define macro MAIN() = takes (0) returns (0) {
    __VERBATIM([SIG])  // Inserts the function selector bytes
}

Nested builtin function:

#define macro MAIN() = takes (0) returns (0) {
    // __FUNC_SIG normally produces: 632e1a7d4d (PUSH4 + selector)
    // __VERBATIM strips the PUSH4, emitting just: 2e1a7d4d
    __VERBATIM(__FUNC_SIG("withdraw(uint256)"))

    // __RIGHTPAD normally produces: 7f1234...000 (PUSH32 + 32 bytes)
    // __VERBATIM strips the PUSH32, emitting just the 32 bytes
    __VERBATIM(__RIGHTPAD(0x1234))
}

__BYTES(<string>|<string constant>)

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.

You can pass either:

  • A string literal directly: __BYTES("hello")
  • A string constant reference: __BYTES([GREETING]) where GREETING is a string constant

Note: __BYTES only accepts string values. Hex constants are not allowed and will result in a compilation error.

Examples

Direct string literal:

#define macro MAIN() = takes (0) returns (0) {
    __BYTES("hello")  // Compiles to: PUSH5 0x68656c6c6f
}

String constant reference:

#define constant GREETING = "hello"
#define constant ERROR_MSG = "Unauthorized"

#define macro MAIN() = takes (0) returns (0) {
    __BYTES([GREETING])    // Compiles to: PUSH5 0x68656c6c6f
    __BYTES([ERROR_MSG])   // Compiles to: PUSH12 0x556e617574686f72697a6564
}

Chained __BYTES constants:

#define constant MSG = "test"
#define constant MSG_BYTES = __BYTES([MSG])

#define macro MAIN() = takes (0) returns (0) {
    [MSG_BYTES]  // Uses the pre-computed bytes
}

__ASSERT_PC(<expression>)

This function is a compile-time assertion that validates the current bytecode position (program counter) matches the expected value. It generates no bytecode - it’s purely a compile-time check. If the assertion fails, compilation will stop with an error showing the expected vs actual position.

This is useful for ensuring critical instructions (like jump destinations) are positioned at specific bytecode offsets, which can be important for gas optimization or when interfacing with external systems that expect specific bytecode layouts.

Example

#define macro MAIN() = takes (0) returns (0) {
    __ASSERT_PC(0x00)    // Assert we're at the start
    0x01 0x02            // PUSH1 0x01, PUSH1 0x02 = 4 bytes
    __ASSERT_PC(0x04)    // Assert we're at byte 4
    target:              // Label at position 0x04 (generates JUMPDEST = 1 byte)
    __ASSERT_PC(0x05)    // Assert we're at byte 5
    0x11                 // PUSH1 0x11 = 2 bytes
    __ASSERT_PC(0x07)    // Assert we're at byte 7
}

You can also use constants and combine with for-loops:

#define constant TARGET_OFFSET = 0x20

#define macro MAIN() = takes (0) returns (0) {
    // Fill space to reach target offset using for-loop with constant
    for(i in 0..[TARGET_OFFSET]) {
        stop  // Expands to 32 stop instructions = 32 bytes
    }
    __ASSERT_PC([TARGET_OFFSET])  // Ensure we're at the expected offset (0x20 = 32 bytes)
    important_label:             // Label generates JUMPDEST automatically
}

The position can be any compile-time expression, including macro arguments and loop variables:

#define constant BASE = 0x02

#define macro CHECK(pc) = takes (0) returns (0) {
    __ASSERT_PC(<pc>)
}

#define macro MAIN() = takes (0) returns (0) {
    0x00 pop                 // PUSH0 POP = 2 bytes
    __ASSERT_PC([BASE])
    CHECK(0x02)
    for(i in 0..3) {
        __ASSERT_PC([BASE] + <i> * 3)
        0x01 pop             // PUSH1 0x01 POP = 3 bytes per iteration
    }
}

Combining Builtin Functions

Some builtin functions can be combined with other functions. Possible combinations are:

  • __RIGHTPAD(__FUNC_SIG("test(address, uint256)"))
  • __RIGHTPAD(__BYTES("hello"))

Builtin Functions as Macro Arguments

Builtin functions that can be evaluated at compile-time can be passed as macro arguments. This allows for more flexible and reusable macro definitions.

The following builtin functions support being passed as macro arguments:

  • __FUNC_SIG - Function selectors
  • __EVENT_HASH - Event hashes
  • __BYTES - String bytes
  • __RIGHTPAD - Right-padded values

Example

#define function transfer(address,uint256) nonpayable returns ()

#define macro PUSH_VALUE(val) = takes(0) returns(1) {
    <val>
}

#define macro MAIN() = takes(0) returns(0) {
    // Pass builtin function call as macro argument
    PUSH_VALUE(__FUNC_SIG(transfer))

    // Pass padded builtin as argument
    PUSH_VALUE(__RIGHTPAD(0x1234))

    // Pass bytes as argument
    PUSH_VALUE(__BYTES("test"))

    pop pop pop
}

This feature enables creating generic utility macros that work with computed values, improving code reusability and maintainability.

Example

// Define a function
#define function test1(address, uint256) nonpayable returns (bool)
#define function test2(address, uint256) nonpayable returns (bool)

// Define an event
#define event TestEvent1(address, uint256)
#define event TestEvent2(address, uint256)

#define macro TEST1() = takes (0) returns (0) {
    0x00 0x00                // [address, uint]
    __EVENT_HASH(TestEvent1) // [sig, address, uint]
    0x00 0x00                // [mem_start, mem_end, sig, address, uint]
    log3                     // []
}

#define macro TEST2() = takes (0) returns (0) {
    0x00 0x00                // [address, uint]
    __EVENT_HASH(TestEvent2) // [sig, address, uint]
    0x00 0x00                // [mem_start, mem_end, sig, address, uint]
    log3                     // []
}

#define macro MAIN() = takes (0) returns (0) {
    // Identify which function is being called.
    0x00 calldataload 0xE0 shr
    dup1 __FUNC_SIG(test1) eq test1 jumpi
    dup1 __FUNC_SIG(test2) eq test2 jumpi

    // Revert if no function matches
    0x00 0x00 revert

    test1:
        TEST1()

    test2:
        TEST2()
}

See Also

  • Constants - Documentation for FREE_STORAGE_POINTER(), a special compile-time function that allocates unique storage slots
  • Builtin Constants - Documentation for built-in constants like __NOOP