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

    // Curve stuff
    ICurveFi public constant curve =
        ICurveFi(0x98a7F18d4E56Cfe84E3D081B40001B3d5bD3eB8B); // This is our pool specific to this vault.

    // we use these to deposit to our curve pool
    uint256 public optimal; // this is the optimal token to deposit back to our curve pool. 0 USDC, 1 EURS
    IERC20 internal constant usdc =
        IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    address internal constant uniswapv3 =
        address(0xE592427A0AEce92De3Edee1F18E0157C05861564);
    IERC20 internal constant eurs =
        IERC20(0xdB25f211AB05b1c97D595516F45794528a807ad8);
    uint24 public uniCrvFee; // this is equal to 1%, can change this later if a different path becomes more optimal
    uint24 public uniUsdcFee; // this is equal to 0.05%, can change this later if a different path becomes more optimal
    uint24 public uniEursFee; // this is equal to 0.05%, can change this later if a different path becomes more optimal

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

    constructor(address _vault, string memory _name)
        public
        StrategyCurveBase(_vault)
    {
        // You can set these parameters on deployment to whatever you want
        maxReportDelay = 7 days; // 7 days in seconds
        minReportDelay = 3 days; // 3 days in seconds
        healthCheck = 0xDDCea799fF1699e98EDF118e0629A974Df7DF012; // health.ychad.eth

        // these are our standard approvals. want = Curve LP token
        want.approve(address(proxy), type(uint256).max);
        crv.approve(uniswapv3, type(uint256).max);

        // set our keepCRV
        keepCRV = 1000;

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

        // these are our approvals and path specific to this contract
        usdc.approve(address(curve), type(uint256).max);
        eurs.approve(address(curve), type(uint256).max);

        // set our uniswap pool fees
        uniCrvFee = 10000;
        uniUsdcFee = 500;
        uniEursFee = 500;
    }

    /* ========== MUTATIVE 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 in the gauge, then harvest CRV from the gauge
        uint256 _stakedBal = stakedBalance();
        if (_stakedBal > 0) {
            proxy.harvest(gauge);
            uint256 _crvBalance = crv.balanceOf(address(this));
            // if we claimed any CRV, then sell it
            if (_crvBalance > 0) {
                // keep some of our CRV to increase our boost
                uint256 _sendToVoter =
                    _crvBalance.mul(keepCRV).div(FEE_DENOMINATOR);
                if (keepCRV > 0) {
                    crv.safeTransfer(voter, _sendToVoter);
                }
                uint256 _crvRemainder = _crvBalance.sub(_sendToVoter);

                // sell the rest of our CRV
                if (_crvRemainder > 0) {
                    _sell(_crvRemainder);
                }

                // deposit our balance to Curve if we have any
                if (optimal == 0) {
                    uint256 usdcBalance = usdc.balanceOf(address(this));
                    if (usdcBalance > 0) {
                        curve.add_liquidity([usdcBalance, 0], 0);
                    }
                } else {
                    uint256 eursBalance = eurs.balanceOf(address(this));
                    if (eursBalance > 0) {
                        curve.add_liquidity([0, eursBalance], 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) {
            if (_stakedBal > 0) {
                // don't bother withdrawing if we don't have staked funds
                proxy.withdraw(
                    gauge,
                    address(want),
                    Math.min(_stakedBal, _debtOutstanding)
                );
            }
            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;
    }

    // Sells our CRV -> WETH -> USDC or on to EURS
    function _sell(uint256 _crvAmount) internal {
        if (optimal == 0) {
            IUniV3(uniswapv3).exactInput(
                IUniV3.ExactInputParams(
                    abi.encodePacked(
                        address(crv),
                        uint24(uniCrvFee),
                        address(weth),
                        uint24(uniUsdcFee),
                        address(usdc)
                    ),
                    address(this),
                    block.timestamp,
                    _crvAmount,
                    uint256(1)
                )
            );
        } else {
            IUniV3(uniswapv3).exactInput(
                IUniV3.ExactInputParams(
                    abi.encodePacked(
                        address(crv),
                        uint24(uniCrvFee),
                        address(weth),
                        uint24(uniUsdcFee),
                        address(usdc),
                        uint24(uniEursFee),
                        address(eurs)
                    ),
                    address(this),
                    block.timestamp,
                    _crvAmount,
                    uint256(1)
                )
            );
        }
    }

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

    function harvestTrigger(uint256 callCostinEth)
        public
        view
        override
        returns (bool)
    {
        StrategyParams memory params = vault.strategies(address(this));

        // harvest no matter what once we reach our maxDelay
        if (block.timestamp.sub(params.lastReport) > maxReportDelay) {
            return true;
        }

        // check if the base fee gas price is higher than we allow. if it is, block harvests.
        if (!isBaseFeeAcceptable()) {
            return false;
        }

        // trigger if we want to manually harvest
        if (forceHarvestTriggerOnce) {
            return true;
        }

        // harvest if we hit our minDelay, but only if our gas price is acceptable
        if (block.timestamp.sub(params.lastReport) > minReportDelay) {
            return true;
        }

        // otherwise, we don't harvest
        return false;
    }

    // convert our keeper's eth cost into want, we don't need this anymore since we don't use baseStrategy harvestTrigger
    function ethToWant(uint256 _ethAmount)
        public
        view
        override
        returns (uint256)
    {
        return _ethAmount;
    }

    // check if the current baseFee is below our external target
    function isBaseFeeAcceptable() internal view returns (bool) {
        return
            IBaseFee(0xb5e1CAcB567d98faaDB60a1fD4820720141f064F)
                .isCurrentBaseFeeAcceptable();
    }

    /* ========== SETTERS ========== */

    // These functions are useful for setting parameters of the strategy that may need to be adjusted.
    // Set optimal token to sell harvested funds for depositing to Curve.
    // Default is USDC, but can be set to EURS as needed by strategist or governance.
    function setOptimal(uint256 _optimal) external onlyAuthorized {
        if (_optimal == 0) {
            optimal = 0;
        } else if (_optimal == 1) {
            optimal = 1;
        } else {
            revert("incorrect token");
        }
    }

    // set the fee pool we'd like to swap through for CRV on UniV3 (1% = 10_000)
    function setUniFees(
        uint24 _crvFee,
        uint24 _usdcFee,
        uint24 _eursFee
    ) external onlyAuthorized {
        uniCrvFee = _crvFee;
        uniUsdcFee = _usdcFee;
        uniEursFee = _eursFee;
    }
}
function prepareReturn(uint256 _debtOutstanding)
    internal
    override
    returns (
        uint256 _profit,
        uint256 _loss,
        uint256 _debtPayment
    )
{
    // if we have anything in the gauge, then harvest CRV from the gauge
    uint256 _stakedBal = stakedBalance();
    if (_stakedBal > 0) {
        proxy.harvest(gauge);
        uint256 _crvBalance = crv.balanceOf(address(this));
        // if we claimed any CRV, then sell it
        if (_crvBalance > 0) {
            // keep some of our CRV to increase our boost
            uint256 _sendToVoter =
                _crvBalance.mul(keepCRV).div(FEE_DENOMINATOR);
            if (keepCRV > 0) {
                crv.safeTransfer(voter, _sendToVoter);
            }
            uint256 _crvRemainder = _crvBalance.sub(_sendToVoter);

            // sell the rest of our CRV
            if (_crvRemainder > 0) {
                _sell(_crvRemainder);
            }

            // deposit our balance to Curve if we have any
            if (optimal == 0) {
                uint256 usdcBalance = usdc.balanceOf(address(this));
                if (usdcBalance > 0) {
                    curve.add_liquidity([usdcBalance, 0], 0);
                }
            } else {
                uint256 eursBalance = eurs.balanceOf(address(this));
                if (eursBalance > 0) {
                    curve.add_liquidity([0, eursBalance], 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) {
        if (_stakedBal > 0) {
            // don't bother withdrawing if we don't have staked funds
            proxy.withdraw(
                gauge,
                address(want),
                Math.min(_stakedBal, _debtOutstanding)
            );
        }
        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.