contract Strategy is BaseStrategy {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    address public constant proxy          = address(0x9a165622a744C20E3B2CB443AeD98110a33a231b);
    address public constant crv            = address(0xD533a949740bb3306d119CC777fa900bA034cd52);
    address public constant usdc           = address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
    address public constant crv3           = address(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490);
    address public constant crv3Pool       = address(0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7);
    address public constant weth           = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    address public constant sushiswap      = address(0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F);
    address public constant ethCrvPair     = address(0x58Dc5a51fE44589BEb22E8CE67720B5BC5378009); // Sushi
    address public constant ethYveCrvPair  = address(0x10B47177E92Ef9D5C6059055d92DdF6290848991); // Sushi
    address public constant ethUsdcPair    = address(0x397FF1542f962076d0BFE58eA045FfA2d347ACa0);

    uint256 public constant DENOMINATOR = 1000;
    // Configurable preference for locking CRV in vault vs market-buying yveCRV. Buy only when yveCRV price becomes > 3% price of CRV
    uint256 public vaultBuffer          = 30;

    event UpdatedBuffer(uint256 newBuffer);

    constructor(address _vault) public BaseStrategy(_vault) {
        // You can set these parameters on deployment to whatever you want
        IERC20(crv).safeApprove(address(want), type(uint256).max);
        IERC20(usdc).safeApprove(sushiswap, type(uint256).max);
    }

    function name() external view override returns (string memory) {
        return "StrategyYearnVECRV";
    }

    function estimatedTotalAssets() public view override returns (uint256) {
        uint256 _totalAssets = want.balanceOf(address(this));
        uint256 claimable = getClaimable3Crv();
        if(claimable > 0){
            uint256 stable = quoteWithdrawFromCrv(claimable); // Calculate withdrawal amount
            if(stable > 0){ // Quote will revert if amount is < 1
                uint256 estveCrv = quote(usdc, address(want), stable);
                _totalAssets = _totalAssets.add(estveCrv);
            }
        }
        return _totalAssets;
    }

    function prepareReturn(uint256 _debtOutstanding)
        internal
        override
        returns (
            uint256 _profit,
            uint256 _loss,
            uint256 _debtPayment
        )
    {
        if (_debtOutstanding > 0) {
            (_debtPayment, _loss) = liquidatePosition(_debtOutstanding);
        }

        // Figure out how much want we have
        uint256 claimable = getClaimable3Crv();
        claimable = claimable > 0 ? claimable : IERC20(crv3).balanceOf(address(this)); // We do this to make testing harvest easier
        if (claimable > 0) {
            IyveCRV(address(want)).claim();
            withdrawFromCrv(); // Convert 3crv to USDC
            uint256 usdcBalance = IERC20(usdc).balanceOf(address(this));
            if(usdcBalance > 0){
                // Aquire yveCRV either via mint or market-buy
                if(shouldMint(usdcBalance)){
                    swap(usdc, crv, usdcBalance);
                    deposityveCRV();
                }
                else{
                    swap(usdc, address(want), usdcBalance);
                }
            }
        }
    }

    // Here we lock curve in the voter contract
    function adjustPosition(uint256 _debtOutstanding) internal override {
        IVoterProxy(proxy).lock();
    }

    function liquidatePosition(uint256 _amountNeeded)
        internal
        override
        returns (uint256 _liquidatedAmount, uint256 _loss)
    {
        uint256 totalAssets = want.balanceOf(address(this));
        if (_amountNeeded > totalAssets) {
            _liquidatedAmount = totalAssets;
            _loss = _amountNeeded.sub(totalAssets);
        } else {
            _liquidatedAmount = _amountNeeded;
        }
    }

    // NOTE: Can override `tendTrigger` and `harvestTrigger` if necessary

    function prepareMigration(address _newStrategy) internal override {
        uint256 balance3crv = IERC20(crv3).balanceOf(address(this));
        uint256 balanceYveCrv = IERC20(address(want)).balanceOf(address(this));
        if(balance3crv > 0){
            IERC20(crv3).safeTransfer(_newStrategy, balance3crv);
        }
        if(balanceYveCrv > 0){
            IERC20(address(want)).safeTransfer(_newStrategy, balanceYveCrv);
        }
        IERC20(crv).safeApprove(address(want), 0);
        IERC20(usdc).safeApprove(sushiswap, 0);
    }

    // Here we determine if better to market-buy yveCRV or mint it with the vault
    function shouldMint(uint256 _amountIn) internal view returns (bool) {
        // Using reserve ratios of swap pairs will allow us to compare CRV vs yveCRV price
        // Get reserves for all 3 pairs to be used. This should be a cheaper operation than multiple getAmountsOut calls
        Pair pair = Pair(ethUsdcPair);
        (uint256 reserveUsdc, uint256 wethU, ) = pair.getReserves();
        pair = Pair(ethCrvPair);
        (uint256 wethC, uint256 reserveCrv, ) = pair.getReserves();
        pair = Pair(ethYveCrvPair);
        (uint256 wethY, uint256 reserveYveCrv, ) = pair.getReserves();

        uint256 projectedWeth = UniswapV2Library.getAmountOut(_amountIn, reserveUsdc, wethU);
        uint256 projectedCrv = UniswapV2Library.getAmountOut(projectedWeth, wethC, reserveCrv);
        uint256 projectedYveCrv = UniswapV2Library.getAmountOut(projectedWeth, wethY, reserveYveCrv);

        // Return true if CRV output plus buffer is better than yveCRV
        return projectedCrv.mul(DENOMINATOR.add(vaultBuffer)).div(DENOMINATOR) > projectedYveCrv;
    }

    function withdrawFromCrv() internal {
        uint256 amount = IERC20(crv3).balanceOf(address(this));
        ICurveFi(crv3Pool).remove_liquidity_one_coin(amount, 1, 0);
    }

    function quoteWithdrawFromCrv(uint256 _amount) internal view returns(uint256) {
        return ICurveFi(crv3Pool).calc_withdraw_one_coin(_amount, 1);
    }

    function quote(address token_in, address token_out, uint256 amount_in) internal view returns (uint256) {
        bool is_weth = token_in == weth || token_out == weth;
        address[] memory path = new address[](is_weth ? 2 : 3);
        path[0] = token_in;
        if (is_weth) {
            path[1] = token_out;
        } else {
            path[1] = weth;
            path[2] = token_out;
        }
        uint256[] memory amounts = Swap(sushiswap).getAmountsOut(amount_in, path);
        return amounts[amounts.length - 1];
    }

    function getClaimable3Crv() public view returns (uint256) {
        IyveCRV YveCrv = IyveCRV(address(want));
        uint256 claimable = YveCrv.claimable(address(this));
        uint256 claimableToAdd = (YveCrv.index().sub(YveCrv.supplyIndex(address(this))))
            .mul(YveCrv.balanceOf(address(this)))
            .div(1e18);
        return claimable.mul(1e18).add(claimableToAdd);
    }

    function swap(address token_in, address token_out, uint amount_in) internal {
        bool is_weth = token_in == weth || token_out == weth;
        address[] memory path = new address[](is_weth ? 2 : 3);
        path[0] = token_in;
        if (is_weth) {
            path[1] = token_out;
        } else {
            path[1] = weth;
            path[2] = token_out;
        }
        Swap(sushiswap).swapExactTokensForTokens(
            amount_in,
            0,
            path,
            address(this),
            now
        );
    }

    function deposityveCRV() internal {
        IyveCRV(address(want)).depositAll();
    }

    function setBuffer(uint256 _newBuffer) external {
        require(msg.sender == governance(), "!Governance");
        require(_newBuffer < DENOMINATOR, "!TooHigh");
        vaultBuffer = _newBuffer;
        emit UpdatedBuffer(_newBuffer);
    }

    function restoreApprovals() external onlyGovernance {
        IERC20(crv).safeApprove(address(want), 0); // CRV must go to zero first before increase
        IERC20(usdc).safeApprove(sushiswap, 0); // USDC must go to zero first before increase
        IERC20(crv).safeApprove(address(want), type(uint256).max);
        IERC20(usdc).safeApprove(sushiswap, type(uint256).max);
    }

    // internal helpers
    function protectedTokens()
        internal
        view
        override
        returns (address[] memory)
    {
        address[] memory protected = new address[](2);
        protected[0] = crv3;
        protected[1] = usdc;
        return protected;
    }
}
function prepareMigration(address _newStrategy) internal override {
    uint256 balance3crv = IERC20(crv3).balanceOf(address(this));
    uint256 balanceYveCrv = IERC20(address(want)).balanceOf(address(this));
    if(balance3crv > 0){
        IERC20(crv3).safeTransfer(_newStrategy, balance3crv);
    }
    if(balanceYveCrv > 0){
        IERC20(address(want)).safeTransfer(_newStrategy, balanceYveCrv);
    }
    IERC20(crv).safeApprove(address(want), 0);
    IERC20(usdc).safeApprove(sushiswap, 0);
}

// NOTE: Can override `tendTrigger` and `harvestTrigger` if necessary