Este artículo explica cómo funciona la función getSqrtRatioAtTick() en la biblioteca TickMath de Uniswap V3. La función getSqrtRatioAtTick() toma un índice de tick y devuelve el precio de la raíz cuadrada en ese tick exacto como un número Q64.96. La función calcula:
donde es el índice del tick. A continuación se muestra una captura de pantalla de la función:

Este tutorial asume que el lector ha comprendido nuestra explicación del algoritmo square-and-multiply, en el cual se basa getSqrtRatioAtTick(). Haremos referencia frecuentemente a los conceptos de ese tutorial aquí, por lo que sugerimos que el lector revise ese artículo primero.
Descripción general de getSqrtPriceRatioAtTick
La función sigue estos pasos:
- Calcula el valor absoluto del tick con el código
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));. - Comprueba que el tick se encuentre en el rango del tick mínimo y máximo y revierte si está fuera de rango:
require(absTick <= uint256(MAX_TICK), 'T');. - Calcula como un número Q128.128 utilizando square-and-multiply.
- Si el tick original era positivo, calcula
- Convierte el número Q128.128 a un número Q64.96 con
>> 32
Aquí están los pasos descritos en el código:

Parte 1/5: Por qué Uniswap V3 calcula el valor absoluto del tick, es decir,
La primera línea de código en la función calcula el valor absoluto del tick:
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
Los índices de los ticks pueden ser tanto positivos como negativos. Para evitar manejar ambos casos por separado, la porción de square-and-multiply de getSqrtRatioAtTick() solo calcula ticks negativos. Si el tick original era positivo, entonces el resultado del algoritmo square-and-multiply calcula el recíproco.
Por ejemplo, si el tick original es el tick positivo 5, el algoritmo calcula el tick para -5:
Luego calcula el recíproco:
Observa que en general:
Por lo tanto, la función primero calcula:
Luego, si el tick originalmente era positivo, invierte la respuesta devolviendo el recíproco:
Si el tick originalmente era negativo, el código no calcula el recíproco.
Parte 2/5: Comprobando si el tick está en rango
La segunda línea de código en la función se explica por sí misma:
require(absTick <= uint256(MAX_TICK), 'T');
El tick máximo es una constante de 887272 ******en el archivo, la cual derivamos en nuestro artículo sobre Tick Limits. No necesitamos comprobar si el tick es menor que MIN_TICK ya que calculamos el valor absoluto del tick, por lo que absTick no puede ser negativo.
Parte 3/5: Calculando el precio mediante square and multiply
En esta sección, mostramos cómo la función utiliza el algoritmo square and multiply y derivamos las constantes grandes de la función.
La variable utilizada para calcular el precio es ratio, pero la variable que se devuelve es sqrtPriceX96.
Aquí está la parte relevante de la función que utiliza square and multiply:
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
ratio es un Q128.128 pero sqrtPriceX96 es un Q64.96
Para maximizar la precisión, getSqrtRatioAtTick() utiliza internamente números Q128.128 mientras ejecuta el algoritmo square-and-multiply, pero devuelve la respuesta como un Q64.96. La representación interna del precio es la variable ratio.
Repaso del cálculo previo en square-and-multiply
Para explicar cómo Uniswap V3 derivó las constantes grandes mostradas anteriormente, primero debemos repasar el algoritmo square and multiply.
El algoritmo square-and-multiply se basa en el cálculo previo de las potencias de la base. Ahora demostramos que las constantes grandes en el código se derivan de elevar al cuadrado repetidamente .
Utilizando el algoritmo square-and-multiply, getSqrtRatioAtTick() precalcula:
Los ticks mínimo y máximo en Uniswap V3 son -887,272 y 887,272 respectivamente. Sin embargo, dado que getSqrtPriceRatioAtTick solo calcula la porción negativa directamente y calcula los ticks positivos mediante su recíproco, solo necesita calcular los ticks en el rango [-887,272, 0]. El número de bits requeridos para codificar un número hasta 887,273 (887,272 más 0) es 20, ya que . Esta es la razón por la que los valores precalculados van desde , ,…, .
A modo de ejemplo, supongamos que queremos calcular el precio en el tick -100. Podemos multiplicar los valores precalculados para los ticks -64, -32 y -4 de la siguiente manera:
Derivando los valores de las constantes grandes como números Q128.128
Ahora mostraremos cómo Uniswap V3 derivó las constantes grandes.
Precio de la raíz cuadrada del tick 0
La constante grande 0x100000000000000000000000000000000 (caja violeta) es el número de coma fija Q128.128 (equivalente a 2 << 128).

Esto corresponde al tick 0, . Ten en cuenta que si tick = 0, entonces absTick & 0x1 != 0 en la línea 27 (caja naranja) será falso, activando la segunda parte del operador ternario.
Si este tick es 0, ninguno de los otros bits será 1 y, por lo tanto, ninguno de los condicionales posteriores if (absTick & 0xXX !=0) será verdadero, lo que significa que ratio será igual a 0x100000000000000000000000000000000 sin más modificaciones. Esto es lo esperado, ya que elevar un número a cero devuelve 1.
Precio de la raíz cuadrada del tick -1
Si calculamos el precio del tick -1 en Q128.128 en Python, obtenemos lo siguiente:
>>>
0xfffcb933bd6f b0000000000000000000 # gap added for clarity
Al convertirlo a hexadecimal, se acerca al número mágico resaltado a continuación, pero es evidente que nuestra estimación anterior tiene más ceros al final, lo que significa que tuvo pérdida de precisión:

Aquí está la constante de Uniswap para en comparación con nuestra estimación:
# Uniswap's constant
>>> hex(0xfffcb933bd6fad37aa2d162d1a594001)
0xfffcb933bd6f ad37aa2d162d1a594001 # gap added for clarity
# Our constant
>>> hex(int(2**128 * math.sqrt(1.0001**-1)))
0xfffcb933bd6f b0000000000000000000 # gap added for clarity
# note that Uniswap's and our estimate differ after the gap
En la siguiente sección, mostramos cómo mejorar nuestra estimación para igualar la de Uniswap V3.
Mejorando nuestros cálculos de constantes mediante el uso de Decimal y reordenando las divisiones
Por defecto, los números de punto flotante de Python no tienen suficiente precisión para calcular números con 128 bits de precisión, pero solucionamos esto utilizando la biblioteca Decimal, la cual nos otorga tanta precisión como deseemos:
from decimal import *
getcontext().prec = 100 # use 100 decimals of precision is ~333 bits
Además, para eliminar la imprecisión que involucra decimales, podemos calcular
como
Esto elimina la fracción 1.0001.
Podemos mejorar la precisión nuevamente evitando los exponentes negativos (que implican una división). Observa que podemos deshacernos de los exponentes negativos invirtiendo el numerador y el denominador:
Mirando hacia el tick -2, en lugar de calcular Decimal(10001)**Decimal(-2/2) (o ) para el segundo tick negativo, lo simplificamos directamente a Decimal(10001)**Decimal(-1) para minimizar la introducción de operaciones de división donde no son necesarias.
Con ese cambio, ahora podemos reproducir de forma más precisa los valores precalculados de Uniswap V3. Los valores a continuación se multiplican por 2**128 para convertirlos en números de coma fija:
from decimal import *
getcontext().prec = 100
# tick -1
print(hex(int(Decimal(10000)**Decimal(1/2) * 2**128 / Decimal(10001)**Decimal(1/2))))
# estim: 0xfffcb933bd6fad37aa2d162d1a594001
# uniV3: 0xfffcb933bd6fad37aa2d162d1a594001
# tick -2
print(hex(int(Decimal(10000)**Decimal(1) * 2**128 / Decimal(10001)**Decimal(1))))
# estim: 0xfff97272373d413259a46990580e2139
# uniV3: 0xfff97272373d413259a46990580e213a
# tick -4
print(hex(int(Decimal(10000)**Decimal(2) * 2**128 / Decimal(10001)**Decimal(2))))
# estim: 0xfff2e50f5f656932ef12357cf3c7fdcb
# uniV3: 0xfff2e50f5f656932ef12357cf3c7fdcc
# tick -8
print(hex(int(Decimal(10000)**Decimal(4) * 2**128 / Decimal(10001)**Decimal(4))))
# estim: 0xffe5caca7e10e4e61c3624eaa0941ccf
# uniV3: 0xffe5caca7e10e4e61c3624eaa0941cd0
# tick -16
print(hex(int(Decimal(10000)**Decimal(8) * 2**128 / Decimal(10001)**Decimal(8))))
# estim: 0xffcb9843d60f6159c9db58835c926643
# univ3: 0xffcb9843d60f6159c9db58835c926644
Nota que nuestro cálculo de las constantes en el código anterior es inferior a los valores del código real por solo 1, lo que significa que Uniswap V3 está redondeando hacia arriba el decimal para obtener sus constantes.
En conclusión, cada uno de los “números mágicos” en getSqrtRatioAtTick() son los siguientes números representados como un número de coma fija de 128 bits redondeado hacia arriba (excepto el tick 1).
Parte 4/5: Calculando el recíproco con números Q128.128
La línea de código que se muestra a continuación calcula el recíproco si el tick original era positivo.

Ahora explicaremos por qué el código utiliza type(uint256).max en el numerador. Ten en cuenta que ratio es el precio como un número Q128.128.
Al dividir un número de coma fija por otro número de coma fija , necesitamos multiplicar el numerador por el factor de escala para evitar que los factores de escala se cancelen entre sí.
El valor para “1” en Q128.128 es 1 << 128 o . Para calcular 1 / ratio usando Q128.128, hacemos lo siguiente:
Necesitamos multiplicar el numerador por el factor de escala, el cual es . Esto nos da:
Sin embargo, el valor no puede codificarse en Solidity; el valor más grande que puede codificarse es o type(uint256).max.
Esto significa que los precios calculados para los ticks positivos serán ligeramente redondeados hacia abajo. Las implicaciones de esto se discuten al final.
Parte 5/5: convirtiendo ratio a sqrtPriceX96 redondeando hacia arriba
La última línea de código convierte ratio, que es un número Q128.128, a un número Q64.96 mientras redondea hacia arriba y devuelve el valor.
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
Detalles importantes que este artículo no cubrió
Hicimos las siguientes observaciones sobre el código en este artículo:
- Con excepción del tick -1, las constantes se redondean hacia arriba.
- El cálculo del recíproco para los ticks positivos redondea ligeramente hacia abajo porque el código aproxima a
type(uint256).max. - La conversión final de Q128.128 a Q64.96 redondea hacia arriba si la división de
ratiopor1 << 32no es exacta (observa el operador ternario en el fragmento de código anterior que suma 1 sirationo divide perfectamente por1 << 32).
Prueba empírica de precisión
Probar la función de Solidity se puede hacer simplemente copiando el código en un IDE.
Podemos crear una implementación de referencia en Python de la siguiente manera para obtener el valor correcto para
como
from decimal import *
getcontext().prec = 1000 # set the precision very high
# 2**96 * (1.0001)^(tick/2)
math.ceil(Decimal(2**96)*(Decimal(10001)/Decimal(10000))**(Decimal(tick)/2))
(Nota: también se puede usar una calculadora de precisión completa en línea).
Podemos comparar los resultados para los ticks extremos:
# tick 887272 (MAX_TICK)
solidity: 1461446703485210103287273052203988822378723970342
python : 1461446703485210103244672773810124308346321380903
# tick 0
solidity: 79228162514264337593543950336
python : 79228162514264337593543950336
# tick -887272 (MIN_TICK)
solidity: 4295128739
python : 4295128739
Si revisamos el MAX_TICK, vemos que el código de Solidity sobreestima el valor verdadero:
solidity: 14614467034852101032 87273052203988822378723970342
python : 14614467034852101032 44672773810124308346321380903
Si este error es grave o no depende de cómo la lógica posterior utilice esta función.
getSqrtRatioAtTick() se utiliza cuando los proveedores de liquidez añaden o retiran liquidez y cuando los traders realizan un swap que cruza un tick. Dado que todavía no hemos discutido ninguno de esos mecanismos en esta etapa de nuestro tutorial de Uniswap V3, postergaremos un análisis de errores de esta función.
Resumen
Para calcular el precio de la raíz cuadrada en un tick, getSqrtRatioAtTick() primero calcula el valor absoluto del tick (), y luego itera sobre los 20 bits más significativos de para calcular . Si el tick original era positivo, vuelve a calcular el precio como . Finalmente, convierte la representación de coma fija de 128 bits en una representación de 96 bits y devuelve eso como el precio.