// SPDX-License-Identifier: MIT pragma solidity 0.8.26; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {PathKey} from "./libraries/PathKey.sol"; import {CalldataDecoder} from "./libraries/CalldataDecoder.sol"; import {IV4Router} from "./interfaces/IV4Router.sol"; import {BaseActionsRouter} from "./base/BaseActionsRouter.sol"; import {DeltaResolver} from "./base/DeltaResolver.sol"; import {Actions} from "./libraries/Actions.sol"; import {ActionConstants} from "./libraries/ActionConstants.sol"; import {BipsLibrary} from "./libraries/BipsLibrary.sol"; import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; /// @title UniswapV4Router /// @notice Abstract contract that contains all internal logic needed for routing through Uniswap v4 pools /// @dev the entry point to executing actions in this contract is calling `BaseActionsRouter._executeActions` /// An inheriting contract should call _executeActions at the point that they wish actions to be executed abstract contract V4Router is IV4Router, BaseActionsRouter, DeltaResolver { using SafeCast for *; using CalldataDecoder for bytes; using BipsLibrary for uint256; constructor(IPoolManager _poolManager) BaseActionsRouter(_poolManager) {} function _handleAction(uint256 action, bytes calldata params) internal override { // swap actions and payment actions in different blocks for gas efficiency if (action < Actions.SETTLE) { if (action == Actions.SWAP_EXACT_IN) { IV4Router.ExactInputParams calldata swapParams = params.decodeSwapExactInParams(); _swapExactInput(swapParams); return; } else if (action == Actions.SWAP_EXACT_IN_SINGLE) { IV4Router.ExactInputSingleParams calldata swapParams = params.decodeSwapExactInSingleParams(); _swapExactInputSingle(swapParams); return; } else if (action == Actions.SWAP_EXACT_OUT) { IV4Router.ExactOutputParams calldata swapParams = params.decodeSwapExactOutParams(); _swapExactOutput(swapParams); return; } else if (action == Actions.SWAP_EXACT_OUT_SINGLE) { IV4Router.ExactOutputSingleParams calldata swapParams = params.decodeSwapExactOutSingleParams(); _swapExactOutputSingle(swapParams); return; } } else { if (action == Actions.SETTLE_ALL) { (Currency currency, uint256 maxAmount) = params.decodeCurrencyAndUint256(); uint256 amount = _getFullDebt(currency); if (amount > maxAmount) revert V4TooMuchRequested(maxAmount, amount); _settle(currency, msgSender(), amount); return; } else if (action == Actions.TAKE_ALL) { (Currency currency, uint256 minAmount) = params.decodeCurrencyAndUint256(); uint256 amount = _getFullCredit(currency); if (amount < minAmount) revert V4TooLittleReceived(minAmount, amount); _take(currency, msgSender(), amount); return; } else if (action == Actions.SETTLE) { (Currency currency, uint256 amount, bool payerIsUser) = params.decodeCurrencyUint256AndBool(); _settle(currency, _mapPayer(payerIsUser), _mapSettleAmount(amount, currency)); return; } else if (action == Actions.TAKE) { (Currency currency, address recipient, uint256 amount) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _mapTakeAmount(amount, currency)); return; } else if (action == Actions.TAKE_PORTION) { (Currency currency, address recipient, uint256 bips) = params.decodeCurrencyAddressAndUint256(); _take(currency, _mapRecipient(recipient), _getFullCredit(currency).calculatePortion(bips)); return; } } revert UnsupportedAction(action); } function _swapExactInputSingle(IV4Router.ExactInputSingleParams calldata params) private { uint128 amountIn = params.amountIn; if (amountIn == ActionConstants.OPEN_DELTA) { amountIn = _getFullCredit(params.zeroForOne ? params.poolKey.currency0 : params.poolKey.currency1).toUint128(); } uint128 amountOut = _swap(params.poolKey, params.zeroForOne, -int256(uint256(amountIn)), params.hookData).toUint128(); if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(params.amountOutMinimum, amountOut); } function _swapExactInput(IV4Router.ExactInputParams calldata params) private { unchecked { // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountOut; Currency currencyIn = params.currencyIn; uint128 amountIn = params.amountIn; if (amountIn == ActionConstants.OPEN_DELTA) amountIn = _getFullCredit(currencyIn).toUint128(); PathKey calldata pathKey; for (uint256 i = 0; i < pathLength; i++) { pathKey = params.path[i]; (PoolKey memory poolKey, bool zeroForOne) = pathKey.getPoolAndSwapDirection(currencyIn); // The output delta will always be positive, except for when interacting with certain hook pools amountOut = _swap(poolKey, zeroForOne, -int256(uint256(amountIn)), pathKey.hookData).toUint128(); amountIn = amountOut; currencyIn = pathKey.intermediateCurrency; } if (amountOut < params.amountOutMinimum) revert V4TooLittleReceived(params.amountOutMinimum, amountOut); } } function _swapExactOutputSingle(IV4Router.ExactOutputSingleParams calldata params) private { uint128 amountOut = params.amountOut; if (amountOut == ActionConstants.OPEN_DELTA) { amountOut = _getFullDebt(params.zeroForOne ? params.poolKey.currency1 : params.poolKey.currency0).toUint128(); } uint128 amountIn = ( uint256(-int256(_swap(params.poolKey, params.zeroForOne, int256(uint256(amountOut)), params.hookData))) ).toUint128(); if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn); } function _swapExactOutput(IV4Router.ExactOutputParams calldata params) private { unchecked { // Caching for gas savings uint256 pathLength = params.path.length; uint128 amountIn; uint128 amountOut = params.amountOut; Currency currencyOut = params.currencyOut; PathKey calldata pathKey; if (amountOut == ActionConstants.OPEN_DELTA) { amountOut = _getFullDebt(currencyOut).toUint128(); } for (uint256 i = pathLength; i > 0; i--) { pathKey = params.path[i - 1]; (PoolKey memory poolKey, bool oneForZero) = pathKey.getPoolAndSwapDirection(currencyOut); // The output delta will always be negative, except for when interacting with certain hook pools amountIn = (uint256(-int256(_swap(poolKey, !oneForZero, int256(uint256(amountOut)), pathKey.hookData)))) .toUint128(); amountOut = amountIn; currencyOut = pathKey.intermediateCurrency; } if (amountIn > params.amountInMaximum) revert V4TooMuchRequested(params.amountInMaximum, amountIn); } } function _swap(PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, bytes calldata hookData) private returns (int128 reciprocalAmount) { // for protection of exactOut swaps, sqrtPriceLimit is not exposed as a feature in this contract unchecked { BalanceDelta delta = poolManager.swap( poolKey, SwapParams( zeroForOne, amountSpecified, zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1 ), hookData ); reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); } } }