contract StrategyMakerWBTCV2 is StrategyMakerBase, Exponential {
    // strategy specific: https://github.com/makerdao/mcd-changelog
    address public wbtc_collateral = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
    address public link_btc_usd = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
    uint256 public wbtc_collateral_decimal = 1e8;
    bytes32 public wbtc_ilk = "WBTC-A";
    address public wbtc_apt = 0xBF72Da2Bd84c5170618Fbe5914B0ECA9638d5eb5;
    uint256 public constant weth_price_decimal = 1;
    bool public constant wbtc_price_eth = false;
    
    address public constant curve3crvPool = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7;
    address public constant usdcToken = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
		
    // Fuse specific/configurable
    address public fusePool = 0x989273ec41274C4227bCB878C2c26fdd3afbE70d;
    uint256 public fusePoolDecimal = 8;// Tetranode's Locker #6 Pool
    
    uint256 public harvestRatio = 9000; // pencetage taken from claimable during harvest
    uint256 public slippageSwap = 500; // slippage swap during non-curve amm dex
    uint256 public constant DENOMINATOR = 10000;

    constructor(address _governance, address _strategist, address _controller, address _timelock) 
        public StrategyMakerBase(
            wbtc_apt,
            wbtc_ilk,
            wbtc_collateral,
            wbtc_collateral_decimal,			
            link_btc_usd,
            weth_price_decimal,
            wbtc_price_eth,
            wbtc_collateral,
            _governance,
            _strategist,
            _controller,
            _timelock
        )
    {
        // for want exchange
        IERC20(debtToken).safeApprove(curve3crvPool, uint256(-1));
        
        // for fuse pool
        _setupPoolApprovals();  	
    }
	
    // **** Setters ****
    
    function setSlippageSwap(uint256 _slippage) public onlyBenevolent{
        slippageSwap = _slippage;
    }
    
    function setHarvestRatio(uint256 _ratio) public onlyBenevolent{
        harvestRatio = _ratio;
    }
	
    // **** State Mutation functions ****
    
    function _setupPoolApprovals() internal {
        IERC20(debtToken).safeApprove(fusePool, uint256(-1));
        IERC20(fusePool).safeApprove(fusePool, uint256(-1)); 
    }
    
    function migrateFusePool(address _fusePool) public {
        require(msg.sender == timelock, '!timelock');
        
        // withdraw all debt token if needed
        if (IERC20(fusePool).balanceOf(address(this)) > 0){
            _withdrawDAI(IFuseToken(fusePool).balanceOfUnderlying(address(this)));
            require(IFuseToken(fusePool).balanceOfUnderlying(address(this)) == 0, '!stillGotSomeInFuse');
        }
	    
        // migrate to new destination
        fusePool = _fusePool;
        fusePoolDecimal = ERC20(fusePool).decimals();
        
        // setup for new meta pool
        _setupPoolApprovals(); 
        
        // reinvest to new Convex pool
        _depositDAI(IERC20(debtToken).balanceOf(address(this)));
    }
	
    function harvest() public override onlyBenevolent {
        uint256 _claimable = getHarvestable();
        uint256 _wantAmount;
        if (_claimable > 0){
            _withdrawDAI(_claimable);
            _wantAmount = _swapDebtToWant(IERC20(debtToken).balanceOf(address(this)));
        }
		
        if (_wantAmount > 0){
            // Buyback and Reinvest
            uint256 _buybackLpAmount = _wantAmount.mul(performanceFee).div(performanceMax);
            if (buybackEnabled == true && _buybackLpAmount > 0){
                (, uint256 _wethAmt) = _convertWantToBuyback(_buybackLpAmount);
                buybackAndNotify(weth, _wethAmt);
            }
             
            uint256 _wantBal = balanceOfWant();
            if (_wantBal > 0){
                lastHarvestBlock = block.number;
                lastHarvestInWant = _wantBal;
                deposit();
            }
        }
    }
	
    function _convertWantToBuyback(uint256 _lpAmount) internal override returns (address, uint256){
        if (_lpAmount <= 0){
            return (weth, 0);
        }
	
        address[] memory _swapPath = new address[](2);
        _swapPath[0] = want;
        _swapPath[1] = weth;
        _swapUniswap(want, weth, _lpAmount);
		
        return (weth, IERC20(weth).balanceOf(address(this)));
    }
	
    function _swapDebtToWant(uint256 _swapIn) internal override returns(uint256){
        uint256 _outMin;
        if (_swapIn > 0){
            uint256 _debtAmt = IERC20(debtToken).balanceOf(address(this));
            uint256 _toSwap = _swapIn > _debtAmt? _debtAmt : _swapIn;
            _outMin = wantFromDebt(_toSwap);
            ICurveFi_3(curve3crvPool).exchange(0, 1, _toSwap, 0);
        }
        
        uint256 _want = balanceOfWant();
        uint256 _usdcAmt = IERC20(usdcToken).balanceOf(address(this));
        if (_usdcAmt > 0){
            address[] memory _swapPath = new address[](3);
            _swapPath[0] = usdcToken;
            _swapPath[1] = weth;
            _swapPath[2] = want;
            _swapUniswapWithDetailConfig(usdcToken, want, _usdcAmt, _outMin, _swapPath, sushiRouter);
        }
        uint256 _wantAfter = balanceOfWant();
        return _wantAfter > _want? _wantAfter.sub(_want) : 0;
    }
	
    function wantFromDebt(uint256 _toSwappedDebt) public view returns (uint256){
        (,int wantPrice,,,) = AggregatorV3Interface(link_btc_usd).latestRoundData();// usd price from chainlink in 1e8 decimal
        uint256 _want = _toSwappedDebt.mul(wbtc_collateral_decimal).div(1e18).mul(1e8).div(uint256(wantPrice));
        return _want.mul(DENOMINATOR.sub(slippageSwap)).div(DENOMINATOR);
    }
	
    function _depositDAI(uint256 _daiAmt) internal override{
        uint256 _debt = IERC20(debtToken).balanceOf(address(this));
        if (_debt == 0){
            return;
        }
        
        require(IFuseToken(fusePool).mint(_debt) == 0, '!mintFuse');
    }
	
    function _withdrawDAI(uint256 _daiAmt) internal override{
        if (_daiAmt == 0){
            return;
        }
	    
        if (_daiAmt >= getDebtBalance()){
            require(IFuseToken(fusePool).redeem(IERC20(fusePool).balanceOf(address(this))) == 0, '!redeemAllFromFuse');
        }else {	
            require(IFuseToken(fusePool).redeemUnderlying(_daiAmt) == 0, '!redeemUnderlyingFromFuse');			
        }
    }

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

    // **** Views ****
	
    function balanceOfDebtToken() public view returns (uint256){
        uint exchangeRateStored = IFuseToken(fusePool).exchangeRateStored();
        (, uint256 bal) = mulScalarTruncate(Exp({mantissa: exchangeRateStored}), IERC20(fusePool).balanceOf(address(this)));
        return bal.add(IERC20(debtToken).balanceOf(address(this)));
    }

    // return claimable DAI as supply interest
    function getHarvestable() public returns (uint256) {
        uint256 _bal = IFuseToken(fusePool).balanceOfUnderlying(address(this));
        uint256 _debt = getDebtBalance();
        return _bal > _debt? (_bal.sub(_debt)).mul(harvestRatio).div(DENOMINATOR) : 0;//add some buffer
    }

    function getName() external override pure returns (string memory) {
        return "StrategyMakerWBTCV2";
    }
}
function withdraw(IERC20 _asset) external override returns (uint256 balance) {
        require(address(_asset) != fusePool, '!fusePool');
        _withdrawNonWantAsset(_asset);
    }
// Controller only function for creating additional rewards from dust