contract StrategyUnitRenbtcV1 is StrategyUnitBase {
    // strategy specific
    address public constant renbtc_collateral = 0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D;
    uint256 public constant renbtc_collateral_decimal = 1e8;
    address public constant renbtc_oracle = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
    uint256 public constant renbtc_price_decimal = 1;
    bool public constant renbtc_price_eth = false;
	
    // farming in usdp3crv 
    address public constant usdp3crv = 0x7Eb40E450b9655f4B3cC4259BCC731c63ff55ae6;
    address public constant usdp = 0x1456688345527bE1f37E9e627DA0837D6f08C925;
    address public constant usdp_gauge = 0x055be5DDB7A925BfEF3417FC157f53CA77cA7222;
    address public constant curvePool = 0x42d7025938bEc20B69cBae5A77421082407f053A;
    address public constant mintr = 0xd061D61a4d941c39E5453435B6345Dc261C2fcE0;
    
    // slippage protection for one-sided ape in/out
    uint256 public slippageProtectionIn = 50; // max 0.5%
    uint256 public slippageProtectionOut = 50; // max 0.5%
    uint256 public constant DENOMINATOR = 10000;

    constructor(address _governance, address _strategist, address _controller, address _timelock) 
        public StrategyUnitBase(
            renbtc_collateral,
            renbtc_collateral_decimal,
            renbtc_oracle,
            renbtc_price_decimal,
            renbtc_price_eth,
            renbtc_collateral,
            _governance,
            _strategist,
            _controller,
            _timelock
        )
    {
        // approve for Curve pool and DEX
        IERC20(usdp).safeApprove(curvePool, uint256(-1));
        IERC20(usdp3crv).safeApprove(curvePool, uint256(-1));
        
        IERC20(usdp3crv).safeApprove(usdp_gauge, uint256(-1));
        
        IERC20(crv).safeApprove(univ2Router2, uint256(-1));
        IERC20(weth).safeApprove(univ2Router2, uint256(-1));
        IERC20(renbtc_collateral).safeApprove(univ2Router2, uint256(-1));
        IERC20(usdcBuyback).safeApprove(univ2Router2, uint256(-1));
    }
	
    // **** Setters ****	
	
    function setSlippageProtectionIn(uint256 _slippage) external onlyGovernanceAndStrategist{
        slippageProtectionIn = _slippage;
    }
	
    function setSlippageProtectionOut(uint256 _slippage) external onlyGovernanceAndStrategist{
        slippageProtectionOut = _slippage;
    }
	
    // **** State Mutation functions ****	

    function getHarvestable() external returns (uint256) {
        return ICurveGauge(usdp_gauge).claimable_tokens(address(this));
    }

    function _convertWantToBuyback(uint256 _lpAmount) internal override returns (address, uint256){
        _swapUniswap(renbtc_collateral, usdcBuyback, _lpAmount);
        return (usdcBuyback, IERC20(usdcBuyback).balanceOf(address(this)));
    }	
	
    function harvest() public override onlyBenevolent {

        // Collects crv tokens
        ICurveMintr(mintr).mint(usdp_gauge);
        uint256 _crv = IERC20(crv).balanceOf(address(this));
        if (_crv > 0) {
            _swapUniswap(crv, weth, _crv);
        }

        // buyback $MM
        uint256 _to = IERC20(weth).balanceOf(address(this));
        uint256 _buybackAmount = _to.mul(performanceFee).div(performanceMax);		
        if (buybackEnabled == true && _buybackAmount > 0) {
            buybackAndNotify(weth, _buybackAmount);
        }
		
        // re-invest to compounding profit
        _swapUniswap(weth, want, IERC20(weth).balanceOf(address(this)));
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            lastHarvestBlock = block.number;
            lastHarvestInWant = _want;
            deposit();
        }
    }
	
    function _depositUSDP(uint256 _usdpAmt) internal override{	
        if (_usdpAmt > 0 && checkSlip(_usdpAmt)) {
            uint256[2] memory amounts = [_usdpAmt, 0]; 
            ICurveFi_2(curvePool).add_liquidity(amounts, 0);
        }
		
        uint256 _usdp3crv = IERC20(usdp3crv).balanceOf(address(this));
        if (_usdp3crv > 0){
            ICurveGauge(usdp_gauge).deposit(_usdp3crv);		
        }
    }
	
    function _withdrawUSDP(uint256 _usdpAmt) internal override {	
        uint256 _requiredUsdp3crv = estimateRequiredUsdp3crv(_usdpAmt);
        _requiredUsdp3crv = _requiredUsdp3crv.mul(DENOMINATOR.add(slippageProtectionOut)).div(DENOMINATOR);// try to remove bit more
		
        uint256 _usdp3crv = IERC20(usdp3crv).balanceOf(address(this));
        uint256 _withdrawFromGauge = _usdp3crv < _requiredUsdp3crv? _requiredUsdp3crv.sub(_usdp3crv) : 0;
			
        if (_withdrawFromGauge > 0){
            uint256 maxInGauge = ICurveGauge(usdp_gauge).balanceOf(address(this));
            ICurveGauge(usdp_gauge).withdraw(maxInGauge < _withdrawFromGauge? maxInGauge : _withdrawFromGauge);			
        }
		    	
        _usdp3crv = IERC20(usdp3crv).balanceOf(address(this));
        if (_usdp3crv > 0){
            _requiredUsdp3crv = _requiredUsdp3crv > _usdp3crv?  _usdp3crv : _requiredUsdp3crv;
            uint256 maxSlippage = _requiredUsdp3crv.mul(DENOMINATOR.sub(slippageProtectionOut)).div(DENOMINATOR);
            ICurveFi_2(curvePool).remove_liquidity_one_coin(_requiredUsdp3crv, 0, maxSlippage);			
        }
    }

    // Controller only function for creating additional rewards from dust
    function withdraw(IERC20 _asset) external override returns (uint256 balance) {
        require(usdp3crv != address(_asset), "!usdp3crv");
        require(usdp != address(_asset), "!usdp");
        return _withdrawNonWantAsset(_asset);
    }

    // **** Views ****

    function virtualPriceToWant() public view returns (uint256) {
        return ICurveFi_2(curvePool).get_virtual_price();
    }
	
    function estimateRequiredUsdp3crv(uint256 _usdpAmt) public view returns (uint256) {
        uint256[2] memory amounts = [_usdpAmt, 0]; 
        return ICurveFi_2(curvePool).calc_token_amount(amounts, false);
    }
	
    function checkSlip(uint256 _usdpAmt) public view returns (bool){
        uint256 expectedOut = _usdpAmt.mul(1e18).div(virtualPriceToWant());
        uint256 maxSlip = expectedOut.mul(DENOMINATOR.sub(slippageProtectionIn)).div(DENOMINATOR);

        uint256[2] memory amounts = [_usdpAmt, 0]; 
        return ICurveFi_2(curvePool).calc_token_amount(amounts, true) >= maxSlip;
    }

    function getName() external override pure returns (string memory) {
        return "StrategyUnitRenbtcV1";
    }
}
function getHarvestable() external returns (uint256) {
    return ICurveGauge(usdp_gauge).claimable_tokens(address(this));
}

// **** State Mutation functions ****	