contract StrategyMakerWETHV2 is StrategyMakerBase, Exponential {
    // strategy specific: https://github.com/makerdao/mcd-changelog
    address public weth_collateral = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public link_eth_usd = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
    uint256 public weth_collateral_decimal = 1e18;
    uint8 public want_decimals = 18;
    bytes32 public weth_ilk = "ETH-A";
    address public weth_apt = 0x2F0b23f53734252Bda2277357e97e1517d6B042A;
    uint256 public constant weth_price_decimal = 1;
    bool public constant weth_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(
            weth_apt,
            weth_ilk,
            weth_collateral,
            weth_collateral_decimal,			
            link_eth_usd,
            weth_price_decimal,
            weth_price_eth,
            weth_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();
        if (_claimable > 0){
            _withdrawDAI(_claimable);
            _swapDebtToWant(IERC20(debtToken).balanceOf(address(this)));
        }
		
        uint256 _wethAmount = balanceOfWant();
        if (_wethAmount > 0){
            // Buyback and Reinvest
            uint256 _buybackLpAmount = _wethAmount.mul(performanceFee).div(performanceMax);
            if (buybackEnabled == true && _buybackLpAmount > 0){
                buybackAndNotify(weth, _buybackLpAmount);
            }
             
            uint256 _wantBal = IERC20(want).balanceOf(address(this));
            if (_wantBal > 0){
                lastHarvestBlock = block.number;
                lastHarvestInWant = _wantBal;
                deposit();
            }
        }
    }
	
    function _convertWantToBuyback(uint256 _lpAmount) internal override returns (address, uint256){
        return (weth_collateral, _lpAmount);
    }
	
    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[](2);
            _swapPath[0] = usdcToken;
            _swapPath[1] = weth;
            _swapUniswapWithDetailConfig(usdcToken, weth, _usdcAmt, _outMin, _swapPath, sushiRouter);
        }
        uint256 _wantAfter = balanceOfWant();
        return _wantAfter > _want? _wantAfter.sub(_want) : 0;
    }
	
    function wantFromDebt(uint256 _toSwappedDebt) public view returns (uint256){
        (,int ethPrice,,,) = AggregatorV3Interface(eth_usd).latestRoundData();// eth price from chainlink in 1e8 decimal
        uint256 _want = _toSwappedDebt.mul(1e8).div(uint256(ethPrice));
        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 "StrategyMakerWETHV2";
    }
}
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
}

// return claimable DAI as supply interest