Los contratos Router proporcionan un contrato inteligente orientado al usuario para
- acuñar y quemar LP tokens de forma segura (añadir y retirar liquidez)
- intercambiar tokens del par de forma segura
- Añaden la capacidad de intercambiar Ether al integrarse con el contrato ERC20 de wrapped Ether (WETH).
- Añaden las comprobaciones de seguridad relacionadas con el slippage que se omitieron en el contrato principal (core).
- Añaden soporte para tokens con comisión por transferencia (fee on transfer tokens).
Router02 es todo lo que hace Router01 pero con soporte añadido para tokens con comisión por transferencia
Cuando abrimos por primera vez la carpeta de contratos en el repositorio periphery, vemos tres contratos

Router02 es Router01 con funciones adicionales para tokens con comisión por transferencia. Cuando observamos la interfaz de Router02, podemos ver que hereda de Router01 (recuadro rojo) (lo que significa que implementa todas sus funciones), y tiene las siguientes funciones adicionales, que son todas para realizar operaciones con soporte para tokens con comisión por transferencia (resaltado en amarillo).

swapExactTokensForTokens y swapTokensForExactTokens
Empecemos con las funciones del Router para el intercambio (swap) de tokens. Hay dos funciones que logran esto (resaltadas en verde).

La diferencia en los nombres de estas funciones es la siguiente:
- En
swapExactTokensForTokens, que “el primer token sea exacto” significa que la cantidad del token de entrada (input token) que estás intercambiando es una cantidad fija. - En
swapTokensForExactTokens, que “el segundo token sea exacto” indica que la cantidad del token de salida (output token) que deseas recibir es una cantidad fija.
Si un usuario solo está intercambiando dos tokens, entonces proporcionará a estas funciones un array address[] calldata path (resaltado en azul) [address(tokenIn), address(tokenOut)]. Si está saltando a través de varios pools, especificará [address(tokenIn), address(intermediateToken), …, address(tokenOut)].
swapExactTokensForTokens
En el caso de swapExactTokensForTokens, el usuario especifica exactamente qué cantidad del primer token va a depositar y la cantidad mínima del token de salida que aceptará.
Por ejemplo, supongamos que queremos intercambiar 25 token0 por 50 token1. Si este es el precio exacto en el estado actual, esto no deja margen de tolerancia para que el precio cambie antes de que nuestra transacción se confirme, lo que provocaría un revert. Por lo tanto, en su lugar especificamos que el mínimo de salida sea 49.5 token1, dejando implícitamente una tolerancia del 1%.
swapTokensForExactTokens
En este caso especificamos que queremos exactamente 50 token1, pero estamos dispuestos a intercambiar hasta 25.5 token0 para obtenerlo.
¿Qué función de swap usar?
La mayoría de los usuarios que utilizan una EOA probablemente optarían por usar la función de entrada exacta, porque necesitan tener un paso de aprobación (approval), y el intercambio fallaría si necesitaran introducir más de lo que aprobaron. Al tener una entrada exacta, pueden aprobar la cantidad exacta. Sin embargo, los contratos inteligentes que se integran con Uniswap pueden tener requisitos más complejos, por lo que el router les da la opción de usar ambas.
Cómo funciona el swap
Cuando la entrada es exacta (swapExactTokensForTokens), la función predice la salida esperada a lo largo de un único swap o de una cadena de swaps. Si la salida resultante está por debajo de la cantidad especificada por el usuario, la función hace un revert. Viceversa para la salida exacta: calcula la entrada requerida y hace un revert si está por encima del umbral especificado por el usuario.
Luego, ambas funciones transferirán los tokens del usuario al par (recuerda, Uniswap V2 Pair requiere que los tokens se envíen al contrato antes de que se llame a la función swap() del contrato del par). Finalmente, ambas llaman a la función interna _swap() que se analiza a continuación.

La función _swap()
A nivel interno, todas las funciones públicas que contienen swap() en su nombre llaman a la función interna _swap() que se muestra a continuación.
Recuerda que la firma de la función swap principal especifica el amountOut para ambos tokens y el amountIn está implícito en la cantidad que se transfirió antes de que se llamara a la función.

_addLiquidity
¿Recuerdas las comprobaciones de seguridad al añadir liquidez? Específicamente, queremos asegurarnos de depositar los dos tokens exactamente en la misma proporción (ratio) que tiene el par actualmente; de lo contrario, la cantidad de LP tokens que acuñamos es la peor de las dos proporciones entre lo que proporcionamos y los balances del par. Sin embargo, la proporción podría cambiar entre el momento en que el proveedor de liquidez intenta añadir liquidez y el momento en que se confirma la transacción.
Para protegerse contra esto, un proveedor de liquidez debe proporcionar (como parámetro) el balance mínimo que busca depositar para token0 y token1 (UniswapV2 los llama amountAMin y amountBMin). Luego, transfieren una cantidad superior a esos mínimos (UniswapV2 los llama amountADesired y amountBDesired). Si la proporción del par ha cambiado de tal manera que ya no se respetan los mínimos, entonces la transacción hace un revert.
_addLiquidity tomará amountADesired y calculará la cantidad correcta de tokenB que respetará la proporción. Si esta cantidad es superior a amountBDesired (la cantidad de B que envió el proveedor de liquidez), entonces comenzará con amountBDesired y calculará la cantidad óptima de B. La lógica se muestra a continuación. Ten en cuenta que añadir liquidez puede crear un nuevo contrato de par si no existe ya.

Por ejemplo, supongamos que el balance actual del par es 100 token0 y 300 token1. Queremos añadir 20 y 60 de token0 y token1 respectivamente, pero la proporción del par podría cambiar. Así que en su lugar aprobamos al router para 21 token0 y 63 token1 al mismo tiempo que decimos que lo mínimo que queremos depositar es 20 y 60. Si la proporción cambia de tal manera que la cantidad óptima de token0 a depositar es 19.9, entonces la transacción hace un revert.
Recuerda que dijimos que quote no debe usarse como oráculo, y eso sigue siendo cierto. Sin embargo, para los propósitos de añadir liquidez, no estamos interesados en el promedio de los precios anteriores, sino en el precio actual (la proporción del pool) de ahora mismo, porque el proveedor de liquidez debe respetarlo.
addLiquidity() y addLiquidityEth()
Estas funciones deberían explicarse por sí mismas. Primero calculan la proporción óptima utilizando la función _addLiquidity vista anteriormente, luego transfieren los activos al par, y finalmente llaman a mint en el par. La única diferencia es que la función addLiquidityEth envolverá (wrap) el Ether en WETH primero.

Retirar liquidez
Retirar liquidez llama a burn pero utiliza los parámetros amountAMin y amountBMin (resaltados en rojo) como comprobaciones de seguridad para garantizar que el proveedor de liquidez recupere la cantidad de tokens que espera. Si la proporción de tokens cambia drásticamente antes de que los tokens de liquidez sean quemados, entonces el usuario que está quemando los tokens no recuperará la cantidad de token A o B que espera.
La función removeLiquidityEth llama a removeLiquidity (resaltado en verde) pero establece el router como el receptor de los tokens. El token ERC20 normal se transfiere luego al proveedor de liquidez, y el WETH se desenvuelve (unwrap) a ETH, enviándose de vuelta al proveedor de liquidez.

removeLiquidityWithPermit() y removeLiquidityETHWithPermit()
En la línea 109 del archivo anterior con el comentario gris send liquidity to pair, este paso asume que el contrato del par tiene aprobación para transferir los LP tokens desde el proveedor de liquidez para quemarlos. Esto significa que quemar los LP tokens requiere aprobar al par primero. Este paso se puede omitir con permit(), ya que los LP tokens de Uniswap V2 son un ERC20 Permit Token. La función removeLiquidityWithPermit() recibe una firma para aprobar y quemar en una sola transacción. Si uno de los tokens es WETH, el proveedor de liquidez usaría removeLiquidityETHWithPermit().
Router02: dando soporte a tokens con comisión por transferencia
Para manejar tokens con comisión por transferencia, el router no puede realizar directamente sus cálculos sobre argumentos como amountIn() (para intercambios) o liquidity() (para retirar liquidez). Añadir liquidez no se ve afectado por los tokens con comisión por transferencia porque al usuario solo se le acredita lo que realmente transfiere al par.


Wrappers alrededor de UniswapV2Library
El resto de las funciones en la librería Router son wrappers (envoltorios) alrededor de las funciones de la UniswapV2Library como se muestra a continuación.
function quote(uint amountA, uint reserveA, uint reserveB) public pure override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure override returns (uint amountOut) {
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) public pure override returns (uint amountIn) {
return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) {
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) {
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
El parámetro deadline
En los Routers de Uniswap V2, todas las funciones públicas tienen un parámetro deadline (fecha límite). Cuando realizas una operación en Uniswap ahora mismo, implica que quieres operar a los precios actuales.
Al escribir un contrato inteligente que se integre con Uniswap, no establezcas el deadline como block.timestamp ni como block.timestamp más una constante.
Tu contrato inteligente necesita asegurar por separado que la transacción enviada por el usuario no sea demasiado antigua. Esto significa que tu propio contrato debe aceptar un parámetro deadline del usuario y enviarlo a Uniswap, o hacer un revert si block.timestamp > deadline.
Cómo explotar transacciones antiguas
Un constructor de bloques malicioso (block builder) puede “retener” transacciones de swap y ejecutarlas mucho más tarde, cuando dichas transacciones sean útiles para manipular el precio, o para descargar (dump) tokens sobre el usuario a un precio desfavorable. Un parámetro deadline limita la ventana de tiempo en la que un atacante puede llevar a cabo dicho exploit. Un deadline debe estar lo suficientemente lejos en el futuro para que haya tiempo de ejecutar la transacción incluso durante congestión, pero no más de eso. Por lo general, esto significa que el deadline debería ser del orden de unos minutos a partir de la firma de la transacción.
Sin embargo, si un contrato inteligente no incorpora un deadline o hace que el parámetro sea inútil ignorándolo y reenviando el block.timestamp actual a Uniswap, entonces el usuario no estará protegido.
Nunca establezcas amountMin en cero ni amountMax en type(uint).max
Otro error muy común es establecer amountMin en cero o amountMax en un valor muy alto. Esto destruye la protección contra el slippage de precios y los ataques sándwich (sandwich attacks).
Conclusión
Los contratos Router proporcionan un mecanismo orientado al usuario para intercambiar tokens con protección contra el slippage, posiblemente a través de múltiples pools, y añaden soporte para operar con ETH y tokens con comisión por transferencia (en Router02). El depósito de liquidez no necesita tener en cuenta los tokens con comisión por transferencia porque Uniswap solo acredita lo que realmente se transfirió al pool.
Las funciones de depósito de liquidez aseguran que el usuario solo deposite en la proporción exacta del pool. Retirar liquidez puede ser tan simple como transferir LP tokens al router y luego quemarlos, o puede incluir desenvolver (unwrapping) WETH y retirar tokens con comisión por transferencia.
Además, se incluye soporte para aprobaciones sin gas a través de ERC20 Permit.
Un contrato inteligente que se integre con Uniswap no debe deshabilitar la protección contra swaps retrasados y el slippage de precios.
Publicado originalmente el 10 de noviembre de 2023