// File: contracts/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    event Approval(address, address, uint256);
    event Transfer(address, address, uint256);

    function name() external view returns (string memory);

    function decimals() external view returns (uint8);

    function transferFrom(address, address, uint256) external returns (bool);

    function allowance(address, address) external view returns (uint256);

    function approve(address, uint256) external returns (bool);

    function transfer(address, uint256) external returns (bool);

    function balanceOf(address) external view returns (uint256);
}

// File: contracts/interfaces/IWETH.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IERC20.sol";

interface IWETH is IERC20 {
    function withdraw(uint256 amount) external;

    function deposit() external payable;
}

// File: contracts/lib/CallbackValidation.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "./PoolAddress.sol";

/// @notice Provides validation for callbacks from Uniswap V3 Pools
library CallbackValidation {
    /// @notice Returns the address of a valid Uniswap V3 Pool
    /// @param factory The contract address of the Uniswap V3 factory
    /// @param tokenA The contract address of either token0 or token1
    /// @param tokenB The contract address of the other token
    /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
    /// @return pool The V3 pool contract address
    function verifyCallback(
        address factory,
        address tokenA,
        address tokenB,
        uint24 fee
    ) internal view returns (address pool) {
        return
            verifyCallback(
                factory,
                PoolAddress.getPoolKey(tokenA, tokenB, fee)
            );
    }

    /// @notice Returns the address of a valid Uniswap V3 Pool
    /// @param factory The contract address of the Uniswap V3 factory
    /// @param poolKey The identifying key of the V3 pool
    /// @return pool The V3 pool contract address
    function verifyCallback(
        address factory,
        PoolAddress.PoolKey memory poolKey
    ) internal view returns (address pool) {
        pool = PoolAddress.computeAddress(factory, poolKey);
        require(msg.sender == address(pool));
    }
}

// File: contracts/lib/PoolAddress.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Provides functions for deriving a pool address from the factory, tokens, and the fee
library PoolAddress {
    bytes32 internal constant POOL_INIT_CODE_HASH =
        0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;

    /// @notice The identifying key of the pool
    struct PoolKey {
        address token0;
        address token1;
        uint24 fee;
    }

    /// @notice Returns PoolKey: the ordered tokens with the matched fee levels
    /// @param tokenA The first token of a pool, unsorted
    /// @param tokenB The second token of a pool, unsorted
    /// @param fee The fee level of the pool
    /// @return Poolkey The pool details with ordered token0 and token1 assignments
    function getPoolKey(
        address tokenA,
        address tokenB,
        uint24 fee
    ) internal pure returns (PoolKey memory) {
        if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
        return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
    }

    /// @notice Deterministically computes the pool address given the factory and PoolKey
    /// @param factory The Uniswap V3 factory contract address
    /// @param key The PoolKey
    /// @return pool The contract address of the V3 pool
    function computeAddress(
        address factory,
        PoolKey memory key
    ) internal pure returns (address pool) {
        require(key.token0 < key.token1);
        pool = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            factory,
                            keccak256(
                                abi.encode(key.token0, key.token1, key.fee)
                            ),
                            POOL_INIT_CODE_HASH
                        )
                    )
                )
            )
        );
    }
}

// File: contracts/lib/SafeCast.sol



// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
    /// @notice Cast a uint256 to a uint160, revert on overflow
    /// @param y The uint256 to be downcasted
    /// @return z The downcasted integer, now type uint160
    function toUint160(uint256 y) internal pure returns (uint160 z) {
        require((z = uint160(y)) == y);
    }

    /// @notice Cast a int256 to a int128, revert on overflow or underflow
    /// @param y The int256 to be downcasted
    /// @return z The downcasted integer, now type int128
    function toInt128(int256 y) internal pure returns (int128 z) {
        require((z = int128(y)) == y);
    }

    /// @notice Cast a uint256 to a int256, revert on overflow
    /// @param y The uint256 to be casted
    /// @return z The casted integer, now type int256
    function toInt256(uint256 y) internal pure returns (int256 z) {
        require(y < 2 ** 255);
        z = int256(y);
    }
}

// File: contracts/lib/SafeTransfer.sol



// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import "../interfaces/IERC20.sol";

library SafeTransfer {
    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool s, ) = address(token).call(
            abi.encodeWithSelector(
                IERC20.transferFrom.selector,
                from,
                to,
                value
            )
        );
        require(s, "safeTransferFrom failed");
    }

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        (bool s, ) = address(token).call(
            abi.encodeWithSelector(IERC20.transfer.selector, to, value)
        );
        require(s, "safeTransfer failed");
    }

    function safeApprove(IERC20 token, address to, uint256 value) internal {
        (bool s, ) = address(token).call(
            abi.encodeWithSelector(IERC20.approve.selector, to, value)
        );
        require(s, "safeApprove failed");
    }

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }
}

// File: contracts/MeowlRouterV3.sol



// SPDX-License-Identifier: AGPL-3.0-only

pragma solidity ^0.8.19;

import {IERC20} from "./interfaces/IERC20.sol";
import {SafeTransfer} from "./lib/SafeTransfer.sol";
import {IWETH} from "./interfaces/IWETH.sol";
import {CallbackValidation, PoolAddress} from "./lib/CallbackValidation.sol";
import {SafeCast} from "./lib/SafeCast.sol";

interface IUniswapV3Pool {
    function swap(
        address recipient,
        bool zeroForOne,
        int256 amountSpecified,
        uint160 sqrtPriceLimitX96,
        bytes calldata data
    ) external returns (int256 amount0, int256 amount1);
}

// &&&&&&&&&%%%&%#(((/,,,**,,,**,,,*******/*,,/%%%%%%#.,%%%%%%%%%%%%%%%% ./(#%%%%/. #%%/***,,*,*,,,,**,
// %&&&&&&&&&%%%/((((*,,,,,,,**/.,,,*****/%%#.      ,**,,%%%%%%%%%%%%%%%%%%#.     #%%%%%#*,,,,,,*,,,,,*
// ,.,,*(%&&%%/*/((((,,,,,,,,**((%%,,**,  .#%%/*%%%%%%%,*%%%%%%%%%%%%%%%%#%    ..  #%%%%%,*,*,,**,*,**,
// ..,.,,.,,.,.*((((,,,,,,,****(#%%%%%,*%%%%%. .(%%%%%#,#%%%%%%%%%%%%%%%%,  &@# . * (%%%%***,*,,*,*,,,,
// .,.,,..,.,.,/(((,,,,,*****/#%%%%%%%%%%%%%,         ,%%%%%%%%%%%%%%%%%% .    .  / *#%%#***,*,*,**,,**
// ,,.,,,,.,,.,/((,,,****/#%%%%%%%%%%%%%%,     , ....   #%%%%%%%%&&%%%%%, /  ...  ,(%%%%/****,****,*#%#
// .,,.,.,.,.,,/(##%%%%%%%%%%%%%%%%%%%%% , /, .......    %%%%%%%%&&%%%%%% /*  .  , @%%%%/*******/(##%##
// ,..,,..,.,,%%%%%%%%%%%%%%%%%%%%%%%%%(#@..(/ .....  /, %%%%%%%%&&&%%%%%(.*////  @%%%%%/****(#%%%%%%#/
// ,,,..,.,,,.%%%%%%%%%%%%%%%%%%%%%%%%%%%%@../(*     @ .,%%%%%%%%%%%%%%%%%%%%/ ./(%%%%%/*/(#%%%%%%%#**/
// .,,,,,,,,,,*,#%%%%%%%%%%%%%%%%%%%%%%%%%%&@, ,*/(/,. #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#(#%%%%%%%#//*///
// ,,.,,.,,,,,,,,,,#%%%%%%%%%%%%%%%%%%%%%%%%%%(    .#%%%%%%%%%%%%#%&&&%%%%%%%%%%%%%%%*%%%#%%%#(**(**//*
// .,,,,..,*..,.,,,,...*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%,(%#%%%(*/*///*//#%

contract MeowlRouterV3 {
    using SafeTransfer for IERC20;
    using SafeTransfer for IWETH;
    using SafeCast for uint256;

    address internal immutable feeAddress;

    address internal constant WETH9 =
        0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    address internal constant FACTORY =
        0x1F98431c8aD98523631AE4a59f267346ea31F984;

    uint32 internal constant FEE_NUMERATOR = 875;
    uint32 internal constant FEE_DENOMINATOR = 100000;

    /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)
    uint160 internal constant MIN_SQRT_RATIO = 4295128739;
    /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)
    uint160 internal constant MAX_SQRT_RATIO =
        1461446703485210103287273052203988822378723970342;

    event Swap(
        address tokenIn,
        address tokenOut,
        uint actualAmountIn,
        uint actualAmountOut,
        uint feeAmount
    );

    constructor() {
        feeAddress = msg.sender;
    }

    struct SwapCallbackData {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address payer;
    }

    receive() external payable {}

    // *** Receive profits from contract *** //
    function recover(address token) public {
        require(msg.sender == feeAddress, "shoo");
        if (token == address(0)) {
            SafeTransfer.safeTransferETH(msg.sender, address(this).balance);
            return;
        } else {
            IERC20(token).safeTransfer(
                msg.sender,
                IERC20(token).balanceOf(address(this))
            );
        }
    }

    /*
        Payload structure
        - tokenIn: address       - Address of the token you're swapping
        - tokenOut: address      - Address of the token you want
        - fee: uint24            - Pool Fee
        - minAmountOut: uint128  - Min amount out
        - amountIn?: uint128     - Amount you're giving via swap
    */

    fallback() external payable {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        uint minAmountOut;
        address receiver;
        uint feeAmount;

        assembly {
            // bytes20
            tokenIn := shr(96, calldataload(0))
            // bytes20
            tokenOut := shr(96, calldataload(20))
            // bytes20
            fee := shr(232, calldataload(40))
            // uint128
            minAmountOut := shr(128, calldataload(43))
        }

        uint actualAmountIn;

        if (address(tokenIn) == WETH9 && msg.value > 0) {
            feeAmount = (msg.value * FEE_NUMERATOR) / FEE_DENOMINATOR;
            actualAmountIn = msg.value - feeAmount;
            receiver = msg.sender;
        } else {
            uint amountIn;
            assembly {
                // uint128
                amountIn := shr(128, calldataload(59))
            }
            uint balanceTokenInBefore = IERC20(tokenIn).balanceOf(
                address(this)
            );
            IERC20(tokenIn).safeTransferFrom(
                msg.sender,
                address(this),
                amountIn
            );
            // support fee on transfer tokens
            actualAmountIn =
                IERC20(tokenIn).balanceOf(address(this)) -
                balanceTokenInBefore;
            receiver = address(this);
        }

        bytes memory data = abi.encode(
            SwapCallbackData({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: fee,
                payer: receiver
            })
        );

        bool zeroForOne = tokenIn < tokenOut;

        uint balBefore = IERC20(tokenOut).balanceOf(address(receiver));

        getPool(tokenIn, tokenOut, fee).swap(
            receiver,
            zeroForOne,
            actualAmountIn.toInt256(),
            (zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1),
            data
        );

        // support fee on transfer tokens
        uint actualAmountOut = IERC20(tokenOut).balanceOf(address(receiver)) -
            balBefore;

        require(actualAmountOut >= minAmountOut, "Too little received");

        if (receiver == address(this)) {
            // Only support native ETH out because we can't differentiate
            if (tokenOut == WETH9) {
                IWETH(WETH9).withdraw(actualAmountOut);

                feeAmount = (actualAmountOut * FEE_NUMERATOR) / FEE_DENOMINATOR;

                SafeTransfer.safeTransferETH(
                    msg.sender,
                    actualAmountOut - feeAmount
                );
            } else {
                feeAmount = (actualAmountOut * FEE_NUMERATOR) / FEE_DENOMINATOR;

                IERC20(tokenOut).safeTransfer(
                    msg.sender,
                    actualAmountOut - feeAmount
                );
            }
        }

        emit Swap(
            tokenIn,
            tokenOut,
            actualAmountIn,
            actualAmountOut,
            feeAmount
        );
    }

    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata _data
    ) external {
        require(amount0Delta > 0 || amount1Delta > 0); // swaps entirely within 0-liquidity regions are not supported

        SwapCallbackData memory data = abi.decode(_data, (SwapCallbackData));

        CallbackValidation.verifyCallback(
            FACTORY,
            data.tokenIn,
            data.tokenOut,
            data.fee
        );

        pay(
            data.tokenIn,
            data.payer,
            msg.sender,
            amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta)
        );
    }

    function pay(
        address token,
        address payer,
        address recipient,
        uint256 value
    ) internal {
        if (token == WETH9 && address(this).balance >= value) {
            // pay with WETH9
            IWETH(WETH9).deposit{value: value}(); // wrap only what is needed to pay
            IWETH(WETH9).transfer(recipient, value);
        } else if (payer == address(this)) {
            IERC20(token).safeTransfer(recipient, value);
        } else {
            // pull payment
            IERC20(token).safeTransferFrom(payer, recipient, value);
        }
    }

    function getPool(
        address tokenA,
        address tokenB,
        uint24 fee
    ) internal pure returns (IUniswapV3Pool) {
        return
            IUniswapV3Pool(
                PoolAddress.computeAddress(
                    FACTORY,
                    PoolAddress.getPoolKey(tokenA, tokenB, fee)
                )
            );
    }
}