// Contract: Kanpai
// Target function: function allocate(address to, uint256 amount) external onlyOwner
(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}

pragma abicoder v2;

/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
    /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}

/// @title ERC20 rewards
/// @author @niera26
/// @notice buy and sell tax on this token with rewardToken as rewards
/// @notice source: https://github.com/niera26/erc20-rewards-contracts
contract ERC20Rewards is Ownable, ERC20, ERC20Burnable, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeERC20 for IERC20Metadata;
    using EnumerableSet for EnumerableSet.AddressSet;

    // =========================================================================
    // dependencies.
    // =========================================================================

    ISwapRouter public constant swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
    IUniswapV2Router02 public constant router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    IERC20Metadata public constant rewardToken = IERC20Metadata(0x6De037ef9aD2725EB40118Bb1702EBb27e4Aeb24); // RNDR

    // the amm pair where the tranfers from/to are taxed.
    // (populated with WETH/this token pair address in the constructor).
    IUniswapV2Pair public immutable pair;

    // =========================================================================
    // rewards management.
    // =========================================================================

    struct Share {
        uint256 amount; // recorded balance after last transfer.
        uint256 earned; // amount of tokens earned but not claimed yet.
        uint256 tokenPerShareLast; // token per share value of the last earn occurrence.
    }

    // numerator multiplier so tokenPerShare does not get rounded to 0.
    uint256 private constant PRECISION = 1e18;

    // scale factor so reward token scales to 18 decimals.
    uint256 private immutable SCALE_FACTOR;

    // total shares of this token.
    // (different from total supply because of addresses excluded from rewards).
    uint256 public totalShares;

    // shareholders record.
    // (addresses included to rewards are updated every time they send/receive tokens).
    mapping(address => Share) private shareholders;

    // the accumulated amount of reward token per share.
    uint256 private tokenPerShare;

    // total amount of reward tokens ever claimed.
    uint256 public totalRewardClaimed;

    // total amount of reward tokens ever distributed.
    uint256 public totalRewardDistributed;

    // contract addresses that opted in for rewards.
    mapping(address => bool) public isOptin;

    // =========================================================================
    // reward donations.
    // =========================================================================

    // the number of reward tokens to emit per block.
    uint256 public rewardTokenPerBlock;

    // the amount of reward tokens emitted on last emitting block update.
    uint256 public emittedRewardsCache;

    // last block reward tokens has been emitted.
    uint256 public lastEmittingBlock;

    // =========================================================================
    // operator address.
    // =========================================================================

    // the operator address receive marketing tax and can set non critical
    // settings. Allows to renounce ownership so critical settings can't be
    // updated anymore. It is the same as the owner by default and receive the
    // marketing tax. It can:
    // - update itself.
    // - remove max wallet limits.
    // - set uniswap V3 pool fee setting.
    // - set the reward token per block rate.
    address public operator;

    // =========================================================================
    // anti-bot and limitations.
    // =========================================================================

    EnumerableSet.AddressSet private blacklist;

    uint256 public maxWallet = type(uint256).max; // set to 1% in initialize
    uint256 public startBlock = 0;
    uint8 public deadBlocks = 1;

    // =========================================================================
    // pool options.
    // =========================================================================

    uint24 public poolFee = 10000; // works for RNDR

    // =========================================================================
    // fees.
    // =========================================================================

    uint24 public constant maxSwapFee = 3000;
    uint24 public constant maxMarketingFee = 10000;
    uint24 public constant feeDenominator = 10000;

    uint24 public buyFee = 2400;
    uint24 public sellFee = 2400;
    uint24 public marketingFee = 10000;

    // =========================================================================
    // events.
    // =========================================================================

    event OptIn(address addr);
    event OptOut(address addr);
    event AddToBlacklist(address addr);
    event RemoveFromBlacklist(address addr);
    event Claim(address indexed addr, address indexed to, uint256 amount);
    event Distribute(address indexed addr, uint256 amount);
    event Sweep(address indexed addr, address indexed token, uint256 amount);

    // =========================================================================
    // modifiers.
    // =========================================================================

    modifier onlyOperator() {
        require(msg.sender == operator, "!operator");
        _;
    }

    // =========================================================================
    // constructor.
    // =========================================================================

    constructor(string memory name, string memory symbol, uint256 _totalSupply)
        Ownable(msg.sender)
        ERC20(name, symbol)
    {
        // operator is deployer by default.
        operator = msg.sender;

        // set the reward token scale factor.
        uint8 rewardTokenDecimals = rewardToken.decimals();

        require(rewardTokenDecimals <= 18, "reward token decimals must be <= 18");

        SCALE_FACTOR = 10 ** (18 - rewardTokenDecimals);

        // create an amm pair with WETH.
        // as a contract, pair is automatically excluded from rewards.
        IUniswapV2Factory factory = IUniswapV2Factory(router.factory());

        pair = IUniswapV2Pair(factory.createPair(router.WETH(), address(this)));

        // mint total supply to itself.
        _mint(address(this), _totalSupply * 10 ** decimals());
    }

    // =========================================================================
    // exposed contract values.
    // =========================================================================

    /**
     * Whether the given address is blacklisted or not.
     *
     * The only way an address can be blacklisted is by receiving tokens when
     * trading is not enabled.
     */
    function isBlacklisted(address addr) public view returns (bool) {
        return blacklist.contains(addr);
    }

    /**
     * The circulating supply.
     *
     * It is the total supply minus the balances of all blacklisted addresses.
     *
     * Very gas intensive, should not be used in a state modifying function.
     */
    function circulatingSupply() public view returns (uint256) {
        uint256 lockedSupply;
        uint256 length = blacklist.length();

        for (uint256 i = 0; i < length; i++) {
            lockedSupply += super.balanceOf(blacklist.at(i));
        }

        return totalSupply() - lockedSupply;
    }

    /**
     * Balance of the given account.
     *
     * 0 when trading is enabled and the account is blacklisted.
     */
    function balanceOf(address account) public view override returns (uint256) {
        if (!isBlacklisted(account) || !_isTradingEnabled()) {
            return super.balanceOf(account);
        }

        return 0;
    }

    /**
     * Locked balance of the given account.
     *
     * 0 when the the account is not blacklisted.
     */
    function lockedBalanceOf(address account) public view returns (uint256) {
        if (isBlacklisted(account)) {
            return super.balanceOf(account);
        }

        return 0;
    }

    /**
     * The remaining rewards to emit.
     */
    function remainingRewards() public view returns (uint256) {
        return rewardBalance() - emittedRewards();
    }

    /**
     * The reward balance (amount that can be emitted in total).
     *
     * It is this contract balance of reward tokens minus the amount remaining
     * to claim.
     */
    function rewardBalance() public view returns (uint256) {
        uint256 amountToClaim = totalRewardDistributed - totalRewardClaimed;

        return rewardToken.balanceOf(address(this)) - amountToClaim;
    }

    /**
     * The amount of reward emitted since the last emitting block recorded,
     * according to the reward token per block.
     *
     * Emitting block is recorded when setting the reward tokens per block and
     * during an distribution occurrence.
     *
     * This value is capped by the reward balance.
     */
    function emittedRewards() public view returns (uint256) {
        if (lastEmittingBlock == 0) return 0;
        if (rewardTokenPerBlock == 0) return 0;

        uint256 balance = rewardBalance();

        if (balance == 0) return 0;

        uint256 emitted = emittedRewardsCache + (rewardTokenPerBlock * (block.number - lastEmittingBlock));

        return emitted < balance ? emitted : balance;
    }

    /**
     * The amount of reward tokens the given address can claim.
     */
    function pendingRewards(address addr) external view returns (uint256) {
        return _pendingRewards(shareholders[addr]);
    }

    // =========================================================================
    // exposed user functions.
    // =========================================================================

    /**
     * Claim reward tokens and send them to given address.
     */
    function claim(address to) external nonReentrant {
        Share storage share = shareholders[msg.sender];

        _earn(share);

        uint256 amountToClaim = share.earned;

        if (amountToClaim == 0) return;

        share.earned = 0;

        totalRewardClaimed += amountToClaim;

        rewardToken.safeTransfer(to, amountToClaim);

        emit Claim(msg.sender, to, amountToClaim);
    }

    /**
     * Optin for rewards when you are excluded from rewards (contracts).
     */
    function rewardOptin() external {
        _includeToRewards(msg.sender);

        isOptin[msg.sender] = true;

        emit OptIn(msg.sender);
    }

    /**
     * Optout for rewards when you are included to rewards (contracts).
     */
    function rewardOptout() external {
        _removeFromRewards(msg.sender);

        isOptin[msg.sender] = false;

        emit OptOut(msg.sender);
    }

    /**
     * Swap the collected tax to ETH.
     *
     * Pass minimal expected amount to prevent slippage/frontrun.
     */
    function swapCollectedTax(uint256 amountOutMin) public {
        // return if no tax collected.
        uint256 amountIn = balanceOf(address(this));

        if (amountIn == 0) return;

        // swap tax to eth.
        uint256 collectedEth = _swapTokenToETHV2(address(this), amountIn, amountOutMin);

        // collect marketing tax.
        uint256 marketingAmount = (collectedEth * marketingFee) / feeDenominator;

        if (marketingAmount > 0) {
            payable(operator).transfer(marketingAmount);
        }
    }

    /**
     * Distribute reward token.
     *
     * Pass minimal expected amount to prevent slippage/frontrun.
     */
    function distribute(uint256 amountOutMinimum) public {
        if (totalShares == 0) return;

        // distribute the rewards that was emitted since last update.
        uint256 amountToDistribute = emittedRewards();

        // swap eth balance to reward token and add it to amount to distribute.
        uint256 amountIn = address(this).balance;

        if (amountIn > 0) {
            amountToDistribute += _swapETHToRewardV3(address(this), amountIn, amountOutMinimum);
        }

        // stop when no rewards.
        if (amountToDistribute == 0) return;

        // distribute rewards.
        tokenPerShare += (amountToDistribute * SCALE_FACTOR * PRECISION) / totalShares;
        totalRewardDistributed += amountToDistribute;

        // reset emitted rewards.
        emittedRewardsCache = 0;
        lastEmittingBlock = block.number;

        emit Distribute(msg.sender, amountToDistribute);
    }

    /**
     * Sweep any other ERC20 mistakenly sent to this contract.
     */
    function sweep(IERC20 otherToken) external {
        require(address(otherToken) != address(this), "!sweep");
        require(address(otherToken) != address(rewardToken), "!sweep");

        uint256 amount = otherToken.balanceOf(address(this));

        otherToken.safeTransfer(msg.sender, amount);

        emit Sweep(msg.sender, address(otherToken), amount);
    }

    // =========================================================================
    // exposed admin functions.
    // =========================================================================