Ethereum: de historia a labs

Artículo 43 de 48

90%

completado al llegar aquí

Sistemas distribuidosNota de investigación13 min de lectura

Lending y liquidaciones: cuando el colateral respira mal

Cómo se presta contra colateral: health factor, close factor y bonus de liquidación en Aave V3, y por qué oráculo y liquidador sostienen la solvencia.

Ruta Ethereum: 0/48

Imagina que depositas 10 ETH en un protocolo de préstamos y pides 12 000 USDC contra ellos. Nadie revisó tu historial crediticio y nadie te llamará si no pagas. Entonces, ¿qué protege a quienes pusieron esos USDC en el pool? La respuesta es una regla que cualquiera puede ejecutar: si el valor de tu colateral cae demasiado, un tercero puede pagar parte de tu deuda y quedarse con parte de tu colateral, con descuento.

Esa regla es la liquidación. Este capítulo la recorre con el código de Aave V3 como referencia: cómo se mide la salud de una posición, qué puede hacer un liquidador y qué partes del sistema (el oráculo de precios y el propio liquidador) pueden fallar. Los números del ejemplo son hipotéticos y sirven solo para seguir la aritmética.

Flujo visual

De posición sana a liquidación

La solvencia depende de una cadena: precio, umbral, incentivo y alguien dispuesto a ejecutar.
  1. Depositar colateralEl usuario aporta un activo que cuenta con su umbral de liquidación.
  2. Tomar deudaPide prestado otro activo; el health factor queda por encima de 1.
  3. El oráculo cambia el precioEl colateral vale menos en la moneda base del protocolo.
  4. Health factor bajo 1La posición se vuelve liquidable por cualquier cuenta.
  5. LiquidaciónEl liquidador repaga deuda y recibe colateral con bonus.

Brújula de lectura

Actor, evidencia y estado

Antes de avanzar, ubica quién actúa, qué prueba queda y qué parte del sistema cambia.

Actor

Liquidador

Cualquier cuenta que llame a liquidationCall cuando el health factor está bajo 1.

Evidencia

Precio del oráculo

El protocolo no ve el mercado; ve el número que le entrega su oráculo en ese bloque.

Estado

Colateral y deuda

Saldos del usuario en el pool, expresados en la moneda base para calcular el health factor.

Preguntas guía

Usa estas preguntas para leer el capítulo como un diseñador del sistema, no como usuario:

  • ¿Qué número exacto decide que una posición es liquidable, y quién lo calcula?
  • ¿Por qué un tercero querría pagar la deuda de otro?
  • ¿Cuánta deuda puede repagar un liquidador en una sola llamada, y por qué no toda?
  • ¿Qué pasa si el precio que ve el contrato está desactualizado o fue manipulado?
  • ¿Cuándo una liquidación llega tarde y el pool termina con deuda incobrable?

Conexiones dentro de la serie

Prerequisitos internos recomendados:

Invariante de solvencia: qué mide el health factor

Empecemos con vocabulario. El colateral es lo que depositas como garantía. La deuda es lo que pides prestado. Cada activo de colateral tiene un umbral de liquidación (liquidation threshold, LT): la fracción de su valor que cuenta para sostener deuda. Un LT de 80 % significa que 100 USD de colateral sostienen como máximo 80 USD de deuda antes de que la posición sea liquidable.

El health factor (HF) junta todo en un número. En Aave V3 se calcula como la suma del colateral en moneda base, ponderada por su umbral de liquidación medio, dividida por la deuda total en moneda base. Si no hay deuda, el HF es el máximo representable.

HF=∑iCi⋅LTiDHF = \frac{\sum_i C_i \cdot LT_i}{D}

Aquí CiC_i es el valor del colateral ii en moneda base, LTiLT_i su umbral y DD la deuda total en moneda base. La posición es liquidable cuando HF<1HF < 1: la validación de liquidationCall exige que el health factor esté por debajo de HEALTH_FACTOR_LIQUIDATION_THRESHOLD, que vale 1e18 (es decir, 1,0 con 18 decimales).

Con el ejemplo hipotético: 10 ETH a 2000 USD, LT de 80 % y deuda de 12 000 USDC dan HF=10⋅2000⋅0,8/12000≈1,33HF = 10 \cdot 2000 \cdot 0{,}8 / 12000 \approx 1{,}33. Si ETH baja a 1450 USD, HF=11600/12000≈0,97HF = 11600 / 12000 \approx 0{,}97. Nadie movió un token, pero la posición ya es liquidable.

El invariante que el protocolo intenta sostener es más amplio que el HF de una cuenta: el valor del colateral recuperable debe cubrir la deuda total del pool. El HF es la alarma temprana. Si nadie actúa a tiempo, el colateral puede caer por debajo de la deuda y aparece la deuda incobrable (bad debt): préstamos que ya nadie tiene incentivo para repagar.

Traza de ejecución de una liquidación en Aave V3

El punto de entrada es una función pública del pool. Cualquiera puede llamarla; no hay lista de liquidadores autorizados.

solidity
// Firma tomada de IPool.sol (aave-v3-core).
function liquidationCall(
    address collateralAsset, // colateral que el liquidador quiere recibir
    address debtAsset,       // deuda que el liquidador repaga
    address user,            // posición a liquidar
    uint256 debtToCover,     // cuánta deuda quiere cubrir
    bool receiveAToken       // recibir aTokens o el activo subyacente
) external;

La ejecución sigue cuatro pasos. Primero, valida que el HF del usuario esté por debajo de 1 y que las reservas estén activas y no pausadas. Segundo, fija el close factor, la fracción máxima de deuda que se puede repagar en una llamada: 50 % si el HF está por encima de 0,95 y 100 % si está en 0,95 o menos.

Tercero, calcula cuánto colateral entrega. Convierte la deuda cubierta a unidades de colateral con los precios del oráculo y la multiplica por el bonus de liquidación, un parámetro por activo expresado como porcentaje sobre 100 % (por ejemplo, 10 500 equivale a 105 %). Si el usuario no tiene tanto colateral, se entrega todo el disponible y se reduce la deuda cubierta en proporción. Cuarto, si el activo tiene configurada una fee de liquidación, una parte del bonus va al tesoro del protocolo y no al liquidador.

Sigamos el ejemplo con un bonus hipotético de 5 % y sin fee. Con HF de 0,97 el close factor es 50 %: el liquidador repaga 6000 USDC y recibe colateral por 6000⋅1,05=63006000 \cdot 1{,}05 = 6300 USD, unos 4,34 ETH a 1450 USD. Quedan unos 5,66 ETH (8200 USD) contra 6000 de deuda, y el HF sube a 8200⋅0,8/6000≈1,098200 \cdot 0{,}8 / 6000 \approx 1{,}09. La posición vuelve a estar sana, más pequeña.

Las versiones posteriores del código, en el repositorio aave-v3-origin, añaden una regla para posiciones pequeñas: si el colateral o la deuda de la reserva quedan bajo un umbral en moneda base, se permite liquidar el 100 %, para que no queden restos que a nadie le conviene liquidar. Antes de integrar, lee la versión desplegada en la red que uses; los parámetros cambian por gobernanza.

Figura 1

Health factor antes y después de una liquidación

Fuentes:

[1][2]
Health factor antes y después de una liquidación
Cómo leer esta figura

Tres columnas. A la izquierda, posición sana: colateral C por umbral LT sobre deuda D da HF mayor o igual a 1. En el centro, el precio del oráculo cae y el HF queda bajo 1; la posición es liquidable por cualquiera. A la derecha, el liquidador repaga una deuda d como máximo igual al close factor por D, y recibe colateral por valor d por uno más el bonus; colateral y deuda bajan y el HF vuelve a ser mayor o igual a 1. Una nota al pie indica que el close factor es 50 por ciento con HF sobre 0,95 y 100 por ciento con HF en 0,95 o menos.

Visual original de TrautsLab. Una caída de precio baja el health factor bajo 1. El liquidador repaga como máximo el close factor de la deuda y recibe colateral equivalente más el bonus, y la posición restante vuelve a quedar sobre 1 si el colateral alcanza.

Riesgo operativo del oráculo: precio viejo o precio equivocado

Toda la aritmética anterior usa precios que el contrato recibe de un oráculo: un contrato que publica datos externos en la cadena. Si el oráculo entrega un precio viejo, el protocolo liquida tarde o no liquida. Si entrega un precio manipulado, liquida posiciones sanas o presta contra colateral inflado.

Los feeds de Chainlink exponen latestRoundData(), que devuelve roundId, answer, startedAt, updatedAt y answeredInRound. La documentación indica que latestAnswer no incluye marca de tiempo para comprobar frescura, y que la responsabilidad de monitorear y mitigar riesgos de mercado y de código recae en quien integra el feed.

El siguiente contrato es ilustrativo, no auditado. Muestra los dos controles mínimos al leer un feed: precio positivo y antigüedad acotada.

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
interface AggregatorV3Interface {
    function decimals() external view returns (uint8);
    function latestRoundData()
        external
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
 
/// Ilustrativo: lectura de precio con controles mínimos. No es código auditado.
contract PriceGuard {
    AggregatorV3Interface public immutable feed;
    uint256 public immutable maxAge; // segundos; se fija a partir del heartbeat publicado del feed
 
    error StalePrice(uint256 updatedAt);
    error InvalidPrice(int256 answer);
 
    constructor(AggregatorV3Interface feed_, uint256 maxAge_) {
        feed = feed_;
        maxAge = maxAge_;
    }
 
    function price() external view returns (uint256 value, uint8 decimals) {
        (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();
        if (answer <= 0) revert InvalidPrice(answer);
        if (block.timestamp - updatedAt > maxAge) revert StalePrice(updatedAt);
        return (uint256(answer), feed.decimals());
    }
}

Aave añade otra defensa pensada para redes de capa 2. Si el pool tiene configurado un PriceOracleSentinel y este indica que las liquidaciones no están permitidas (por ejemplo, tras una caída del secuenciador), solo se pueden liquidar posiciones con HF por debajo de 0,95. La idea general: cuando no confías en el precio, reduces lo que el precio puede disparar.

Un precio tomado directamente del estado de un AMM en el mismo bloque es fácil de mover con un préstamo flash. Ese riesgo ya se trató en el capítulo de oráculos; aquí basta recordar que el health factor hereda cualquier debilidad del precio que lo alimenta.

Cuello de botella del liquidador: cuando la transacción no llega

El protocolo no liquida solo. Depende de que alguien vea la posición, construya la transacción, pague gas y logre incluirla en un bloque. Ese alguien solo actúa si el bonus supera sus costos: gas, deslizamiento al vender el colateral y riesgo de que el precio siga cayendo.

Tres situaciones rompen esa cadena, y conviene simularlas antes de fijar parámetros:

  • Congestión. Muchas posiciones caen a la vez, el gas sube y las liquidaciones pequeñas dejan de ser rentables.
  • Competencia por el bloque. Varios liquidadores apuntan a la misma posición; solo una transacción gana y el resto revierte o paga sin resultado.
  • Mercado ilíquido. Si vender el colateral mueve mucho el precio, el bonus no compensa y nadie liquida hasta que la posición tiene más deuda que colateral.
Lectura crítica

Qué revisar en un mercado de lending

Reglas de decisión rápida

  • ¿El precio puede estar viejo?

    updatedAt lejano o feed sin marca de tiempo

    → Exigir antigüedad máxima y pausar préstamos si se supera.

  • ¿El precio puede moverse dentro de un bloque?

    lectura directa de reservas de un AMM

    → Usar feeds agregados o precios promediados en el tiempo.

  • ¿El bonus cubre gas y deslizamiento?

    liquidez del colateral en los mercados donde se vende

    → Simular caídas fuertes con liquidez real antes de listar el activo.

  • ¿Quedan restos sin incentivo?

    posiciones pequeñas tras liquidaciones parciales

    → Permitir liquidación completa bajo un umbral de tamaño.

Cada eslabón de la cadena de solvencia falla de forma distinta.

Lo que sostiene la solvencia

Quédate con una frase: un protocolo de lending es solvente mientras exista un precio confiable y alguien con incentivo para liquidar a tiempo. El health factor es solo el número que dice cuándo empieza esa carrera. En la ruta de práctica, este mecanismo se vuelve un ejercicio local: un oráculo que puedes mover a mano y una liquidación que ejecutas tú mismo.

Referencias

Bibliografía académica

Formato IEEENumerado para ingeniería, sistemas y protocolos.
  1. [1]

    Aave, "LiquidationLogic.sol," aave-v3-core, GitHub repository.

    source codeID: AAVE-V3-LIQUIDATION-LOGICAbrir fuente (abre en otra pestaña)

    Close factor, bonus de liquidación y fee del protocolo.

  2. [2]

    Aave, "GenericLogic.sol," aave-v3-core, GitHub repository.

    source codeID: AAVE-V3-GENERIC-LOGICAbrir fuente (abre en otra pestaña)

    calculateUserAccountData: fórmula del health factor.

  3. [3]

    Aave, "ValidationLogic.sol," aave-v3-core, GitHub repository.

    source codeID: AAVE-V3-VALIDATION-LOGICAbrir fuente (abre en otra pestaña)

    validateLiquidationCall y umbral HF = 1e18.

  4. [4]

    Aave, "IPool.sol," aave-v3-core, GitHub repository.

    source codeID: AAVE-V3-IPOOLAbrir fuente (abre en otra pestaña)

    Firma de liquidationCall y getUserAccountData.

  5. [5]

    Aave DAO, "LiquidationLogic.sol," aave-v3-origin, GitHub repository.

    source codeID: AAVE-V3-ORIGIN-LIQUIDATIONAbrir fuente (abre en otra pestaña)

    MIN_BASE_MAX_CLOSE_FACTOR_THRESHOLD y MIN_LEFTOVER_BASE.

Fuentes primarias, papers seminales y especificaciones técnicas citadas en esta investigación.

Rutas vecinas

Si este tema te abrió hambre, sigue por aquí.

Series y labs que comparten área o dependen de esta ruta. Pocas opciones, para no convertir la curiosidad en menú infinito.

Sigue leyendo

Ideas conectadas con este artículo

Una ruta corta para continuar sin abrir veinte pestañas y perder el hilo.

Los comentarios se cargan al final de la lectura para no meter peso antes de tiempo.