// File: BuyBack.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
import {ERC20} from "ERC20.sol";
import {Owned} from "Owned.sol";
import {ERC4626} from "ERC4626.sol";
import {IBuyBack} from "IBuyBack.sol";
import {IBurner} from "IBurner.sol";
import {IVester} from "IVester.sol";
import {IGelatoTopUp} from "IGelatoTopUp.sol";
import {ICurve3Pool} from "ICurve3Pool.sol";
import {IUniFactory} from "IUniFactory.sol";
import {IUniV2} from "IUniV2.sol";
import {IUniV3} from "IUniV3.sol";
import {IUniV3_POOL} from "IUniV3Pool.sol";
import {IWETH9} from "IWETH9.sol";

////////////////////////////////////////////////////////////////////////////////////////////
//                  LIBRARIES
////////////////////////////////////////////////////////////////////////////////////////////

library BuyBackErrors {
    error NotOwner(); // 0x30cd7471
    error NotKeeper(); // 0xf512b278
    error GelatoDepositFailed(); //
}

////////////////////////////////////////////////////////////////////////////////////////////
//    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
//    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
//    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
//    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██████▓▓▓▓▓▓▓▓▓▓▓▓█▌░,,▀██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓
//    █▓▓▓███▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████████▓▓▓▓▓▓▓▓▓▓▓█▌▒▒▒▒▒░▀█▓▓▓▓▓▓▓▓▓▓▓█▓`╙█▓▓▓▓▓▓▓▓▓▓
//    █▓▓▓███████▓▓▓▓▓▓▓▓▓▓▓▓▓██████████▓▓▓▓▓▓▓▓▓▓▓▌▒▒▒▒▒▒░░██▓▓▓▓▓▓▓▓█╜░▒▒█▓▓▓▓▓▓▓▓▓▓
//    ██▓▓███████████▓▓▓█████████████████▓▓▓▓▓▓▓▓▓▓█▒▒▒▒▒▒░░░▀█▓▓▓▓▓▓█░░▒▒▒▓█▓▓▓▓▓▓▓▓▓
//    ██▓▓████████████████████████████████▓▓▓▓▓▓▓▓▓█▒▒▒▒▒▒░░░░░▀▀▀▀▀▀▀▀Ñ▄▒▒▒█▓▓▓▓▓▓▓▓▓
//    ███▓▓███████████▀▒██▌▒▀██████████▄▓▒█▓▓▓▓▓▓▓▓█░░░░░@▓██▄░░░░░░░░░░▒▓█▓▓▓▓▓▓▓▓▓▓▓
//    ▓██▓▓▓██████████▒▒▀██▒████████████████▓▓▓▓▓▓▓█▌ ░╠▒╠█████▌░░░░░░░▒▓████▓▓▓▓▓▓▓▓▓
//    ████▓▓▓████████████████████▀▀▀░░░ ░╙▀███▓▓▓▓▓█▒░░░▀▄▓██▓█▀░░░░╙╣ ░▀▓████▓▓▓▓▓▓▓▓
//    █▓▓███▓████████████████▀░░░░░░░░░░░░░░██▓▓▓▓█▀░░░░░░╙╙▀'░,▄▄╖╖▄▄▄µ▄█▄▄░▐█▓▓▓▓▓▓▓
//    ███████▓█████████████▀░▄█████▄░░░░░░░▓███▓▓▓▌░░░ `    ░╓▄██████████████▄▓▓▓▓▓▓▓▓
//    ████▓▓██████████████▌░██▓▓██▓█▄,░░░ ╙██▓█▓▓█░░░░░  ░░╓█████████████████▓█▓▓▓▓▓▓▓
//    █▒▓█▓▓▓█████████████ ░░▀▓▓██▓█▀M ░░░╢╟▀▐█▓▓▓░' ░ ░░░███╣╣▓█▓█████████▌▓▌ ╟▓▓▓▓▓▓
//    ▓█╣▓██▓████████████▒░░░░░░╜░░.░░░░░▒▓█▒░▓█▓▌ ▒▒░░ ▄████▓╣▓█╢▓██████▌╫▌▓█U░█▓▓▓▓▓
//    ▓▓█▒▓██████████████░░░░░  ░░ ░░░░░░░▓▌░░▓█▓░░░░░░▐██████████████████▓███▌ ▓█▓▓▓▓
//    ▓▓▓█▒▓████████████▌░  ░░   ░░░░░░░╙╙░░▄▓▓█▌░░░░░ ▓██████████████████████▌░▐█▓▓▓▓
//    ▓▓▓▓█▒▓███████████▒'  ░░     ░░░░g██████▓█░░░░░░░▐█████████████████████▀░  █▓▓▓▓
//    ▓╢▓▓██▒▒███████████╓  ░░      '░]██████▓▓█░ ░░░░ ░▀██████████████████▀░░  ╓▓▓▓▓▓
//    ▒╢╢╢█▓█▒▓█████████████▄,      ,▄██████▓▓▓▓▓▄╖░░ ░░░╙▀█████████████▀░g▒░,▄▄██▓▓▓▓
//    ▒╢╫╣▓███▒▓████████████████▄ ▓█████████▓▓▓▓▓▓██╙╨m▄, ░░▀██████████▄Ñ▒▄███▓▓╢▓▓▓▓▓
//    ▒╫╫██████▓█████████████████████████████▓▓▓▓█▀░░░░'⌠╙░░░▒░▀██▀▓██▀░▒▀▀████▓▓▓▓▓▓▓
//    ╢▓█████▓▀'▒▒▀████████████████████████████▓▓▓█▀░░░░░░░░░░░░░░░░░░  ░░░░░▀██▓▓▓▓▓▓
//    ▓█████▒░░░▒▒▓█▓█████████████████████████████▀░`' ░░ ░░░░░░░░░▒░░░  ░    ░▀█▓▓▓▓▓
//    ██████▄░░░░▒▒▒▀████████████▀▓███████████████▄░░  ░   ░░░░░░ ░░░░░░    ░    ▀▓╣▓▓
//    ████████W░░░░░▄█▀▀█▀▀▀███▀╙░╙████████████████▌░        ░░░░░░░░░░░░░░       ╙███
//    █████████░w░╟█▓▓▄ ░░░░░░░░░░░▓█████████████████,      ░,░ ░░░░░░░░░░░░░░ ░░  `░▀
//    █████████▄████▌▒▓▌`  ░░░░░ ░░╠██████████████████▌     ░░░  ░██░░░░░░░░░░░░░░░ ░
//    ████████████▌╙█▒╢█W  ░ ░░░ ░░░▓███████████████████╖░  ░░ ░ ░██▌░░░░░░░░░ ░░  ░░░
//    ███████████▓▄¿▐███████▄,░    ░░▀████████████████████╖░░ ░░ ░███▒░░░░░░ ░ ░░░ ░ ░
//    ███████████▌▀████▌░░ ░▀▀▓▄░░ ░░░╙█████████████████████▄,   └███▌░░░░░░░░   ░ ░░
//    ████████████░ ██▓▒▒▒▒░ ░░░╙▀▀╜░░░ ▀████████████████████░  ░j████░   ░  ░ ░░░░░░
//    ████████████░░▐█▓█▀░▒░░░░    ░ '   ▓██████████████████░ ░░░░████░░░░░░░░░ ░░ ░░░
//    ███████████▌░░  ╙▀█▓▄╓▄   ░       ╓███████████████████     ░████▌  ░  ░░░░
//    ███████████┘      ` ██████▌%▄,╓,╥╙▀▐█████████████████M      ████▌              ,
////////////////////////////////////////////////////////////////////////////////////////////
//                  BUY BACK CONTRACT
////////////////////////////////////////////////////////////////////////////////////////////

contract BuyBack is IBuyBack, Owned {
    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  CONSTANTS
    ////////////////////////////////////////////////////////////////////////////////////////////
    enum AMM {
        UNIv2,
        UNIv3,
        CURVE
    }

    uint256 public constant KEEPER_MIN_ETH = 2E17;
    uint256 public constant MIN_TOPUP_ETH = 5E17;
    uint256 public constant MIN_SEND_TO_TREASURY = 1E9;
    uint256 public constant MIN_BURN = 1E22;

    /// crv pool usdc index
    int128 internal constant USDC_CRV_INDEX = 1;
    uint256 internal constant DEFAULT_DECIMALS_FACTOR = 1E18;
    uint256 internal constant BP = 1E4;

    /// Gelato addresses
    address internal constant GELATO_WALLET =
        0x2807B4aE232b624023f87d0e237A3B1bf200Fd99;
    address internal constant GELATO_KEEPER =
        0x701137e5b01c7543828DF340d05b9f9BEd277F7d;
    address internal constant GELATO_ETH =
        0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    /// Gro addresses
    address public constant GRO_TREASURY =
        0x359F4fe841f246a095a82cb26F5819E10a91fe0d;
    address public constant GRO_BURNER =
        0x1F09e308bb18795f62ea7B114041E12b426b8880;
    address public constant GRO_VESTER =
        0x748218256AfE0A19a88EBEB2E0C5Ce86d2178360;

    /// token addresses
    address internal constant WETH =
        address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address internal constant USDC =
        address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    address internal constant GRO =
        address(0x3Ec8798B81485A254928B70CDA1cf0A2BB0B74D7);
    address internal constant CRV_3POOL =
        address(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);
    ERC20 internal constant CRV_3POOL_TOKEN =
        ERC20(address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490));

    /// AMM addresses
    address internal constant THREE_POOL =
        0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
    address internal constant UNI_V2 =
        address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    address internal constant UNI_V3 =
        address(0xE592427A0AEce92De3Edee1F18E0157C05861564);
    address internal constant UNI_V2_FACTORY =
        address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
    address internal constant USDC_ETH_V3 =
        address(0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  CONTRACT VARIABLES
    ////////////////////////////////////////////////////////////////////////////////////////////

    // keep track of the total amount of USDC
    uint256 public treasury;
    uint256 public burner;
    uint256 public keeper;

    /// Percentage division between recievers of buy back actions
    ///     denoted in BP, should add up to 100%
    struct distributionSplit {
        uint16 treasury;
        uint16 burner;
        uint16 keeper;
    }

    /// Information regarding tokens that are being used
    struct tokenData {
        address wrapped; // if 4626, address of the vault
        uint256 minSellAmount;
        AMM amm;
        uint24 fee; // amm fee, used for uniV3
    }

    // list of tokens
    address[] public tokens;
    mapping(address => tokenData) public tokenInfo;

    mapping(address => bool) public keepers;
    distributionSplit public tokenDistribution;

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  EVENTS
    ////////////////////////////////////////////////////////////////////////////////////////////
    event LogNewTokenAdded(
        address token,
        address wrapped,
        uint256 minSellAmount,
        AMM amm,
        uint24 fee
    );
    event LogTokenRemoved(address token);
    event tokenSold(
        address token,
        uint256 amountToSell,
        uint256 amountToTreasury,
        uint256 amountToKeeper,
        uint256 amountToBurner
    );
    event TopUpKeeper(uint256 tokenAmount);
    event SendToTreasury(uint256 tokenAmount);
    event BurnTokens(uint256 tokenAmount, uint256 groAmount);
    event LogDepositReceived(address sender, uint256 value);

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  CONSTRUCTOR
    ////////////////////////////////////////////////////////////////////////////////////////////

    constructor() Owned(msg.sender) {
        ERC20(GRO).approve(GRO_BURNER, type(uint256).max);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  SETTERS
    ////////////////////////////////////////////////////////////////////////////////////////////

    function setKeeper(address _keeper) external {
        if (msg.sender != owner) revert BuyBackErrors.NotOwner();
        keepers[_keeper] = true;
    }

    function revokeKeeper(address _keeper) external {
        if (msg.sender != owner) revert BuyBackErrors.NotOwner();
        keepers[_keeper] = false;
    }

    /// @notice sets the distribution of tokens between treasury, burner and keeper
    /// @param _treasury percentage of tokens to be sent to treasury
    /// @param _burner percentage of tokens to be sent to burner
    /// @param _keeper percentage of tokens to be sent to keeper
    function setTokenDistribution(
        uint16 _treasury,
        uint16 _burner,
        uint16 _keeper
    ) external {
        if (msg.sender != owner) revert BuyBackErrors.NotOwner();
        tokenDistribution.treasury = _treasury;
        tokenDistribution.burner = _burner;
        tokenDistribution.keeper = _keeper;
    }

    /// @notice adds token to list of tokens that can be sold
    /// @param _token address of token to be added
    /// @param _wrapped address of wrapped token
    /// @param _minSellAmount minimum amount of token to be sold
    /// @param _amm index of AMM to be used
    /// @param _fee fee of AMM to be used
    function setToken(
        address _token,
        address _wrapped,
        uint256 _minSellAmount,
        uint8 _amm,
        uint24 _fee
    ) external {
        if (msg.sender != owner) revert BuyBackErrors.NotOwner();
        tokens.push(_token);
        AMM amm = AMM(_amm);
        tokenData memory tokenI = tokenData(
            _wrapped,
            _minSellAmount,
            amm,
            _fee
        );
        tokenInfo[_token] = tokenI;
        if (_amm == 0) {
            ERC20(_token).approve(UNI_V2, type(uint256).max);
        } else if (_amm == 1) {
            ERC20(_token).approve(UNI_V3, type(uint256).max);
        }
        emit LogNewTokenAdded(_token, _wrapped, _minSellAmount, amm, _fee);
    }

    /// @notice removes token from list of tokens that can be sold
    /// @param _token address of token to be removed
    function removeToken(address _token) external {
        if (msg.sender != owner) revert BuyBackErrors.NotOwner();

        uint256 noOfTokens = tokens.length;
        for (uint256 i = 0; i < noOfTokens; i++) {
            if (tokens[i] == _token) {
                tokens[i] = tokens[noOfTokens - 1];
                tokens.pop();
                delete tokenInfo[_token];
                emit LogTokenRemoved(_token);
            }
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  TRIGGERS
    ////////////////////////////////////////////////////////////////////////////////////////////

    /// @notice Iterates through tokens and returns first token that can be sold
    /// @return address of token that can be sold
    function canSellToken() public view override returns (address) {
        uint256 noOfTokens = tokens.length;
        address token;
        for (uint256 i = 0; i < noOfTokens - 1; i++) {
            token = tokens[i];
            if (
                ERC20(token).balanceOf(address(this)) >
                tokenInfo[token].minSellAmount
            ) {
                return token;
            }
        }
        return address(0);
    }

    /// @notice returns bool if the contract can send to treasury if value of USDC > MIN_SEND_TO_TREASURY
    function canSendToTreasury() public view override returns (bool) {
        if (ERC20(USDC).balanceOf(address(this)) > MIN_SEND_TO_TREASURY)
            return true;
        return false;
    }

    /// @notice returns bool if the contract can burn tokens if value of GRO denominated in USDC > MIN_BURN
    /// @return bool if the contract can burn tokens
    function canBurnTokens() public view override returns (bool) {
        if (
            getPriceV2(USDC, GRO, ERC20(USDC).balanceOf(address(this))) >
            MIN_BURN
        ) {
            return true;
        }
        return false;
    }

    /// @notice returns bool if the contract can top up keeper if gelato wallet balance < KEEPER_MIN_ETH
    /// and contract balance > MIN_TOPUP_ETH
    /// @return bool if the contract can top up keeper
    function canTopUpKeeper() public view override returns (bool) {
        if (
            IGelatoTopUp(GELATO_WALLET).userTokenBalance(
                GELATO_KEEPER,
                GELATO_ETH
            ) <
            KEEPER_MIN_ETH &&
            topUpAvailable() > MIN_TOPUP_ETH
        ) {
            return true;
        }
        return false;
    }

    /// @notice returns cumulative amount of ETH in this contract plus USDC value of keeper balance denomiated in ETH
    /// @return uint256 of ETH available to top up keeper
    function topUpAvailable() public view returns (uint256) {
        uint256 balance = address(this).balance;
        balance += getPriceV3(keeper);
        return balance;
    }

    /// @notice returns bool if the contract can buy back tokens
    /// @return tokenToSell address of token that can be sold
    /// @return canTreasury bool if contract can send to treasury
    /// @return canBurn bool if contract can burn tokens
    /// @return canTopUp bool if contract can top up keeper
    function buyBackTrigger()
        external
        view
        returns (
            address tokenToSell,
            bool canTreasury,
            bool canBurn,
            bool canTopUp
        )
    {
        tokenToSell = canSellToken();
        canTreasury = canSendToTreasury();
        canBurn = canBurnTokens();
        canTopUp = canTopUpKeeper();
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  CORE
    ////////////////////////////////////////////////////////////////////////////////////////////

    /// @notice Unwraps WETH to ETH and send it to Gelato keeper, then sets keeper storage var to 0
    function topUpKeeper() public override {
        if (msg.sender != owner || !keepers[msg.sender])
            revert BuyBackErrors.NotKeeper();
        uint256 _keeperAmount = uniV3Swap(USDC, WETH, 500, keeper, true);
        if (_keeperAmount == 0) return;
        (bool success, ) = GELATO_WALLET.call{value: _keeperAmount}(
            abi.encodeWithSignature(
                "depositFunds(address,address,uint256)",
                GELATO_KEEPER,
                GELATO_ETH,
                _keeperAmount
            )
        );
        if (!success) revert BuyBackErrors.GelatoDepositFailed();
        emit TopUpKeeper(_keeperAmount);
        keeper = 0;
    }

    /// @notice sends USDC to treasury then sets treasury storage var to 0
    function sendToTreasury() public override {
        if (msg.sender != owner || !keepers[msg.sender])
            revert BuyBackErrors.NotKeeper();
        uint256 _treasury = treasury;
        ERC20(USDC).transfer(GRO_TREASURY, _treasury);
        emit SendToTreasury(_treasury);
        treasury = 0;
    }

    /// @notice burns GRO tokens then sets burner storage var to 0
    function burnTokens() public override {
        if (msg.sender != owner || !keepers[msg.sender])
            revert BuyBackErrors.NotKeeper();
        uint256 _burner = burner;
        uint256 amount = uniV2Swap(USDC, GRO, _burner);
        IBurner(GRO_BURNER).reVest(amount);
        IVester(GRO_VESTER).exit(amount);
        emit BurnTokens(_burner, amount);
        burner = 0;
    }

    /// @notice Unwraps ERC4626 tokens to underlying asset
    /// @param _amount amount of tokens to unwrap
    /// @param _wrapper address of wrapper contract
    function _unwrapToken(
        uint256 _amount,
        address _wrapper
    ) internal returns (uint256, address) {
        ERC4626 wrapper = ERC4626(_wrapper);
        address asset = address(wrapper.asset());
        uint256 amount = wrapper.redeem(_amount, address(this), address(this));
        return (amount, asset);
    }

    /// @notice sell tokens for USDC through predefined AMM
    /// @param _token address of token to sell
    /// @param _amount amount of token to sell
    /// @param _amm AMM to use for swap
    /// @param _fee fee to use for swap
    /// @return amount of tokens received from swap
    function _sellTokens(
        address _token,
        uint256 _amount,
        AMM _amm,
        uint24 _fee
    ) internal returns (uint256 amount) {
        if (_amm == AMM.CURVE) {
            amount = curveSwap(_amount);
        } else if (_amm == AMM.UNIv2) {
            amount = uniV2Swap(_token, USDC, _amount);
        } else if (_amm == AMM.UNIv3) {
            amount = uniV3Swap(_token, USDC, _fee, _amount, false);
        }
    }

    /// @notice sell tokens for USDC and distribute to treasury, burner, keeper, and owner
    /// @param _token address of token to sell
    function sellTokens(address _token) public override {
        if (msg.sender != owner || !keepers[msg.sender])
            revert BuyBackErrors.NotKeeper();

        uint256 amountToSell = ERC20(_token).balanceOf(address(this));
        tokenData memory tokenI = tokenInfo[_token];
        if (amountToSell < tokenI.minSellAmount) return;

        address wrapper = tokenI.wrapped;
        if (wrapper != address(0))
            (amountToSell, _token) = _unwrapToken(amountToSell, wrapper);
        uint256 amount = _sellTokens(
            _token,
            amountToSell,
            tokenI.amm,
            tokenI.fee
        );
        uint256 amountToTreasury = (amount * tokenDistribution.treasury) / BP;
        uint256 amountToBurner = (amount * tokenDistribution.burner) / BP;
        uint256 amountToKeeper = amount - (amountToTreasury + amountToBurner);

        treasury += amountToTreasury;
        burner += amountToBurner;
        keeper += amountToKeeper;

        emit tokenSold(
            _token,
            amountToSell,
            amountToTreasury,
            amountToKeeper,
            amountToBurner
        );
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  UTILITY
    ////////////////////////////////////////////////////////////////////////////////////////////

    function getToken(address _token) external view returns (tokenData memory) {
        return tokenInfo[_token];
    }

    /// @notice Fetch price from Uniswap V2
    /// @param _start address of token to sell
    /// @param _end address of token to buy
    /// @param _amount amount of token to sell
    function getPriceV2(
        address _start,
        address _end,
        uint256 _amount
    ) internal view returns (uint256 price) {
        if (_amount == 0) return 0;
        address[] memory path = new address[](2);
        path[0] = _start;
        path[1] = _end;

        uint256[] memory uniSwap = IUniV2(UNI_V2).getAmountsOut(_amount, path);
        return uniSwap[uniSwap.length - 1];
    }

    /// @notice Returns the amount of ETH that can be bought with the given amount of USDC
    /// @param _amount The amount of USDC to sell for ETH
    /// @return price The amount of ETH that can be bought with the given amount of USDC
    function getPriceV3(uint256 _amount) public view returns (uint256 price) {
        (uint160 sqrtPriceX96, , , , , , ) = IUniV3_POOL(USDC_ETH_V3).slot0();
        price = ((2 ** 192 * DEFAULT_DECIMALS_FACTOR) /
            uint256(sqrtPriceX96) ** 2);
        return (_amount * 1E18) / price;
    }

    /// @notice Swap tokens on Uniswap V2
    /// @param _start address of token to sell
    /// @param _end address of token to buy
    /// @param _amount amount of token to sell
    function uniV2Swap(
        address _start,
        address _end,
        uint256 _amount
    ) internal returns (uint256) {
        if (_amount == 0) return 0;
        if (ERC20(_start).allowance(address(this), UNI_V2) == 0)
            ERC20(_start).approve(UNI_V2, _amount);
        address[] memory path = new address[](2);
        path[0] = _start;
        path[1] = _end;

        uint256[] memory swap = IUniV2(UNI_V2).swapExactTokensForTokens(
            _amount,
            uint256(0),
            path,
            address(this),
            block.timestamp
        );
        return swap[1];
    }

    /// @notice Swap tokens on Uniswap V3
    /// @param _start address of token to sell
    /// @param _end address of token to buy
    /// @param _fees fees to use for swap
    /// @param _amount amount of token to sell
    /// @param _eth whether to unwrap WETH or not
    function uniV3Swap(
        address _start,
        address _end,
        uint24 _fees,
        uint256 _amount,
        bool _eth
    ) internal returns (uint256 amount) {
        if (_amount == 0) return 0;
        amount = IUniV3(UNI_V3).exactInput(
            IUniV3.ExactInputParams(
                abi.encodePacked(_start, uint24(_fees), _end),
                address(this),
                block.timestamp,
                _amount,
                uint256(1)
            )
        );
        if (_eth) {
            IWETH9(WETH).withdraw(amount);
        }
    }

    /// @notice Swap tokens on Curve
    /// @param _amount amount of token to sell
    function curveSwap(uint256 _amount) internal returns (uint256) {
        if (_amount == 0) return 0;
        ICurve3Pool(THREE_POOL).remove_liquidity_one_coin(
            _amount,
            USDC_CRV_INDEX,
            0
        );
        return ERC20(USDC).balanceOf(address(this));
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    //                  FALLBACK
    ////////////////////////////////////////////////////////////////////////////////////////////

    receive() external payable {
        emit LogDepositReceived(msg.sender, msg.value);
    }

    fallback() external payable {
        emit LogDepositReceived(msg.sender, msg.value);
    }

    /// @notice This function is used to sweep any tokens that are stuck in the contract
    function sweep(address asset) external onlyOwner {
        ERC20(asset).transfer(
            msg.sender,
            ERC20(asset).balanceOf(address(this))
        );
    }
}

// File: ERC20.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author solmate/src (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*///////////////////////////////////////////////////////////////
                                  EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 amount
    );

    /*///////////////////////////////////////////////////////////////
                             METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*///////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*///////////////////////////////////////////////////////////////
                             EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    bytes32 public constant PERMIT_TYPEHASH =
        keccak256(
            "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
        );

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*///////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*///////////////////////////////////////////////////////////////
                              ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount)
        public
        virtual
        returns (bool)
    {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount)
        public
        virtual
        returns (bool)
    {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max)
            allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*///////////////////////////////////////////////////////////////
                              EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            bytes32 digest = keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(
                        abi.encode(
                            PERMIT_TYPEHASH,
                            owner,
                            spender,
                            value,
                            nonces[owner]++,
                            deadline
                        )
                    )
                )
            );

            address recoveredAddress = ecrecover(digest, v, r, s);

            require(
                recoveredAddress != address(0) && recoveredAddress == owner,
                "INVALID_SIGNER"
            );

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return
            block.chainid == INITIAL_CHAIN_ID
                ? INITIAL_DOMAIN_SEPARATOR
                : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256(
                        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
                    ),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// File: Owned.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author solmate/src (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

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

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}

// File: ERC4626.sol



// SPDX-License-Identifier: MIT
// Adpated from OZ Draft Implementation

pragma solidity ^0.8.12;

import {ERC20} from "ERC20.sol";

abstract contract ERC4626 is ERC20 {
    event Deposit(
        address indexed caller,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256 assets,
        uint256 shares
    );

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view virtual returns (ERC20);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets()
        external
        view
        virtual
        returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(
        uint256 assets
    ) external view virtual returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToAssets(
        uint256 shares
    ) external view virtual returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
     * through a deposit call.
     *
     * - MUST return a limited value if receiver is subject to some deposit limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
     * - MUST NOT revert.
     */
    function maxDeposit(
        address receiver
    ) external view virtual returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
     *   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
     *   in the same transaction.
     * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
     *   deposit would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewDeposit(
        uint256 assets
    ) external view virtual returns (uint256 shares);

    /**
     * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   deposit execution, and are accounted for during deposit.
     * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function deposit(
        uint256 assets,
        address receiver
    ) external virtual returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
     * - MUST return a limited value if receiver is subject to some mint limit.
     * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
     * - MUST NOT revert.
     */
    function maxMint(
        address receiver
    ) external view virtual returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
     * current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
     *   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
     *   same transaction.
     * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
     *   would be accepted, regardless if the user has enough tokens approved, etc.
     * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by minting.
     */
    function previewMint(
        uint256 shares
    ) external view virtual returns (uint256 assets);

    /**
     * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
     *
     * - MUST emit the Deposit event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
     *   execution, and are accounted for during mint.
     * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
     *   approving enough underlying tokens to the Vault contract, etc).
     *
     * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
     */
    function mint(
        uint256 shares,
        address receiver
    ) external virtual returns (uint256 assets);

    /**
     * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
     * Vault, through a withdraw call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxWithdraw(
        address owner
    ) external view virtual returns (uint256 maxAssets);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
     *   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
     *   called
     *   in the same transaction.
     * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
     *   the withdrawal would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by depositing.
     */
    function previewWithdraw(
        uint256 assets
    ) external view virtual returns (uint256 shares);

    /**
     * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   withdraw execution, and are accounted for during withdraw.
     * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function withdraw(
        uint256 assets,
        address receiver,
        address owner
    ) external virtual returns (uint256 shares);

    /**
     * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
     * through a redeem call.
     *
     * - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
     * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
     * - MUST NOT revert.
     */
    function maxRedeem(
        address owner
    ) external view virtual returns (uint256 maxShares);

    /**
     * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
     * given current on-chain conditions.
     *
     * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
     *   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
     *   same transaction.
     * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
     *   redemption would be accepted, regardless if the user has enough shares, etc.
     * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
     * - MUST NOT revert.
     *
     * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
     * share price or some other type of condition, meaning the depositor will lose assets by redeeming.
     */
    function previewRedeem(
        uint256 shares
    ) external view virtual returns (uint256 assets);

    /**
     * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
     *
     * - MUST emit the Withdraw event.
     * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
     *   redeem execution, and are accounted for during redeem.
     * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
     *   not having enough shares, etc).
     *
     * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
     * Those methods should be performed separately.
     */
    function redeem(
        uint256 shares,
        address receiver,
        address owner
    ) external virtual returns (uint256 assets);
}

// File: IBuyBack.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

abstract contract IBuyBack {
    function canSellToken() external view virtual returns (address);

    function canSendToTreasury() public view virtual returns (bool);

    function canBurnTokens() external view virtual returns (bool);

    function canTopUpKeeper() external view virtual returns (bool);

    function sellTokens(address _token) external virtual;

    function burnTokens() external virtual;

    function sendToTreasury() external virtual;

    function topUpKeeper() external virtual;
}

// File: IBurner.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

/// Gro burner interface - used to move gro tokens into the vesting contract
interface IBurner {
    function reVest(uint256 amount) external;
}

// File: IVester.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

/// Gro Vester interface - used to move vesting tokens into the bonus contract
interface IVester {
    function exit(uint256 amount) external;
}

// File: IGelatoTopUp.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

/// Gelato top up wallet interface
interface IGelatoTopUp {
    function depositFunds(
        address _receiver,
        address _token,
        uint256 _amount
    ) external;

    function userTokenBalance(
        address _user,
        address _token
    ) external view returns (uint256);
}

// File: ICurve3Pool.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

/// Curve 3pool interface
interface ICurve3Pool {
    function get_virtual_price() external view returns (uint256);

    function calc_withdraw_one_coin(
        uint256 _token_amount,
        int128 i
    ) external view returns (uint256);

    function remove_liquidity_one_coin(
        uint256 _token_amount,
        int128 i,
        uint256 min_amount
    ) external;

    function balanceOf(address account) external view returns (uint256);
}

// File: IUniFactory.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

interface IUniFactory {
    function getPair(
        address _tokenA,
        address _tokenB
    ) external view returns (address);
}

// File: IUniV2.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

/// Uniswap v2 router interface
interface IUniV2 {
    function getAmountsOut(
        uint256 amountIn,
        address[] calldata path
    ) external view returns (uint256[] memory amounts);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

// File: IUniV3.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

interface IUniV3 {
    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    function exactInput(
        ExactInputParams calldata params
    ) external payable returns (uint256 amountOut);
}

// File: IUniV3Pool.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

interface IUniV3_POOL {
    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );
}

// File: IWETH9.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

interface IWETH9 {
    /// @notice Deposit ether to get wrapped ether
    function deposit() external payable;

    /// @notice Withdraw wrapped ether to get ether
    function withdraw(uint256) external;
}