Ir al contenido principal
BlockchainMar 28, 2026

Deep EVM #10: Gestión del Stack en Huff — takes(), returns() y el Arte del dup/swap

OS
Open Soft Team

Engineering Team

La disciplina del stack

En Huff, no hay variables — todo vive en el stack. Cada macro declara su contrato de stack con takes(N) returns(M): consume N elementos y produce M. Esta anotación no es ejecutada por la EVM — es documentación que el compilador Huff verifica estáticamente.

El stack de la EVM tiene 1024 posiciones, pero solo las 16 superiores son accesibles vía DUP y SWAP. Esto significa que la planificación del layout del stack es una habilidad crítica.

Patrones fundamentales de dup/swap

DUP: copiar un elemento del stack

// DUP1: duplicar el tope
// Stack: [a] -> [a, a]

// DUP2: copiar el segundo elemento
// Stack: [a, b] -> [b, a, b]

// DUP3: copiar el tercero
// Stack: [a, b, c] -> [c, a, b, c]

Regla: DUPn copia el elemento en posición n (contando desde 1 en el tope) y lo empuja al tope.

SWAP: intercambiar elementos

// SWAP1: intercambiar los dos superiores
// Stack: [a, b] -> [b, a]

// SWAP2: intercambiar tope con el tercero
// Stack: [a, b, c] -> [c, b, a]

Rotación del stack

Uno de los patrones más comunes es la rotación — reorganizar elementos sin perder ninguno:

// Rotar 3 elementos: [a, b, c] -> [c, a, b]
#define macro ROT3() = takes(3) returns(3) {
    swap2   // [c, b, a]
    swap1   // [c, a, b]
}

// Rotar inverso: [a, b, c] -> [b, c, a]
#define macro RROT3() = takes(3) returns(3) {
    swap1   // [b, a, c]
    swap2   // [b, c, a]
}

Ejemplo práctico: implementación de transferencia

Veamos cómo gestionar el stack para una función transfer ERC-20:

#define macro TRANSFER() = takes(0) returns(0) {
    // Leer parámetros del calldata
    0x04 calldataload       // [to]
    0x24 calldataload       // [amount, to]
    
    // Verificar amount > 0
    dup1 iszero             // [amount==0, amount, to]
    err jumpi               // [amount, to]
    
    // Leer balance del sender
    caller                  // [sender, amount, to]
    dup1                    // [sender, sender, amount, to]
    BALANCE_SLOT()          // [slot, sender, amount, to]
    sload                   // [senderBal, sender, amount, to]
    
    // Verificar balance suficiente
    dup1                    // [senderBal, senderBal, sender, amount, to]
    dup4                    // [amount, senderBal, senderBal, sender, amount, to]
    gt                      // [amount>bal, senderBal, sender, amount, to]
    err jumpi               // [senderBal, sender, amount, to]
    
    // Restar del sender
    dup3                    // [amount, senderBal, sender, amount, to]
    swap1 sub               // [newSenderBal, sender, amount, to]
    dup2                    // [sender, newSenderBal, sender, amount, to]
    BALANCE_SLOT()          // [slot, newSenderBal, sender, amount, to]
    sstore                  // [sender, amount, to]
    
    // Sumar al receptor
    swap2                   // [to, amount, sender]
    dup1                    // [to, to, amount, sender]
    BALANCE_SLOT()          // [slot, to, amount, sender]
    dup1 sload              // [toBal, slot, to, amount, sender]
    dup4 add                // [newToBal, slot, to, amount, sender]
    swap1 sstore            // [to, amount, sender]
    
    // Emitir evento Transfer
    swap1                   // [amount, to, sender]
    0x00 mstore             // [to, sender]
    swap1                   // [sender, to]
    [TRANSFER_SIG]          // [sig, sender, to]
    0x20 0x00               // [0, 32, sig, sender, to]
    log3                    // []
    
    // Retornar true
    0x01 0x00 mstore
    0x20 0x00 return
    
    err:
    0x00 0x00 revert
}

Herramientas de visualización

Debug del stack en Huff requiere herramientas:

  1. Comentarios de stack — Documenta el estado del stack después de cada opcode con // [elem1, elem2, ...]
  2. Foundry tracesforge test -vvvv muestra trazas de opcodes con el stack
  3. evm.codes playground — Visualizador interactivo del stack paso a paso
  4. huffc –print-stack — Algunas versiones del compilador pueden analizar el flujo del stack

Patrones anti-stack-overflow

Con funciones complejas, el stack puede crecer peligrosamente:

// Patrón: almacenar temporalmente en memoria
#define macro COMPLEX_CALC() = takes(5) returns(1) {
    // Demasiados valores en el stack? Almacena en memoria
    0x00 mstore     // Guardar primer valor en mem[0x00]
    0x20 mstore     // Guardar segundo en mem[0x20]
    // ... calcular con los 3 restantes ...
    0x00 mload      // Recuperar cuando sea necesario
}

Cada MSTORE cuesta 3 gas + posible expansión de memoria. Pero si la alternativa es DUP16+ (que no existe), no hay otra opción.

Errores comunes

  1. Stack underflow — Intentar POP de un stack vacío causa revert
  2. Stack imbalance — Una macro que deja elementos extra causa bugs en el caller
  3. Olvidar POP — Valores huérfanos en el stack acumulan basura
  4. DUP/SWAP incorrecto — El off-by-one más costoso: DUP2 vs DUP3 puede significar leer el valor equivocado

Conclusión

La gestión del stack es el 80% del trabajo en Huff. Cada macro debe tener un contrato de stack claro, comentarios de estado en cada opcode, y pruebas exhaustivas. Dominar dup/swap/rot es la diferencia entre un contrato Huff funcional y uno con bugs sutiles e impredecibles.