En Uniswap V2, el protocolo rastrea las reservas de tokens y deriva el precio spot, , y la liquidez total, , donde e son las reservas de los tokens X e Y.
Uniswap V3, en cambio, rastrea el precio actual y la liquidez, y deriva las reservas. Este cálculo es complejo y se cubrirá en capítulos posteriores.
En realidad, Uniswap V3 almacena la raíz cuadrada del precio, , en lugar del precio en sí. Este enfoque mejora la eficiencia de gas, como examinaremos en detalle en un capítulo posterior. No perdemos precisión al hacer esto, ya que el precio siempre se puede derivar de su raíz cuadrada.
El objetivo de este capítulo es discutir dónde y cómo Uniswap V3 almacena la raíz cuadrada del precio, , y cómo maneja el problema de que el precio del token puede ser un valor decimal, mientras que Solidity no tiene un tipo de número de punto flotante o decimal.
La variable sqrtPriceX96
La raíz cuadrada del precio se almacena en la variable de campo sqrtPriceX96 del struct slot0 en el contrato del pool, como se muestra en la imagen a continuación.

El struct slot0 también almacena otras variables, como tick, que representa el tick actual, como vimos en el capítulo sobre ticks. Las otras variables en slot0 se relacionan con el oráculo, las comisiones o la seguridad del contrato y se examinarán más adelante.
El nombre de la variable sqrtPriceX96 ya indica que el valor almacenado es la raíz cuadrada del precio (sqrtPrice) en un formato de número Q96 (X96), específicamente el formato Q64.96.
En el capítulo anterior se proporcionó una explicación detallada del formato de número Q. En la siguiente sección, revisaremos brevemente su uso en Uniswap V3.
La raíz cuadrada del precio, , como un valor de punto fijo
Uniswap V3 almacena la raíz cuadrada del precio como un número de punto fijo.
A modo de repaso, los números de punto fijo nos permiten representar números fraccionarios de manera eficiente en cuanto a gas. Por ejemplo, supongamos que necesitamos almacenar 1.0050122696230506 pero solo podemos almacenar números enteros. Un enfoque es multiplicar el valor por un número grande, como , dando como resultado:
Luego descartamos la porción decimal y almacenamos 79625275426524700982079509374.
Por lo tanto, la relación entre la representación de punto fijo Q64.96 de un número y el número (original) es
Para recuperar el valor original, simplemente dividimos la representación de punto fijo entre , como
En nuestro ejemplo, el valor 1.0050122696230506 se representa como un número de punto fijo: 79625275426524700982079509374. Para recuperar el valor original, dividimos este valor entre , obteniendo aproximadamente 1.0050122696230507, que está muy cerca del número original.
Esto es exactamente lo que hace Uniswap V3 para sortear el hecho de que Solidity no tiene un tipo flotante o decimal. La variable que contiene la raíz cuadrada del precio del token, sqrtPriceX96 , es un número de punto fijo que se obtiene multiplicando la raíz cuadrada real, y posiblemente fraccionaria, del precio , por .
La relación entre y sqrtPriceX96
La relación entre y sqrtPriceX96 es que es la raíz cuadrada real del precio, mientras que sqrtPriceX96 representa la raíz cuadrada del precio de un token en el formato Q64.96.
Esta relación se puede expresar con una fórmula simple. Para convertir a sqrtPriceX96, utilizamos:
Para volver a convertir sqrtPriceX96 a , utilizamos
Calculando sqrtPriceX96 usando Python
A continuación se muestra un código en Python para convertir un precio a sqrtPriceX96 y luego recuperar el precio original:
from decimal import Decimal, getcontext
getcontext().prec = 100
price = Decimal('1.0050122696230506')
sqrtPriceX96 = price * Decimal(2) ** 96 # Decimal('79625275426524700982079509374.6667867672150016')
original_price = sqrtPriceX96 / 2 ** 96 # Decimal('1.0050122696230506')
Se utilizó la biblioteca decimal para aumentar la precisión del cálculo.
Ejemplos de valores de sqrtPriceX96
Revisemos algunos ejemplos.
- Si la raíz cuadrada actual del precio de un token es 100, este valor se almacenará en la variable
sqrtPriceX96como
- Si la raíz cuadrada actual del precio es 323.002, se almacenará en
sqrtPriceX96como:
Para recuperar los valores originales (y reales), simplemente divida los valores anteriores entre .
Consultando sqrtPriceX96 en slot0 para encontrar el precio de un pool
El pool ETH:DAI en Base
El valor actual de sqrtPriceX96 para el pool ETH:DAI en Base se puede ver en la imagen a continuación como 4552234755200983230583166215033.

Para convertir de sqrtPriceX96 a , dividimos el valor obtenido entre . Así que:
A partir de la raíz cuadrada del precio, obtenemos el precio real elevando el valor al cuadrado:
Esto representa el valor de Ether en términos de DAI al momento en que se escribió esta parte del texto.
El pool USDC:ETH en mainnet
Como otro ejemplo, obtengamos el sqrtPriceX96 en el pool USDC:ETH en mainnet. Este ejemplo es diferente del anterior por dos razones: primero, el precio es para USDC en términos de Ether, en lugar de Ether en términos de una stablecoin. Segundo, Ether tiene 18 posiciones decimales, mientras que USDC tiene solo 6, a diferencia del ejemplo anterior donde ambos tokens tenían 18 posiciones decimales.
Como se puede ver en la imagen, el valor es 1506673274302120988651364689808458.

El valor para se puede calcular como
El precio se puede calcular como
Dado que este es un pool USDC:ETH, lo que tenemos es el precio de USDC en términos de ETH. El precio de ETH en términos de USDC está dado por la inversa del precio obtenido:
Por último, USDC tiene 6 posiciones decimales, mientras que ETH tiene 18. Necesitamos tener en cuenta esta diferencia; para calcular el precio de ETH en términos de USDC, debemos multiplicar el valor anterior por .
Esto representa el valor de Ether en términos de USDC al momento en que se escribió esta parte del texto. ETH ha sido extremadamente volátil al momento en que escribimos esto.
El precio máximo en el protocolo
Dado que la raíz cuadrada del precio se almacena en un número de formato Q64.96, el número entero más grande que puede almacenar es aproximadamente . El precio correspondiente a esta raíz cuadrada se puede obtener elevando al cuadrado este número entero más grande.
Por lo tanto, la mayor raíz cuadrada de un precio con la que puede trabajar el protocolo es aproximadamente , y el mayor precio correspondiente está ligeramente por debajo de .
El precio mínimo en el protocolo
Un número de punto fijo derivado de puede representar fracciones tan pequeñas como . Esto se debe a que , cuando se multiplica por , da como resultado 1, que se puede almacenar como un número entero.
Valores menores que están fuera de rango. Por ejemplo, , cuando se convierte a punto fijo multiplicando por , se convierte en , o 0.5. Como solo se conserva la parte entera, se redondea hacia abajo a 0, causando que se pierda la información del valor original.
Por lo tanto, en teoría, el precio más bajo con el que el protocolo podría trabajar es o . Sin embargo, como veremos en el próximo capítulo, no permite un precio tan bajo.
El protocolo impone una simetría entre la raíz cuadrada más grande del precio que un token puede asumir, que es , y la raíz cuadrada más pequeña del precio que un token puede asumir, que se impone que sea . Esta simetría tiene sentido, ya que el valor del token X en relación con Y es el inverso del valor del token Y en relación con X.
Por qué usar Q64.96 para almacenar la raíz cuadrada del precio
Esta no es una pregunta fácil de responder, y el equipo del protocolo podría haber elegido un formato de número Q diferente. Dado que decidieron empaquetar la raíz cuadrada del precio junto con el tick y otra información en un solo slot de almacenamiento de 256 bits, el espacio que quedó para la raíz cuadrada del precio fue de 160 bits.
En la siguiente sección, mostraremos cómo el uso de 64 bits para la porción de números enteros es suficiente para acomodar los precios en un escenario del mundo real, permitiendo que el protocolo soporte un pool donde una moneda vale billones de dólares mientras que la otra vale solo fracciones de centavo.
Límites de precio de un token en un escenario del mundo real
Como hemos visto, el precio más alto que puede manejar el protocolo es aproximadamente (), o aproximadamente en orden de magnitud. Dado que los precios de los tokens siempre son relativos a otro token, la diferencia de precio entre dos tokens en un pool puede abarcar hasta órdenes de magnitud.
También debemos tener cuidado de tener en cuenta los decimales al calcular las diferencias de precio. Por ejemplo, un token con 18 posiciones decimales se almacena en su contrato como unidades, mientras que un token con 8 posiciones decimales se almacena como unidades. Por lo tanto, si estos dos tokens tienen el mismo valor en dólares, su diferencia de precio en el pool ya tendrá 10 órdenes de magnitud solo debido a las posiciones decimales.
Consideremos un ejemplo del mundo real, un pool WBTC:PEPE. Actualmente, 1 WBTC vale aproximadamente 100,000 () dólares, mientras que 1 PEPE vale aproximadamente 0.00001 dólares, lo que supone una diferencia de 10 órdenes de magnitud.
Sin embargo, también debemos considerar la diferencia en posiciones decimales. WBTC tiene 8 posiciones decimales, mientras que PEPE tiene 18 posiciones decimales, lo que resulta en una diferencia de 10 órdenes de magnitud, además de la diferencia de 10 órdenes de magnitud debido a la diferencia de precio.
Por lo tanto, la unidad más pequeña del token WBTC (un Satoshi, o WBTC) vale (o cien trillones) de veces más que la unidad más pequeña de PEPE (que no tiene nombre pero representa PEPE).
Veinte órdenes de magnitud es una diferencia significativa, pero el pool de Uniswap V3 permite hasta 38 órdenes de magnitud de diferencia. Por lo tanto, el precio de WBTC en relación con PEPE podría aumentar en otros 18 órdenes de magnitud antes de alcanzar el límite de Uniswap V3.
Suponiendo que PEPE se mantenga en su precio actual (0.00001 dólares), el precio de Bitcoin debe alcanzar los cien mil trillones de dólares antes de que se alcancen los límites de precio de Uniswap V3. Del mismo modo, asumiendo que Bitcoin alcance 1 billón de dólares, PEPE podría caer hasta 0.0000000000000001 dólares (una décima parte de una milbillonésima de dólar) antes de que se alcancen los límites de precio del protocolo.
Derivar estos límites queda como ejercicio para el lector.
Resumen
- Uniswap V3 almacena la raíz cuadrada de un precio en un formato de número Q64.96. Hace esto porque no hay números de punto flotante en Solidity, y trabajar con números de punto fijo en binario es eficiente en cuanto a gas.
- El precio más alto de un token que el protocolo puede almacenar es aproximadamente . Para mantener la simetría, el precio más bajo que puede almacenar es . Los precios de los tokens en el pool no pueden exceder estos valores.