Ethereum: de historia a labs

Artículo 41 de 48

85%

completado al llegar aquí

Sistemas distribuidosNota de investigación13 min de lectura

AMMs: x*y=k sin vender humo matemático

Un AMM de producto constante con números reales: invariante x·y=k, comisión, LP shares, por qué el precio spot no es un oráculo y qué pierde el LP.

Ruta Ethereum: 0/48

Un contrato guarda 100 ETH y 200.000 USDC. Llega alguien con 10.000 USDC y quiere ETH. No hay vendedores con órdenes publicadas ni un motor de emparejamiento: el contrato calcula cuánto ETH entregar con una fórmula, recibe los USDC y actualiza sus saldos. El comprador se lleva unos 4,75 ETH. ¿Por qué 4,75 y no 5, si el precio "era" 2.000 USDC por ETH?

Ese contrato es un AMM (automated market maker, creador de mercado automático). La documentación de Uniswap lo describe como un conjunto de contratos que reemplaza el libro de órdenes por un pool de dos tokens ERC-20 cuyo precio sale del estado del pool; quien intercambia opera contra las reservas, no contra otra persona. Este capítulo reconstruye ese cálculo desde el código de Uniswap v2, el diseño de producto constante más estudiado.

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

Trader, LP y arbitrajista

El trader cambia reservas, el LP las aporta y el arbitrajista alinea el precio con otros mercados.

Evidencia

Reservas y evento Swap

getReserves muestra el estado; el evento registra entradas y salidas de cada intercambio.

Estado

reserve0, reserve1 y totalSupply

Dos reservas y la oferta de LP tokens definen precio y propiedad del pool.

Preguntas guía

  • ¿Qué cantidad debe mantenerse (o crecer) después de cada intercambio?
  • ¿Por qué el precio recibido empeora a medida que crece la operación?
  • ¿Qué representa un LP token y cómo se calcula cuántos se emiten?
  • ¿Por qué otro contrato no debe leer el precio del pool en el mismo bloque como si fuera un oráculo?

La invariante: x · y = k

Llama x a las reservas de ETH e y a las de USDC. Un pool de producto constante exige que, después de cada intercambio, el producto x · y no disminuya. Con 100 ETH y 200.000 USDC, k = 20.000.000. El precio spot (el precio marginal para una operación infinitamente pequeña) es el cociente de reservas: y / x = 2.000 USDC por ETH.

El whitepaper de Uniswap v2 resume así el diseño heredado de v1: cada par guarda reservas de dos activos y mantiene la invariante de que el producto de las reservas no puede disminuir; los traders pagan una comisión de 30 puntos básicos (0,3 %) que va a los proveedores de liquidez.

En el contrato del par, la comprobación es literal. Tras transferir los tokens de salida, swap mide los saldos nuevos, descuenta 3/1000 de lo que entró y exige que el producto ajustado no sea menor que el producto de las reservas anteriores:

solidity
// Extracto de UniswapV2Pair.swap (Solidity 0.5.16, versión original)
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');

El contrato no calcula cuánto entregar: confía en que quien llama pida una salida que respete la invariante, y revierte si no. Por eso existe una biblioteca periférica que hace el cálculo previo.

Traza de un swap con números

La función getAmountOut de la biblioteca de v2 despeja la salida máxima que respeta la invariante con comisión. Aquí está la misma aritmética, ilustrativa, portada a Solidity 0.8:

solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;
 
/// Ilustrativo: misma aritmética que UniswapV2Library.getAmountOut, portada a 0.8.
library AmmMath {
    function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
        internal
        pure
        returns (uint256 amountOut)
    {
        require(amountIn > 0, "INSUFFICIENT_INPUT_AMOUNT");
        require(reserveIn > 0 && reserveOut > 0, "INSUFFICIENT_LIQUIDITY");
        uint256 amountInWithFee = amountIn * 997;
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = reserveIn * 1000 + amountInWithFee;
        amountOut = numerator / denominator;
    }
}

Apliquemos la fórmula al ejemplo.

PasoCálculoResultado
Entrada con comisión10.000 × 997 / 10009.970 USDC efectivos
Salida9.970 × 100 / (200.000 + 9.970)4,7483 ETH
Precio pagado10.000 / 4,7483≈ 2.106 USDC/ETH
Reservas nuevas95,2517 ETH y 210.000 USDCspot ≈ 2.205 USDC/ETH
Producto nuevo95,2517 × 210.000≈ 20.002.858, mayor que k

Sin comisión, la salida sería 100 × 10.000 / 210.000 ≈ 4,7619 ETH. La diferencia entre 2.000 y 2.106 tiene dos partes: la comisión y el impacto de precio, que es el movimiento de la curva causado por la propia operación. Cuanto mayor es la operación respecto de las reservas, mayor el impacto. Y el producto final queda por encima de 20.000.000: esa diferencia es la comisión que se queda en el pool.

Figura 1

Un swap recorre la curva x·y = k

Fuentes:

[2]
Un swap recorre la curva x·y = k
Cómo leer esta figura

Gráfico con las reservas de ETH en el eje horizontal, entre 90 y 105, y las de USDC en el eje vertical, entre 190 mil y 220 mil. Una curva azul representa x por y igual a 20 millones. Un punto marca el estado inicial en 100 ETH y 200.000 USDC, con precio spot y sobre x igual a 2000. Una flecha roja horizontal hacia la izquierda indica que salen 4,75 ETH y una flecha verde vertical hacia arriba indica que entran 10.000 USDC, llegando al punto final en 95,25 ETH y 210.000 USDC, con precio spot cercano a 2205. Un recuadro explica que el precio pagado es 10.000 dividido 4,75, cerca de 2106, mayor que el spot inicial por el impacto de precio y la comisión de 0,3 por ciento.

Visual original de TrautsLab. Comprar ETH con 10.000 USDC mueve el pool de (100; 200.000) a (95,25; 210.000): el precio pagado, unos 2.106 USDC por ETH, queda entre el spot inicial de 2.000 y el spot final de unos 2.205.

LP shares: quién es dueño de qué

Un LP (liquidity provider, proveedor de liquidez) deposita ambos tokens y recibe LP tokens, un ERC-20 que representa su fracción del pool. En mint, el primer depósito emite sqrt(amount0 · amount1) menos MINIMUM_LIQUIDITY (1.000 unidades mínimas), que se envían a la dirección cero y quedan bloqueadas para siempre. Los depósitos posteriores reciben el mínimo entre amount0 · totalSupply / reserve0 y amount1 · totalSupply / reserve1, así que depositar en una proporción distinta de la del pool regala la diferencia.

Las comisiones no se reparten en cada swap. Se quedan en las reservas, hacen crecer k y, por tanto, el valor de cada LP token. Al retirar con burn, el LP recibe su fracción de reservas ya engordadas. Si la comisión de protocolo está activada (feeTo distinto de cero), el contrato emite para el protocolo LP tokens equivalentes a 1/6 del crecimiento de sqrt(k) desde el último cálculo.

La misma documentación de Uniswap aclara que en v2 los LP tokens son fungibles, mientras que en v3 y v4 cada LP elige un rango de precios y su posición deja de ser intercambiable con la de otro. Ese cambio es el tema del capítulo siguiente.

Riesgo operativo: el precio spot no es un oráculo

Supón que un contrato de préstamos valora colateral leyendo reserve1 / reserve0 del pool. El whitepaper de v2 describe el ataque: alguien compra ETH en el par para inflar el precio, dispara la operación que lee ese precio (por ejemplo, una liquidación o el cierre de un derivado) y vende el ETH de vuelta. Todo puede ocurrir en una sola transacción atómica. El AMM funcionó según su curva; lo que falló fue el contrato que confundió un precio transitorio con evidencia.

La respuesta de v2 fue registrar el precio antes del primer intercambio de cada bloque, que es más difícil de manipular, y acumularlo ponderado por tiempo. En _update, los contadores price0CumulativeLast y price1CumulativeLast suman el precio por los segundos transcurridos, en formato de punto fijo UQ112.112. Quien necesite un precio promedio ponderado por tiempo (TWAP) guarda el acumulador en dos momentos y divide la diferencia por el tiempo. Una ventana más larga encarece la manipulación a costa de un precio menos actual, y los timestamps de bloque no son exactos.

Para un contrato en producción, la regla práctica es leer un TWAP o un feed dedicado, nunca el spot del mismo bloque. El capítulo de oráculos desarrolla las alternativas.

Riesgo del LP: pérdida frente a mantener y frente al rebalanceo

El LP no solo cobra comisiones: también vende el activo que sube y compra el que baja, porque los arbitrajistas llevan el pool al precio externo. Se puede calcular cuánto cuesta eso con la invariante. Si el precio externo se multiplica por r, un pool de producto constante sin comisiones queda con un valor igual a 2·√r / (1 + r) veces el que tendrían los mismos tokens guardados fuera. Con r = 2 (el precio se duplica) la diferencia es de unos −5,7 %; con r = 4, de −20 %. Es la llamada pérdida impermanente: desaparece si el precio vuelve, y se realiza si el LP retira antes.

La investigación académica pone números más precisos. Angeris y coautores muestran que, bajo condiciones comunes, los mercados de producto constante siguen de cerca el precio de referencia, justamente porque los arbitrajistas lo imponen. Milionis, Moallemi, Roughgarden y Zhang descomponen el retorno del LP en una exposición al mercado y un componente de microestructura: comisiones cobradas menos pérdidas frente a arbitrajistas, que llaman loss-versus-rebalancing (LVR).

Para quien decide poner tesorería en un pool, la conclusión es operativa: comparar "APR de comisiones" con cero es un error. La comparación correcta es contra mantener los activos o contra una estrategia que rebalancea a precios de mercado.

La capa periférica: mínimo a recibir y plazo

Ningún usuario debería llamar a swap directamente. El router de v2 ofrece funciones como swapExactTokensForTokens(amountIn, amountOutMin, path, to, deadline): calcula las salidas por la ruta, revierte si la última es menor que amountOutMin y revierte si el bloque llega después de deadline. Esos dos parámetros son la defensa del usuario frente a cambios de precio y frente a quien reordena transacciones, como se vio en el capítulo de MEV.

Revisión de integración

Antes de integrar un AMM en un sistema propio

0 de 5 revisados

Lab propuesto: reproducir la tabla en una red local

Desplegar en Anvil dos ERC-20 de prueba y un par de producto constante, aportar 100 y 200.000 unidades y ejecutar el swap de 10.000. La validación observable es que getReserves devuelva las reservas de la tabla (salvo redondeo entero), que el evento Swap registre las cantidades y que un segundo swap con amountOutMin mayor que la salida calculada revierta. Como teardown basta detener Anvil.

Conexiones dentro de la serie

Referencias

Bibliografía académica

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

    Uniswap Labs, "How Uniswap Works," Uniswap Docs (repositorio Uniswap/docs).

    Pools de dos ERC-20, fórmula de producto constante e impacto de precio.

  2. [2]

    H. Adams, N. Zinsmeister y D. Robinson, "Uniswap v2 Core," whitepaper, marzo de 2020.

    whitepaperID: UNISWAP-V2-WHITEPAPERAbrir fuente (abre en otra pestaña)

    Producto constante, comisión de 0,3 %, oráculo TWAP y riesgos de manipulación del precio spot.

  3. [3]

    Uniswap, "UniswapV2Pair.sol," Uniswap/v2-core, GitHub.

    source codeID: UNISWAP-V2-PAIRAbrir fuente (abre en otra pestaña)

    swap con comprobación de K, mint con MINIMUM_LIQUIDITY, comisión de protocolo y acumuladores de precio.

  4. [4]

    Uniswap, "UniswapV2Library.sol," Uniswap/v2-periphery, GitHub.

    source codeID: UNISWAP-V2-LIBRARYAbrir fuente (abre en otra pestaña)

    getAmountOut con comisión de 0,3 % (factor 997/1000).

  5. [5]

    G. Angeris, H.-T. Kao, R. Chiang, C. Noyes y T. Chitra, "An analysis of Uniswap markets," arXiv:1911.03380, 2019.

    Los mercados de producto constante siguen el precio de referencia bajo condiciones comunes.

  6. [6]

    J. Milionis, C. C. Moallemi, T. Roughgarden y A. L. Zhang, "Automated Market Making and Loss-Versus-Rebalancing," arXiv:2208.06046, 2022.

    Retorno del LP: exposición al mercado más comisiones menos pérdidas frente a arbitrajistas (LVR).

  7. [7]

    Uniswap, "UniswapV2Router02.sol," Uniswap/v2-periphery, GitHub.

    source codeID: UNISWAP-V2-ROUTERAbrir fuente (abre en otra pestaña)

    swapExactTokensForTokens con amountOutMin y deadline.

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.