// File: /contracts/subStrategies/ETHStrategy.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./interfaces/IETHLeverage.sol";
import "./interfaces/IFlashloanReceiver.sol";
import "./interfaces/IWeth.sol";
import "./interfaces/IExchange.sol";
import "./interfaces/IAave.sol";
import "../interfaces/ISubStrategy.sol";
import "../interfaces/IVault.sol";
import "../utils/TransferHelper.sol";

contract ETHStrategy is Ownable, ISubStrategy, IETHLeverage {
    using SafeMath for uint256;

    // Sub Strategy name
    string public constant poolName = "ETHStrategy V0.9";

    // Controller address
    address public controller;

    // Vault address
    address public vault;

    // Constant magnifier
    uint256 public constant magnifier = 10000;

    // Harvest Gap
    uint256 public override harvestGap;

    // Latest Harvest
    uint256 public override latestHarvest;

    // Exchange Address
    address public exchange;

    // Flashloan receiver
    address public receiver;

    // Fee collector
    address public feePool;

    // WETH Address
    address public weth;

    // STETH Address
    address public stETH;

    // ASTETH Address
    address public astETH;

    // aave address
    address public aave;

    // Slippages for deposit and withdraw
    uint256 public depositSlippage;
    uint256 public withdrawSlippage;

    // Max Deposit
    uint256 public override maxDeposit;

    // Last Earn Block
    uint256 public lastEarnBlock;

    // Last Earn Total
    uint256 public lastTotal;

    // Block rate
    uint256 public blockRate;

    // Max Loan Ratio
    uint256 public mlr;

    enum SrategyState {
        Normal,
        Deposit,
        Withdraw,
        EmergencyWithdraw
    }

    SrategyState private curState;

    event SetController(address controller);

    event SetVault(address vault);

    event SetExchange(address exchange);

    event SetFeePool(address feePool);

    event SetDepositSlippage(uint256 depositSlippage);

    event SetWithdrawSlippage(uint256 withdrawSlippage);

    event SetHarvestGap(uint256 harvestGap);

    event SetMaxDeposit(uint256 maxDeposit);

    event SetFlashloanReceiver(address receiver);

    event SetMLR(uint256 oldMlr, uint256 newMlr);

    event SetBlockRate(uint256 oldRate, uint256 newRate);

    event LTVUpdate(
        uint256 oldDebt,
        uint256 oldCollateral,
        uint256 newDebt,
        uint256 newCollateral
    );

    constructor(
        address _weth,
        address _stETH,
        address _astETH,
        uint256 _mlr,
        address _aave,
        address _controller,
        address _vault,
        address _feePool
    ) {
        mlr = _mlr;
        weth = _weth;
        stETH = _stETH;
        astETH = _astETH;
        aave = _aave;

        controller = _controller;
        vault = _vault;
        feePool = _feePool;

        // Block Rate
        blockRate = 4756468797;

        // Set Max Deposit as max uin256
        maxDeposit = type(uint256).max;
    }

    receive() external payable {}

    /**
        Only controller can call
     */
    modifier onlyController() {
        require(controller == _msgSender(), "NOT_CONTROLLER");
        _;
    }

    /**
        Only Flashloan receiver can call
     */
    modifier onlyReceiver() {
        require(receiver == _msgSender(), "NOT_FLASHLOAN_RECEIVER");
        _;
    }

    modifier onDeposit() {
        require(curState == SrategyState.Normal, "REENTERANCY");
        curState = SrategyState.Deposit;
        _;
        curState = SrategyState.Normal;
    }

    modifier onWithdraw() {
        require(curState == SrategyState.Normal, "REENTERANCY");
        curState = SrategyState.Withdraw;
        _;
        curState = SrategyState.Normal;
    }

    modifier onEmergencyWithdraw() {
        require(curState == SrategyState.Normal, "REENTERANCY");
        curState = SrategyState.EmergencyWithdraw;
        _;
        curState = SrategyState.Normal;
    }

    //////////////////////////////////////////
    //           Flash loan Fallback        //
    //////////////////////////////////////////

    /**
        External Function for Callback when to flash loan
     */
    function loanFallback(
        uint256 loanAmt,
        uint256 feeAmt
    ) external override onlyReceiver {
        require(curState != SrategyState.Normal, "NORMAL_STATE_CANT_CALL_THIS");
        require(
            IERC20(weth).balanceOf(address(this)) >= loanAmt,
            "INSUFFICIENT_TRANSFERED"
        );

        if (curState == SrategyState.Deposit) {
            // Withdraw ETH from WETH
            IWeth(weth).withdraw(loanAmt);
            uint256 ethBal = address(this).balance;
            // Transfer ETH to Exchange
            TransferHelper.safeTransferETH(exchange, ethBal);
            // Swap ETH to STETH
            IExchange(exchange).swapStETH(ethBal);

            // Deposit STETH to AAVE
            uint256 stETHBal = IERC20(stETH).balanceOf(address(this));
            IERC20(stETH).approve(aave, 0);
            IERC20(stETH).approve(aave, stETHBal);

            IAave(aave).deposit(stETH, stETHBal, address(this), 0);
            if (getCollateral() == 0) {
                IAave(aave).setUserUseReserveAsCollateral(stETH, true);
            }
            // Repay flash loan
            uint256 repay = loanAmt + feeAmt;
            IAave(aave).borrow(weth, repay, 2, 0, address(this));

            TransferHelper.safeTransfer(weth, receiver, repay);
        } else if (curState == SrategyState.Withdraw) {
            // Withdraw ETH from WETH
            uint256 stETHAmt = (loanAmt *
                IERC20(astETH).balanceOf(address(this))) / getDebt();
            // Approve WETH to AAVE
            IERC20(weth).approve(aave, 0);
            IERC20(weth).approve(aave, loanAmt);

            // Repay WETH to aave
            IAave(aave).repay(weth, loanAmt, 2, address(this));
            IAave(aave).withdraw(stETH, stETHAmt, address(this));

            // Swap STETH to ETH
            TransferHelper.safeTransfer(stETH, exchange, stETHAmt);
            IExchange(exchange).swapETH(stETHAmt);
            // Deposit WETH
            TransferHelper.safeTransferETH(weth, (loanAmt + feeAmt));
            // Repay Weth to receiver
            TransferHelper.safeTransfer(weth, receiver, loanAmt + feeAmt);
        } else if (curState == SrategyState.EmergencyWithdraw) {
            // Withdraw ETH from WETH
            uint256 stETHAmt = (loanAmt *
                IERC20(astETH).balanceOf(address(this))) / getDebt();

            // Approve WETH to AAVE
            IERC20(weth).approve(aave, 0);
            IERC20(weth).approve(aave, loanAmt);

            // Repay WETH to aave
            IAave(aave).repay(weth, loanAmt, 2, address(this));
            IAave(aave).withdraw(stETH, stETHAmt, address(this));

            // Swap STETH to repay flashloan
            IERC20(stETH).approve(exchange, 0);
            IERC20(stETH).approve(exchange, stETHAmt);

            IExchange(exchange).swapExactETH(stETHAmt, loanAmt + feeAmt);
            // Deposit WETH
            TransferHelper.safeTransferETH(weth, (loanAmt + feeAmt));
            // Repay Weth to receiver
            TransferHelper.safeTransfer(weth, receiver, loanAmt + feeAmt);
        } else {
            revert("NOT_A_SS_STATE");
        }
    }

    //////////////////////////////////////////
    //          VIEW FUNCTIONS              //
    //////////////////////////////////////////

    /**
        External view function of total USDC deposited in Covex Booster
     */
    function totalAssets() external view override returns (uint256) {
        return _totalAssets();
    }

    /**
        Internal view function of total USDC deposited
    */
    function _totalAssets() internal view returns (uint256) {
        return getCollateral() - getDebt();
    }

    /**
        Deposit function of USDC
     */
    function deposit(
        uint256 _amount
    ) external override onlyController onDeposit returns (uint256) {
        // Harvest Reward First
        _harvest();

        uint256 deposited = _deposit(_amount);
        return deposited;
    }

    /**
        Deposit internal function
     */
    function _deposit(uint256 _amount) internal returns (uint256) {
        // Get Prev Deposit Amt
        uint256 prevAmt = _totalAssets();

        // Check Max Deposit
        require(prevAmt + _amount <= maxDeposit, "EXCEED_MAX_DEPOSIT");

        uint256 ethAmt = address(this).balance;
        require(ethAmt >= _amount, "INSUFFICIENT_ETH_TRANSFER");

        // Calculate Flashloan Fee - in terms of 1e4
        uint256 fee = IFlashloanReceiver(receiver).getFee();
        uint256 feeParam = fee + magnifier;
        uint256 loanAmt = (_amount * mlr) / (feeParam - mlr);
        // uint256 feeAmt = (loanAmt * fee) / magnifier;

        // Execute flash loan
        IFlashloanReceiver(receiver).flashLoan(weth, loanAmt);

        // Get new total assets amount
        uint256 newAmt = _totalAssets();

        // Deposited amt
        uint256 deposited = newAmt - prevAmt;
        uint256 minOutput = (_amount * (magnifier - depositSlippage)) /
            magnifier;

        require(deposited >= minOutput, "DEPOSIT_SLIPPAGE_TOO_BIG");

        return deposited;
    }

    /**
        Withdraw function of USDC
     */
    function withdraw(
        uint256 _amount
    ) external override onlyController onWithdraw returns (uint256) {
        // Harvest Reward First
        _harvest();

        // Get Prev Deposit Amt
        uint256 prevAmt = _totalAssets();
        require(_amount <= prevAmt, "INSUFFICIENT_ASSET");

        uint256 loanAmt = (getDebt() * _amount) / _totalAssets();
        IFlashloanReceiver(receiver).flashLoan(weth, loanAmt);

        uint256 toSend = address(this).balance;
        TransferHelper.safeTransferETH(controller, toSend);

        return toSend;
    }

    /**
        Harvest reward token from convex booster
     */
    function harvest() external onlyOwner {
        _harvest();
    }

    /**
        Internal Harvest Function
     */
    function _harvest() internal {
        if (_totalAssets() == 0) {
            lastEarnBlock = block.number;
            return;
        }

        uint256 collapsed = block.number - lastEarnBlock;

        // If collapsed is zero, return
        if (collapsed <= harvestGap) return;

        uint256 a = (lastTotal * blockRate * collapsed) / 1e18;
        uint256 b = (_totalAssets() * blockRate * collapsed) / 1e18;
        uint256 stFee;
        if (a <= b) {
            stFee = (a + b) / 2;
        } else {
            stFee = b;
        }

        uint256 feePoolBal = IERC20(vault).balanceOf(feePool);
        uint256 totalEF = IERC20(vault).totalSupply();

        if (totalEF == 0) return;

        stFee = stFee - ((stFee * feePoolBal) / (totalEF));

        uint256 mintAmt = (stFee * totalEF) / (_totalAssets() - stFee);

        // Mint EF token to fee pool
        IVault(vault).mint(mintAmt, feePool);

        lastEarnBlock = block.number;
        lastTotal = _totalAssets();
    }

    /**
        Raise LTV
     */
    function raiseLTV(uint256 lt) public onlyOwner {
        uint256 e = getDebt();
        uint256 st = getCollateral();

        require(e * magnifier < st * mlr, "NO_NEED_TO_RAISE");

        uint256 x = (st * mlr - (e * magnifier)) / (magnifier - mlr);
        uint256 y = (st * lt) / magnifier - e - 1;

        if (x > y) {
            x = y;
        }

        IAave(aave).borrow(weth, x, 2, 0, address(this));
        uint256 wethAmt = IERC20(weth).balanceOf(address(this));
        IWeth(weth).withdraw(wethAmt);

        // Transfer ETH to Exchange
        TransferHelper.safeTransferETH(exchange, wethAmt);
        // Swap ETH to STETH
        IExchange(exchange).swapStETH(wethAmt);

        // Deposit STETH to AAVE
        uint256 stETHBal = IERC20(stETH).balanceOf(address(this));
        IERC20(stETH).approve(aave, 0);
        IERC20(stETH).approve(aave, stETHBal);

        IAave(aave).deposit(stETH, stETHBal, address(this), 0);

        emit LTVUpdate(e, st, getDebt(), getCollateral());
    }

    /**
        Reduce LTV
     */
    function reduceLTV() public onlyOwner onWithdraw {
        uint256 e = getDebt();
        uint256 st = getCollateral();

        require(e * magnifier > st * mlr, "NO_NEED_TO_REDUCE");

        uint256 x = (e * magnifier - st * mlr) / (magnifier - mlr);

        uint256 loanAmt = (x * getDebt()) / getCollateral();

        IFlashloanReceiver(receiver).flashLoan(weth, loanAmt);

        uint256 toSend = address(this).balance;
        TransferHelper.safeTransferETH(weth, toSend);

        uint256 wethBal = IERC20(weth).balanceOf(address(this));
        // Approve WETH to AAVE
        IERC20(weth).approve(aave, 0);
        IERC20(weth).approve(aave, wethBal);

        // Repay WETH to aave
        IAave(aave).repay(weth, wethBal, 2, address(this));
    }



    /**
        Check withdrawable status of required amount
     */
    function withdrawable(
        uint256 _amount
    ) external view override returns (uint256) {
        // Get Current Deposit Amt
        uint256 total = _totalAssets();

        // If requested amt is bigger than total asset, return false
        if (_amount > total) return total;
        // Todo Have to check withdrawable amount
        else return _amount;
    }



    function getCollateral() public view returns (uint256) {
        (uint256 c, , , , , ) = IAave(aave).getUserAccountData(address(this));
        return c;
    }

    function getDebt() public view returns (uint256) {
        //decimal 18
        (, uint256 d, , , , ) = IAave(aave).getUserAccountData(address(this));
        return d;
    }

    //////////////////////////////////////////////////
    //               SET CONFIGURATION              //
    //////////////////////////////////////////////////

    /**
        Set Controller
     */
    function setController(address _controller) public onlyOwner {
        require(_controller != address(0), "INVALID_ADDRESS");
        controller = _controller;

        emit SetController(controller);
    }

    /**
        Set Vault
     */
    function setVault(address _vault) public onlyOwner {
        require(_vault != address(0), "INVALID_ADDRESS");
        vault = _vault;

        emit SetVault(vault);
    }

    /**
        Set Fee Pool
     */
    function setFeePool(address _feePool) public onlyOwner {
        require(_feePool != address(0), "INVALID_ADDRESS");
        feePool = _feePool;

        emit SetController(feePool);
    }

    /**
        Set Deposit Slipage
     */
    function setDepositSlippage(uint256 _slippage) public onlyOwner {
        require(_slippage < magnifier, "INVALID_SLIPPAGE");

        depositSlippage = _slippage;

        emit SetDepositSlippage(depositSlippage);
    }

    /**
        Set Withdraw Slipage
     */
    function setWithdrawSlippage(uint256 _slippage) public onlyOwner {
        require(_slippage < magnifier, "INVALID_SLIPPAGE");

        withdrawSlippage = _slippage;

        emit SetWithdrawSlippage(withdrawSlippage);
    }

    /**
        Set Harvest Gap
     */
    function setHarvestGap(uint256 _harvestGap) public onlyOwner {
        require(_harvestGap > 0, "INVALID_HARVEST_GAP");
        harvestGap = _harvestGap;

        emit SetHarvestGap(harvestGap);
    }

    /**
        Set Max Deposit
     */
    function setMaxDeposit(uint256 _maxDeposit) public onlyOwner {
        require(_maxDeposit > 0, "INVALID_MAX_DEPOSIT");
        maxDeposit = _maxDeposit;

        emit SetMaxDeposit(maxDeposit);
    }

    /**
        Set Flashloan Receiver
     */
    function setFlashLoanReceiver(address _receiver) public onlyOwner {
        require(_receiver != address(0), "INVALID_RECEIVER_ADDRESS");
        receiver = _receiver;

        emit SetFlashloanReceiver(receiver);
    }

    /**
        Set Exchange
     */
    function setExchange(address _exchange) public onlyOwner {
        require(_exchange != address(0), "INVALID_ADDRESS");
        exchange = _exchange;

        emit SetExchange(exchange);
    }

    /**
        Set Block Rate
     */
    function setBlockRate(uint256 _rate) public onlyOwner {
        require(_rate > 0, "INVALID_RATE");

        uint256 oldRate = blockRate;
        blockRate = _rate;

        emit SetBlockRate(oldRate, _rate);
    }

    /**
        Set MLR
     */
    function setMLR(uint256 _mlr) public onlyOwner {
        require(_mlr > 0 && _mlr < magnifier, "INVALID_RATE");

        uint256 oldMlr = mlr;
        mlr = _mlr;

        emit SetMLR(oldMlr, _mlr);
    }
}

// File: /contracts/utils/TransferHelper.sol



// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.0;

// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: APPROVE_FAILED");
    }

    /**
        Transfer Util for both Ether and ERC20
     */
    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        if (address(token) == address(0)) {
            safeTransferETH(to, value);
        } else {
            safeTransferToken(address(token), to, value);
        }
    }

    function safeTransferToken(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FAILED");
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), "TransferHelper: TRANSFER_FROM_FAILED");
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, "TransferHelper: ETH_TRANSFER_FAILED");
    }
}

// File: /contracts/subStrategies/interfaces/IWeth.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IWeth {
    function withdraw(uint256 wad) external;
}

// File: /contracts/subStrategies/interfaces/IFlashloanReceiver.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IFlashloanReceiver {
    function getFee() external view returns (uint256 fee);

    function flashLoan(address token, uint256 amount) external;
}

// File: /contracts/subStrategies/interfaces/IExchange.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IExchange {
    function swapStETH(uint256 amount) external;

    function swapETH(uint256 amount) external;

    function swapExactETH(uint256 input, uint256 output) external;
}

// File: /contracts/subStrategies/interfaces/IETHLeverage.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IETHLeverage {
    function loanFallback(uint256 loanAmt, uint256 feeAmt) external;
}

// File: /contracts/subStrategies/interfaces/IAave.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IAave {
    function borrow(
        address asset,
        uint256 amount,
        uint256 interestRateMode,
        uint16 referralCode,
        address onBehalfOf
    ) external;

    function deposit(
        address asset,
        uint256 amount,
        address onBehalfOf,
        uint16 referralCode
    ) external;

    function withdraw(
        address asset,
        uint256 amount,
        address to
    ) external returns (uint256);

    function repay(
        address asset,
        uint256 amount,
        uint256 rateMode,
        address onBehalfOf
    ) external returns (uint256);

    function getUserAccountData(address)
        external
        view
        returns (
            uint256 totalCollateralETH,
            uint256 totalDebtETH,
            uint256 availableBorrowsETH,
            uint256 currentLiquidationThreshold,
            uint256 ltv,
            uint256 healthFactor
        );

    function setUserUseReserveAsCollateral(address _reserse, bool _useAsCollateral) external;
}

// File: /contracts/interfaces/IVault.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVault {
    function deposit(uint256 assets, address receiver) external payable returns (uint256 shares);

    function mint(uint256 amount, address account) external;
}

// File: /contracts/interfaces/ISubStrategy.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ISubStrategy {
    function totalAssets() external view returns (uint256);

    function deposit(uint256 _amount) external returns (uint256);

    function withdraw(uint256 _amount) external returns (uint256);

    function maxDeposit() external view returns (uint256);

    function withdrawable(uint256 _amount) external view returns (uint256);

    function latestHarvest() external view returns (uint256);

    function harvestGap() external view returns (uint256);
}

// File: @openzeppelin/contracts/utils/math/SafeMath.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/math/SafeMath.sol)

pragma solidity ^0.8.0;

// CAUTION
// This version of SafeMath should only be used with Solidity 0.8 or later,
// because it relies on the compiler's built in overflow checks.

/**
 * @dev Wrappers over Solidity's arithmetic operations.
 *
 * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
 * now has built in overflow checking.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     *
     * _Available since v3.4._
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     *
     * _Available since v3.4._
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     *
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        return a + b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return a - b;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     *
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        return a * b;
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator.
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return a / b;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return a % b;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {trySub}.
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     *
     * - Subtraction cannot overflow.
     */
    function sub(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b <= a, errorMessage);
            return a - b;
        }
    }

    /**
     * @dev Returns the integer division of two unsigned integers, reverting with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function div(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a / b;
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * reverting with custom message when dividing by zero.
     *
     * CAUTION: This function is deprecated because it requires allocating memory for the error
     * message unnecessarily. For custom revert reasons use {tryMod}.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     *
     * - The divisor cannot be zero.
     */
    function mod(
        uint256 a,
        uint256 b,
        string memory errorMessage
    ) internal pure returns (uint256) {
        unchecked {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}