contract StrategyConvexEURT is StrategyConvexBase {
    /* ========== STATE VARIABLES ========== */
    // these will likely change across different wants.

    ICurveFi public curve; // Curve Pool, need this for buying more pool tokens

    // uniswap v3 variables
    address public constant uniswapv3 =
        0xE592427A0AEce92De3Edee1F18E0157C05861564;
    IERC20 public constant eurt =
        IERC20(0xC581b735A1688071A1746c968e0798D642EDE491);
    IERC20 public constant usdt =
        IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    IOracle public oracle = IOracle(0x0F1f5A87f99f0918e6C81F16E59F3518698221Ff); // this is only needed for strats that use uniV3 for swaps

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _vault,
        uint256 _pid,
        address _curvePool,
        string memory _name
    ) public StrategyConvexBase(_vault) {
        // You can set these parameters on deployment to whatever you want
        maxReportDelay = 7 days; // 7 days in seconds, if we hit this then harvestTrigger = True
        debtThreshold = 5 * 1e18; // set a bit of a buffer
        profitFactor = 10_000; // in this strategy, profitFactor is only used for telling keep3rs when to move funds from vault to strategy (what previously was an earn call)
        harvestProfitNeeded = 20_000 * 1e6; // this is how much in USDT we need to make. remember, 6 decimals!
        healthCheck = 0xDDCea799fF1699e98EDF118e0629A974Df7DF012; // health.ychad.eth

        // want = Curve LP
        want.approve(address(depositContract), type(uint256).max);
        crv.approve(sushiswap, type(uint256).max);
        convexToken.approve(sushiswap, type(uint256).max);

        // set our keepCRV
        keepCRV = 1000;

        // this is the pool specific to this vault, used for depositing
        curve = ICurveFi(_curvePool);

        // setup our rewards contract
        pid = _pid; // this is the pool ID on convex, we use this to determine what the reweardsContract address is
        address lptoken;
        (lptoken, , , rewardsContract, , ) = IConvexDeposit(depositContract)
            .poolInfo(_pid);

        // check that our LP token based on our pid matches our want
        require(address(lptoken) == address(want));

        // set our strategy's name
        stratName = _name;

        // strategy-specific approvals and paths
        eurt.approve(address(curve), type(uint256).max);
        weth.approve(uniswapv3, type(uint256).max);

        // set our paths
        crvPath = [address(crv), address(weth)];
        convexTokenPath = [address(convexToken), address(weth)];
    }

    /* ========== VARIABLE FUNCTIONS ========== */
    // these will likely change across different wants.

    function prepareReturn(uint256 _debtOutstanding)
        internal
        override
        returns (
            uint256 _profit,
            uint256 _loss,
            uint256 _debtPayment
        )
    {
        // if we have anything staked, then harvest CRV and CVX from the rewards contract
        if (claimableBalance() > 0) {
            // this claims our CRV, CVX, and any extra tokens like SNX or ANKR. set to false if these tokens don't exist, true if they do.
            IConvexRewards(rewardsContract).getReward(address(this), false);

            uint256 crvBalance = crv.balanceOf(address(this));
            uint256 convexBalance = convexToken.balanceOf(address(this));

            uint256 _sendToVoter = crvBalance.mul(keepCRV).div(FEE_DENOMINATOR);
            if (_sendToVoter > 0) {
                crv.safeTransfer(voter, _sendToVoter);
            }
            uint256 crvRemainder = crvBalance.sub(_sendToVoter);

            if (crvRemainder > 0) {
                _sellCrv(crvRemainder);
            }

            if (convexBalance > 0) {
                _sellConvex(convexBalance);
            }

            // convert our WETH to EURt
            uint256 _wethBalance = weth.balanceOf(address(this));
            uint256 _eurtBalance = 0;
            if (_wethBalance > 0) {
                _eurtBalance = _sellWethForEurt(_wethBalance);
            }

            // deposit our EURt to Curve if we have any
            if (_eurtBalance > 0) {
                curve.add_liquidity([_eurtBalance, 0], 0);
            }
        }

        // debtOustanding will only be > 0 in the event of revoking or if we need to rebalance from a withdrawal or lowering the debtRatio
        if (_debtOutstanding > 0) {
            uint256 _stakedBal = stakedBalance();
            if (_stakedBal > 0) {
                IConvexRewards(rewardsContract).withdrawAndUnwrap(
                    Math.min(_stakedBal, _debtOutstanding),
                    claimRewards
                );
            }
            uint256 _withdrawnBal = balanceOfWant();
            _debtPayment = Math.min(_debtOutstanding, _withdrawnBal);
        }

        // serious loss should never happen, but if it does (for instance, if Curve is hacked), let's record it accurately
        uint256 assets = estimatedTotalAssets();
        uint256 debt = vault.strategies(address(this)).totalDebt;

        // if assets are greater than debt, things are working great!
        if (assets > debt) {
            _profit = assets.sub(debt);
            uint256 _wantBal = balanceOfWant();
            if (_profit.add(_debtPayment) > _wantBal) {
                // this should only be hit following donations to strategy
                liquidateAllPositions();
            }
        }
        // if assets are less than debt, we are in trouble
        else {
            _loss = debt.sub(assets);
        }

        // we're done harvesting, so reset our trigger if we used it
        forceHarvestTriggerOnce = false;
    }

    // migrate our want token to a new strategy if needed, make sure to check claimRewards first
    // also send over any CRV or CVX that is claimed; for migrations we definitely want to claim
    function prepareMigration(address _newStrategy) internal override {
        uint256 _stakedBal = stakedBalance();
        if (_stakedBal > 0) {
            IConvexRewards(rewardsContract).withdrawAndUnwrap(
                _stakedBal,
                claimRewards
            );
        }
        crv.safeTransfer(_newStrategy, crv.balanceOf(address(this)));
        convexToken.safeTransfer(
            _newStrategy,
            convexToken.balanceOf(address(this))
        );
    }

    // Sells our USDT for EURt
    function _sellWethForEurt(uint256 _amount) internal returns (uint256) {
        uint256 _eurtOutput =
            IUniV3(uniswapv3).exactInput(
                IUniV3.ExactInputParams(
                    abi.encodePacked(
                        address(weth),
                        uint24(500),
                        address(usdt),
                        uint24(500),
                        address(eurt)
                    ),
                    address(this),
                    now,
                    _amount,
                    uint256(1)
                )
            );
        return _eurtOutput;
    }

    /* ========== KEEP3RS ========== */

    function harvestTrigger(uint256 callCostinEth)
        public
        view
        override
        returns (bool)
    {
        // trigger if we want to manually harvest
        if (forceHarvestTriggerOnce) {
            return true;
        }

        // harvest if we have a profit to claim
        if (claimableProfitInUsdt() > harvestProfitNeeded) {
            return true;
        }

        // Should not trigger if strategy is not active (no assets and no debtRatio). This means we don't need to adjust keeper job.
        if (!isActive()) {
            return false;
        }

        return super.harvestTrigger(callCostinEth);
    }

    // we will need to add rewards token here if we have them
    function claimableProfitInUsdt() internal view returns (uint256) {
        // calculations pulled directly from CVX's contract for minting CVX per CRV claimed
        uint256 totalCliffs = 1_000;
        uint256 maxSupply = 100 * 1_000_000 * 1e18; // 100mil
        uint256 reductionPerCliff = 100_000 * 1e18; // 100,000
        uint256 supply = convexToken.totalSupply();
        uint256 mintableCvx;

        uint256 cliff = supply.div(reductionPerCliff);
        uint256 _claimableBal = claimableBalance();
        //mint if below total cliffs
        if (cliff < totalCliffs) {
            //for reduction% take inverse of current cliff
            uint256 reduction = totalCliffs.sub(cliff);
            //reduce
            mintableCvx = _claimableBal.mul(reduction).div(totalCliffs);

            //supply cap check
            uint256 amtTillMax = maxSupply.sub(supply);
            if (mintableCvx > amtTillMax) {
                mintableCvx = amtTillMax;
            }
        }

        address[] memory crv_usd_path = new address[](3);
        crv_usd_path[0] = address(crv);
        crv_usd_path[1] = address(weth);
        crv_usd_path[2] = address(usdt);

        address[] memory cvx_usd_path = new address[](3);
        cvx_usd_path[0] = address(convexToken);
        cvx_usd_path[1] = address(weth);
        cvx_usd_path[2] = address(usdt);

        uint256 crvValue;
        if (_claimableBal > 0) {
            uint256[] memory crvSwap =
                IUniswapV2Router02(sushiswap).getAmountsOut(
                    _claimableBal,
                    crv_usd_path
                );
            crvValue = crvSwap[crvSwap.length - 1];
        }

        uint256 cvxValue;
        if (mintableCvx > 0) {
            uint256[] memory cvxSwap =
                IUniswapV2Router02(sushiswap).getAmountsOut(
                    mintableCvx,
                    cvx_usd_path
                );
            cvxValue = cvxSwap[cvxSwap.length - 1];
        }
        return crvValue.add(cvxValue);
    }

    // convert our keeper's eth cost into want
    function ethToWant(uint256 _ethAmount)
        public
        view
        override
        returns (uint256)
    {
        uint256 callCostInWant;
        if (_ethAmount > 0) {
            uint256 callCostInEur =
                oracle.ethToAsset(_ethAmount, address(eurt), 1800);
            callCostInWant = curve.calc_token_amount([callCostInEur, 0], true);
        }
        return callCostInWant;
    }
}
function prepareReturn(uint256 _debtOutstanding)
    internal
    override
    returns (
        uint256 _profit,
        uint256 _loss,
        uint256 _debtPayment
    )
{
    // if we have anything staked, then harvest CRV and CVX from the rewards contract
    if (claimableBalance() > 0) {
        // this claims our CRV, CVX, and any extra tokens like SNX or ANKR. set to false if these tokens don't exist, true if they do.
        IConvexRewards(rewardsContract).getReward(address(this), false);

        uint256 crvBalance = crv.balanceOf(address(this));
        uint256 convexBalance = convexToken.balanceOf(address(this));

        uint256 _sendToVoter = crvBalance.mul(keepCRV).div(FEE_DENOMINATOR);
        if (_sendToVoter > 0) {
            crv.safeTransfer(voter, _sendToVoter);
        }
        uint256 crvRemainder = crvBalance.sub(_sendToVoter);

        if (crvRemainder > 0) {
            _sellCrv(crvRemainder);
        }

        if (convexBalance > 0) {
            _sellConvex(convexBalance);
        }

        // convert our WETH to EURt
        uint256 _wethBalance = weth.balanceOf(address(this));
        uint256 _eurtBalance = 0;
        if (_wethBalance > 0) {
            _eurtBalance = _sellWethForEurt(_wethBalance);
        }

        // deposit our EURt to Curve if we have any
        if (_eurtBalance > 0) {
            curve.add_liquidity([_eurtBalance, 0], 0);
        }
    }

    // debtOustanding will only be > 0 in the event of revoking or if we need to rebalance from a withdrawal or lowering the debtRatio
    if (_debtOutstanding > 0) {
        uint256 _stakedBal = stakedBalance();
        if (_stakedBal > 0) {
            IConvexRewards(rewardsContract).withdrawAndUnwrap(
                Math.min(_stakedBal, _debtOutstanding),
                claimRewards
            );
        }
        uint256 _withdrawnBal = balanceOfWant();
        _debtPayment = Math.min(_debtOutstanding, _withdrawnBal);
    }

    // serious loss should never happen, but if it does (for instance, if Curve is hacked), let's record it accurately
    uint256 assets = estimatedTotalAssets();
    uint256 debt = vault.strategies(address(this)).totalDebt;

    // if assets are greater than debt, things are working great!
    if (assets > debt) {
        _profit = assets.sub(debt);
        uint256 _wantBal = balanceOfWant();
        if (_profit.add(_debtPayment) > _wantBal) {
            // this should only be hit following donations to strategy
            liquidateAllPositions();
        }
    }
    // if assets are less than debt, we are in trouble
    else {
        _loss = debt.sub(assets);
    }

    // we're done harvesting, so reset our trigger if we used it
    forceHarvestTriggerOnce = false;
}

// these will likely change across different wants.