Ir al contenido principal
BlockchainMar 28, 2026

Deep EVM #9: Introducción a Huff — Macros, Etiquetas y Opcodes Crudos

OS
Open Soft Team

Engineering Team

¿Qué es Huff?

Huff es un lenguaje de ensamblador de bajo nivel para la EVM que te da control total sobre el bytecode generado. A diferencia de Yul, que proporciona variables con nombre y funciones, Huff opera directamente sobre el stack — escribes opcodes crudos organizados en macros reutilizables.

Huff fue creado por Aztec Protocol para escribir contratos extremadamente optimizados en gas. Hoy es la herramienta preferida para desarrolladores de MEV, puentes cross-chain y cualquier aplicación donde cada unidad de gas cuenta.

¿Por qué Huff en lugar de Yul?

Yul es excelente, pero tiene limitaciones:

  • El compilador de Yul inserta su propia gestión de memoria
  • Las variables locales de Yul a veces generan SWAP/DUP innecesarios
  • No puedes controlar el layout exacto del bytecode
  • No puedes crear jump tables O(1)

Huff elimina todas estas capas de abstracción. Lo que escribes es (casi) exactamente lo que se despliega.

Estructura de un contrato Huff

// Definir la interfaz
#define function balanceOf(address) view returns (uint256)
#define function transfer(address, uint256) nonpayable returns (bool)

// Constantes
#define constant OWNER_SLOT = FREE_STORAGE_POINTER()

// Macro principal: punto de entrada
#define macro MAIN() = takes(0) returns(0) {
    // Leer el selector de función (primeros 4 bytes)
    0x00 calldataload 0xe0 shr
    
    // Despacho de funciones
    dup1 __FUNC_SIG(balanceOf)  eq balanceOf  jumpi
    dup1 __FUNC_SIG(transfer)   eq transfer   jumpi
    
    // Fallback: revert
    0x00 0x00 revert
    
    balanceOf:
        BALANCE_OF()
    transfer:
        TRANSFER()
}

// Macro para balanceOf
#define macro BALANCE_OF() = takes(0) returns(0) {
    0x04 calldataload           // [account]
    0x00 mstore                 // [] (account en mem[0x00])
    [OWNER_SLOT] 0x20 mstore   // [] (slot en mem[0x20])
    0x40 0x00 sha3              // [hash]
    sload                       // [balance]
    0x00 mstore                 // []
    0x20 0x00 return            // retorna 32 bytes
}

Macros: la unidad fundamental

Las macros en Huff son bloques de código reutilizables. A diferencia de las funciones, las macros se expanden inline — no hay overhead de JUMP.

// takes(N) returns(M) documenta el efecto en el stack
#define macro REQUIRE_NOT_ZERO() = takes(1) returns(1) {
    // Stack de entrada: [value]
    dup1                // [value, value]
    continue jumpi      // [value] (salta si value != 0)
    0x00 0x00 revert    // nunca alcanzado si value != 0
    continue:
}

// Uso:
#define macro SAFE_TRANSFER() = takes(3) returns(0) {
    // Stack: [to, amount, from]
    dup2                // [amount, to, amount, from]
    REQUIRE_NOT_ZERO()  // [amount, to, amount, from]
    // ... lógica de transferencia ...
}

Etiquetas y saltos

En Huff, las etiquetas definen destinos de salto dentro de una macro:

#define macro MAX() = takes(2) returns(1) {
    // Stack: [a, b]
    dup2 dup2       // [a, b, a, b]
    gt              // [a>b, a, b]
    max_a jumpi     // [a, b]
    
    // b >= a: retornar b
    swap1 pop       // [b]
    fin jump
    
    max_a:
    // a > b: retornar a
    pop             // [a]
    
    fin:
}

Cada etiqueta compila a un JUMPDEST en el bytecode. Los saltos son directos — no hay resolución en tiempo de ejecución como en lenguajes de alto nivel.

Constantes y almacenamiento

// FREE_STORAGE_POINTER() asigna slots secuencialmente
#define constant TOTAL_SUPPLY = FREE_STORAGE_POINTER() // slot 0
#define constant BALANCES     = FREE_STORAGE_POINTER() // slot 1
#define constant ALLOWANCES   = FREE_STORAGE_POINTER() // slot 2

// Constantes literales
#define constant MAX_UINT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
#define constant TRANSFER_SIG = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

Compilación y testing

Huff se compila con el compilador huffc:

huffc src/MiContrato.huff -b  # Obtener bytecode
huffc src/MiContrato.huff -r  # Obtener bytecode de runtime

Para testing, Huff se integra con Foundry:

import {HuffDeployer} from "foundry-huff/HuffDeployer.sol";

contract MiContratoTest is Test {
    address contrato;
    
    function setUp() public {
        contrato = HuffDeployer.deploy("MiContrato");
    }
    
    function testBalanceOf() public {
        // Interactuar con el contrato desplegado
        (bool ok, bytes memory data) = contrato.staticcall(
            abi.encodeWithSignature("balanceOf(address)", address(this))
        );
        assertTrue(ok);
    }
}

Comparación de tamaño de bytecode

Para un token ERC-20 básico:

LenguajeBytecode runtimeGas de despliegue
Solidity (optimizado)~2,400 bytes~480,000 gas
Yul~800 bytes~160,000 gas
Huff~350 bytes~70,000 gas

Huff produce bytecode 7x más compacto que Solidity. En despliegue, esto se traduce en ahorros masivos.

Conclusión

Huff es el lenguaje más cercano al metal que puedes usar para la EVM. Las macros proporcionan reutilización sin overhead, las etiquetas dan control preciso sobre el flujo, y la ausencia de abstracciones garantiza que lo que escribes es lo que se despliega. En los próximos artículos exploraremos gestión avanzada del stack, jump tables O(1) y patrones de ejecución adaptativa.