// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../interfaces/ISwapRouter.sol"; import "../RouteTypesV2.sol"; import "../interfaces/IRouteExecutorAdapter.sol"; contract UniswapV3RouteExecutorAdapter is IRouteExecutorAdapter { using SafeERC20 for IERC20; function validate( RouteTypesV2.RouteLeg calldata leg ) external pure override returns (bool ok, string memory reason) { if (leg.provider != RouteTypesV2.Provider.UniswapV3) { return (false, "UniswapV3RouteExecutorAdapter: invalid provider"); } if (leg.target == address(0)) { return (false, "UniswapV3RouteExecutorAdapter: zero target"); } if (leg.providerData.length == 0) { return (false, "UniswapV3RouteExecutorAdapter: missing providerData"); } return (true, ""); } function quote( RouteTypesV2.RouteLeg calldata leg, uint256 amountIn ) external view override returns (uint256 amountOut, uint256 gasEstimate) { (bytes memory path, uint24 fee, address quoter, bool usePath) = abi.decode(leg.providerData, (bytes, uint24, address, bool)); if (quoter != address(0)) { bytes memory data; bool ok; if (usePath) { (ok, data) = quoter.staticcall( abi.encodeWithSignature("quoteExactInput(bytes,uint256)", path, amountIn) ); } else { (ok, data) = quoter.staticcall( abi.encodeWithSignature( "quoteExactInputSingle(address,address,uint24,uint256,uint160)", leg.tokenIn, leg.tokenOut, fee, amountIn, uint160(0) ) ); } if (ok && data.length >= 32) { amountOut = abi.decode(data, (uint256)); } } gasEstimate = usePath ? 220000 : 175000; } function execute( RouteTypesV2.RouteLeg calldata leg, uint256 amountIn ) external override returns (uint256 amountOut) { (bytes memory path, uint24 fee,, bool usePath) = abi.decode(leg.providerData, (bytes, uint24, address, bool)); IERC20(leg.tokenIn).forceApprove(leg.target, 0); IERC20(leg.tokenIn).forceApprove(leg.target, amountIn); if (usePath) { amountOut = ISwapRouter(leg.target).exactInput( ISwapRouter.ExactInputParams({ path: path, recipient: msg.sender, deadline: block.timestamp + 300, amountIn: amountIn, amountOutMinimum: leg.minAmountOut }) ); } else { amountOut = ISwapRouter(leg.target).exactInputSingle( ISwapRouter.ExactInputSingleParams({ tokenIn: leg.tokenIn, tokenOut: leg.tokenOut, fee: fee, recipient: msg.sender, deadline: block.timestamp + 300, amountIn: amountIn, amountOutMinimum: leg.minAmountOut, sqrtPriceLimitX96: 0 }) ); } } }