Zum Hauptinhalt springen
BlockchainMar 28, 2026

Deep EVM #9: Huff-Sprachgrundlagen — Makros, Labels und rohe Opcodes

OS
Open Soft Team

Engineering Team

Was ist Huff?

Solidity ist eine wunderbare Abstraktion — bis sie es nicht mehr ist. Wenn Sie einen Contract brauchen, der in 100 Bytes Runtime-Bytecode passt, Funktionen in O(1) mit einer gepackten Sprungtabelle dispatcht oder 200 Gas auf einem heissen Pfad einspart, brauchen Sie etwas naeher am Metall. Dieses Etwas ist Huff — eine Low-Level-EVM-Assemblersprache mit einem duennen Makrosystem, das Ihnen direkte Kontrolle ueber jeden Opcode gibt.

Huff vs. Yul vs. Solidity

MerkmalSolidityYulHuff
AbstraktionsniveauHochMittelNiedrig
Typ-SystemStarkKeinesKeines
VariablenJaJaNein
Stack-KontrolleAutomatischSemi-automatischManuell
Bytecode-GroesseAm groesstenMittelAm kleinsten

In Huff gibt es keine Variablen. Sie arbeiten direkt mit dem Stack und muessen den Zustand jedes Elements im Kopf behalten.

Grundlegende Syntax

Makros — die grundlegende Einheit

#define macro MAIN() = takes(0) returns(0) {
    // Funktionsselektor aus Calldata laden
    0x00 calldataload 0xe0 shr
    
    // Gegen bekannte Selektoren pruefen
    dup1 __FUNC_SIG(transfer) eq transfer jumpi
    dup1 __FUNC_SIG(balanceOf) eq balanceOf jumpi
    
    // Kein Match -> revert
    0x00 0x00 revert
    
    transfer:
        TRANSFER()
    balanceOf:
        BALANCE_OF()
}

takes() und returns()

takes(n) und returns(m) deklarieren, wie viele Stack-Elemente ein Makro erwartet und wie viele es zuruecklaesst. Dies ist rein deklarativ — der Compiler erzwingt es nicht, aber es dient als Dokumentation:

// Erwartet 2 Stack-Elemente, laesst 1 zurueck
#define macro ADD_TWO() = takes(2) returns(1) {
    add  // Nimmt 2, gibt 1
}

Konstanten und Jump-Labels

#define constant OWNER_SLOT = FREE_STORAGE_POINTER()
#define constant MAX_SUPPLY = 0x989680  // 10,000,000

#define macro CHECK_OWNER() = takes(0) returns(0) {
    caller [OWNER_SLOT] sload eq is_owner jumpi
    0x00 0x00 revert
    is_owner:
}

Funktionsdispatch in Huff

Im Gegensatz zu Solidity, wo der Compiler den Funktionsdispatch automatisch generiert, muessen Sie ihn in Huff manuell implementieren:

#define function transfer(address,uint256) nonpayable returns (bool)
#define function balanceOf(address) view returns (uint256)

#define macro MAIN() = takes(0) returns(0) {
    0x00 calldataload 0xe0 shr  // Selektor extrahieren
    
    dup1 __FUNC_SIG(transfer) eq transfer_jump jumpi
    dup1 __FUNC_SIG(balanceOf) eq balance_jump jumpi
    0x00 0x00 revert
    
    transfer_jump:
        TRANSFER()
    balance_jump:
        BALANCE_OF()
}

Einen einfachen ERC-20 in Huff

Storage-Layout

// Slot 0: totalSupply
// Slot 1: name (String-Pointer)
// mapping(address => uint256) balances: keccak256(address, 2)
// mapping(address => mapping(address => uint256)) allowances: keccak256(spender, keccak256(owner, 3))

#define constant BALANCE_SLOT = 0x02
#define constant ALLOWANCE_SLOT = 0x03

Balance-Abfrage

#define macro BALANCE_OF() = takes(0) returns(0) {
    // Adresse aus Calldata laden
    0x04 calldataload        // [address]
    
    // Storage-Slot berechnen: keccak256(address, BALANCE_SLOT)
    0x00 mstore              // Memory[0x00] = address
    [BALANCE_SLOT] 0x20 mstore  // Memory[0x20] = 2
    0x40 0x00 sha3           // [hash]
    
    sload                    // [balance]
    
    // Ergebnis zurueckgeben
    0x00 mstore
    0x20 0x00 return
}

Bytecode-Groessenvergleich

Ein minimaler ERC-20:

  • Solidity (OpenZeppelin): ~2.800 Bytes Runtime
  • Yul: ~1.200 Bytes Runtime
  • Huff: ~600 Bytes Runtime

Weniger Bytecode bedeutet niedrigere Bereitstellungskosten (200 Gas pro Byte) und potenziell bessere Performance, da weniger Code geladen werden muss.

Debugging von Huff

Huff bietet keine Fehlermeldungen zur Laufzeit. Wenn Ihr Contract revertiert, erhalten Sie nur ein leeres Revert-Payload. Debugging-Strategien:

  1. Stack-Kommentare — Dokumentieren Sie den Stack-Zustand nach jedem Opcode
  2. Inkrementelles Testen — Jedes Makro einzeln mit Foundry testen
  3. cast run — Transaktionen auf einer Fork nachspielen und Opcode-Traces analysieren
  4. forge debug — Schritt-fuer-Schritt durch den Bytecode navigieren

Wann Huff verwenden?

Ja:

  • MEV-Bots, bei denen jede Gas-Einheit zaehlt
  • Minimale Proxy-Contracts (EIP-1167)
  • Vanity-Contracts mit spezifischen Bytecode-Anforderungen
  • Wenn Sie die EVM wirklich verstehen wollen

Nein:

  • Standard-Geschaeftslogik
  • Wenn das Team keine tiefen EVM-Kenntnisse hat
  • Wenn Auditierbarkeit prioritaet hat
  • Fuer Contracts mit komplexer Logik (die Fehlerquote ist zu hoch)

Fazit

Huff ist die ultimative Low-Level-Sprache fuer die EVM. Kein Compiler-Overhead, kein Typ-System, keine Sicherheitsnetze — nur Sie und die Opcodes. Diese Macht hat ihren Preis: hoeheres Fehlerrisiko und schwierigeres Debugging. Aber fuer leistungskritische Anwendungen wie MEV-Bots ist Huff der Goldstandard.