contract StrategyCmpdWbtcV1 is StrategyBase, Exponential, IFlashLoanReceiver {
    address public constant comptroller = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B;
    address public constant lens = 0xd513d22422a3062Bd342Ae374b4b9c20E0a9a074;
    address public constant wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
    address public constant comp = 0xc00e94Cb662C3520282E6f5717214004A7f26888;
    address public constant cwbtc = 0xC11b1268C1A384e55C48c2391d8d480264A3A7F4;
    address public constant cether = 0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5;
    IChiToken public constant chi = IChiToken(0x0000000000004946c0e9F43F4Dee607b0eF1fA1c);
	
    // whether to use Mushrooms Flash Loan in leverage/deleverage
    address public mwbtc = 0xb06661A221Ab2Ec615531f9632D6Dc5D2984179A;
    bool public enableMushroomsFlashloan = true;

    // Require a 0.1 buffer between
    // market collateral factor and strategy's collateral factor
    // when leveraging
    uint256 public colFactorLeverageBuffer = 100;
    uint256 colFactorLeverageBufferMax = 1000;

    // Allow a 0.05 buffer
    // between market collateral factor and strategy's collateral factor
    // until we have to deleverage
    // This is so we can hit max leverage and keep accruing interest
    uint256 public colFactorSyncBuffer = 50;
    uint256 colFactorSyncBufferMax = 1000;
	
    // 1 -> use flash loan to leverage
    bytes public constant flashLeverageFlag = abi.encodePacked("1");
    // 2 -> use flash loan to deleverage
    bytes public constant flashDeleverageFlag = abi.encodePacked("2");

    // Keeper bots
    // Maintain leverage within buffer
    mapping(address => bool) public keepers;
    
    event FlashLoanLeverage(uint256 _amount, uint256 _fee, bytes _data, string lender);
    event FlashLoanDeleverage(uint256 _amount, uint256 _fee, bytes _data, string lender);

    constructor(
        address _governance,
        address _strategist,
        address _controller,
        address _timelock
    )
        public
        StrategyBase(wbtc, _governance, _strategist, _controller, _timelock)
    {
        // Enter cWBTC Market
        address[] memory ctokens = new address[](1);
        ctokens[0] = cwbtc;
        IComptroller(comptroller).enterMarkets(ctokens);
    }

    // **** Modifiers **** //

    modifier onlyKeepers {
        require(
            keepers[msg.sender] ||
                msg.sender == address(this) ||
                msg.sender == strategist ||
                msg.sender == governance,
            "!keepers"
        );
        _;
    }
	
    modifier onlyGovernanceAndStrategist {
        require(msg.sender == governance || msg.sender == strategist, "!governance");
        _;
    }

    // **** Views **** //

    function getName() external override pure returns (string memory) {
        return "StrategyCmpdWbtcV1";
    }

    function getSuppliedView() public view returns (uint256) {
        (, uint256 cTokenBal, , uint256 exchangeRate) = ICToken(cwbtc).getAccountSnapshot(address(this));

        (, uint256 bal) = mulScalarTruncate(Exp({mantissa: exchangeRate}), cTokenBal);

        return bal;
    }

    function getBorrowedView() public view returns (uint256) {
        return ICToken(cwbtc).borrowBalanceStored(address(this));
    }

    function balanceOfPool() public override view returns (uint256) {
        uint256 supplied = getSuppliedView();
        uint256 borrowed = getBorrowedView();
        return supplied.sub(borrowed);
    }

    // Given an unleveraged supply balance, return the target
    // leveraged supply balance which is still within the safety buffer
    function getLeveragedSupplyTarget(uint256 supplyBalance)
        public
        view
        returns (uint256)
    {
        uint256 leverage = getMaxLeverage();
        return supplyBalance.mul(leverage).div(1e18);
    }

    function getSafeLeverageColFactor() public view returns (uint256) {
        uint256 colFactor = getMarketColFactor();

        // Collateral factor within the buffer
        uint256 safeColFactor = colFactor.sub(colFactorLeverageBuffer.mul(1e18).div(colFactorLeverageBufferMax));

        return safeColFactor;
    }

    function getSafeSyncColFactor() public view returns (uint256) {
        uint256 colFactor = getMarketColFactor();

        // Collateral factor within the buffer
        uint256 safeColFactor = colFactor.sub(colFactorSyncBuffer.mul(1e18).div(colFactorSyncBufferMax));

        return safeColFactor;
    }

    function getMarketColFactor() public view returns (uint256) {
        (, uint256 colFactor) = IComptroller(comptroller).markets(cwbtc);

        return colFactor;
    }

    // Max leverage we can go up to, w.r.t safe buffer
    function getMaxLeverage() public view returns (uint256) {
        uint256 safeLeverageColFactor = getSafeLeverageColFactor();

        // Infinite geometric series
        uint256 leverage = uint256(1e36).div(1e18 - safeLeverageColFactor);
        return leverage;
    }

    // If we have a strategy position at this SOS borrow rate and left unmonitored for 24+ hours, we might get liquidated
    // To safeguard with enough buffer, we divide the borrow rate by 2 which indicates allowing 48 hours response time
    function getSOSBorrowRate() public view returns (uint256) {
        uint256 safeColFactor = getSafeLeverageColFactor();
        return (colFactorLeverageBuffer.mul(182).mul(1e20).div(colFactorLeverageBufferMax)).div(safeColFactor);
    }

    // **** Pseudo-view functions (use `callStatic` on these) **** //
    /* The reason why these exists is because of the nature of the
       interest accruing supply + borrow balance. The "view" methods
       are technically snapshots and don't represent the real value.
       As such there are pseudo view methods where you can retrieve the
       results by calling `callStatic`.
    */

    function getCompAccrued() public returns (uint256) {
        (, , , uint256 accrued) = ICompoundLens(lens).getCompBalanceMetadataExt(comp, comptroller, address(this));

        return accrued;
    }

    function getColFactor() public returns (uint256) {
        uint256 supplied = getSupplied();
        uint256 borrowed = getBorrowed();

        return borrowed.mul(1e18).div(supplied);
    }

    function getSuppliedUnleveraged() public returns (uint256) {
        uint256 supplied = getSupplied();
        uint256 borrowed = getBorrowed();

        return supplied.sub(borrowed);
    }

    function getSupplied() public returns (uint256) {
        return ICToken(cwbtc).balanceOfUnderlying(address(this));
    }

    function getBorrowed() public returns (uint256) {
        return ICToken(cwbtc).borrowBalanceCurrent(address(this));
    }

    function getBorrowable() public returns (uint256) {
        uint256 supplied = getSupplied();
        uint256 borrowed = getBorrowed();

        (, uint256 colFactor) = IComptroller(comptroller).markets(cwbtc);

        // 99.99% just in case some dust accumulates
        return supplied.mul(colFactor).div(1e18).sub(borrowed).mul(9999).div(10000);
    }

    function getCurrentLeverage() public returns (uint256) {
        uint256 supplied = getSupplied();
        uint256 borrowed = getBorrowed();

        return supplied.mul(1e18).div(supplied.sub(borrowed));
    }

    // **** Setters **** //

    function addKeeper(address _keeper) public onlyGovernanceAndStrategist {
        keepers[_keeper] = true;
    }

    function removeKeeper(address _keeper) public onlyGovernanceAndStrategist {
        keepers[_keeper] = false;
    }

    function setColFactorLeverageBuffer(uint256 _colFactorLeverageBuffer) public onlyGovernanceAndStrategist {
        colFactorLeverageBuffer = _colFactorLeverageBuffer;
    }

    function setColFactorSyncBuffer(uint256 _colFactorSyncBuffer) public onlyGovernanceAndStrategist {
        colFactorSyncBuffer = _colFactorSyncBuffer;
    }

    function setEnableMushroomsFlashloan(bool _enableMushroomsFlashloan) public onlyGovernanceAndStrategist {
        enableMushroomsFlashloan = _enableMushroomsFlashloan;
    }

    function setMwbtc(address _mwbtc) public onlyGovernanceAndStrategist {
        mwbtc = _mwbtc;
    }

    // **** State mutations **** //

    // Do a `callStatic` on this.
    // If it returns true then run it for realz. (i.e. eth_signedTx, not eth_call)
    function sync() public returns (bool) {
        uint256 colFactor = getColFactor();
        uint256 safeSyncColFactor = getSafeSyncColFactor();

        // If we're not safe
        if (colFactor > safeSyncColFactor) {
            uint256 unleveragedSupply = getSuppliedUnleveraged();
            uint256 idealSupply = getLeveragedSupplyTarget(unleveragedSupply);

            deleverageUntil(idealSupply);

            return true;
        }

        return false;
    }

    function leverageToMax() public {
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        uint256 idealSupply = getLeveragedSupplyTarget(unleveragedSupply);
        leverageUntil(idealSupply);
    }
	
    // only use flashloan when applicable, i.e., there is enough want to borrow
    function _mushroomsFlashloanApplicable(uint256 _gap) internal returns (bool){
        return enableMushroomsFlashloan && mwbtc != address(0) && IERC20(want).balanceOf(mwbtc) > _gap;
    }	

    // Leverages until we're supplying <x> amount
    // 1. Borrow out <X> WBTC
    // 2. Supply <X> WBTC
    function leverageUntil(uint256 _supplyAmount) public onlyKeepers {

        uint256 leverage = getMaxLeverage();
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        require(
            _supplyAmount >= unleveragedSupply &&
                _supplyAmount <= unleveragedSupply.mul(leverage).div(1e18),
            "!leverage"
        );

        // Since we're only leveraging one asset
        // Supplied = borrowed
        uint256 _borrowAndSupply;
        uint256 supplied = getSupplied();
        uint256 _gap = _supplyAmount.sub(supplied);
		
        if (_gap > 0 && _mushroomsFlashloanApplicable(_gap)){
            IVault(mwbtc).flashLoan(address(this), _gap, flashLeverageFlag);
        } else{
            while (supplied < _supplyAmount) {
               _borrowAndSupply = getBorrowable();

               if (supplied.add(_borrowAndSupply) > _supplyAmount) {
                   _borrowAndSupply = _supplyAmount.sub(supplied);
               }

               ICToken(cwbtc).borrow(_borrowAndSupply);
               deposit();

               supplied = supplied.add(_borrowAndSupply);
            }	
        }
    }

    function deleverageToMin() public {
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        deleverageUntil(unleveragedSupply);
    }

    // Deleverages until we're supplying <x> amount
    // 1. Redeem <x> WBTC
    // 2. Repay <x> WBTC
    function deleverageUntil(uint256 _supplyAmount) public onlyKeepers {
        uint256 unleveragedSupply = getSuppliedUnleveraged();
        uint256 supplied = getSupplied();
        require(
            _supplyAmount >= unleveragedSupply && _supplyAmount <= supplied,
            "!deleverage"
        );

        // Since we're only leveraging on 1 asset
        // redeemable = borrowable
        uint256 _redeemAndRepay = getBorrowable();
        uint256 _gap = supplied.sub(_supplyAmount);
		
        if (_gap > 0 && _mushroomsFlashloanApplicable(_gap)){
            IVault(mwbtc).flashLoan(address(this), _gap, flashDeleverageFlag);
        } else {
            do {
               if (supplied.sub(_redeemAndRepay) < _supplyAmount) {
                   _redeemAndRepay = supplied.sub(_supplyAmount);
               }

               require(ICToken(cwbtc).redeemUnderlying(_redeemAndRepay) == 0, "!redeemInDeleverageUntil");
               IERC20(wbtc).safeApprove(cwbtc, 0);
               IERC20(wbtc).safeApprove(cwbtc, _redeemAndRepay);
               require(ICToken(cwbtc).repayBorrow(_redeemAndRepay) == 0, "!repay");

               supplied = supplied.sub(_redeemAndRepay);
            } while (supplied > _supplyAmount);		
        }
    }

    function _convertWantToBuyback(uint256 _lpAmount) internal override returns (address, uint256){
        return (wbtc, _lpAmount);
    }

    function harvest() public override onlyBenevolent {
        address[] memory ctokens = new address[](1);
        ctokens[0] = cwbtc;

        IComptroller(comptroller).claimComp(address(this), ctokens);
        uint256 _comp = IERC20(comp).balanceOf(address(this));
        if (_comp > 0) {
            _swapUniswap(comp, want, _comp);
        }

        uint256 _want = IERC20(want).balanceOf(address(this));
        uint256 _buybackAmount = _want.mul(performanceFee).div(performanceMax);

        if (buybackEnabled == true && _buybackAmount > 0) {
            buybackAndNotify(want, _buybackAmount);
        } else {
            // We want to get back Usdc
            if (_want > 0) {
                // Performance Fees goes to treasury
                IERC20(want).safeTransfer(IController(controller).treasury(), _want.mul(performanceFee).div(performanceMax));
            }
        }

        // re-invest to compounding profit
        if (_want > 0) {
            deposit();
        }
    }

    function deposit() public override {
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeApprove(cwbtc, 0);
            IERC20(want).safeApprove(cwbtc, _want);
            require(ICToken(cwbtc).mint(_want) == 0, "!deposit");
        }
    }

    function _withdrawSome(uint256 _amount)
        internal
        override
        returns (uint256)
    {
        // -- CoinFabrik: save initial balance --
        uint256 _balance = balanceOfWant();
        uint256 _redeem = _amount;

        // Make sure market can cover liquidity
        require(ICToken(cwbtc).getCash() >= _redeem, "!cash-liquidity");

        // How much borrowed amount do we need to free?
        uint256 borrowed = getBorrowed();
        uint256 supplied = getSupplied();
        uint256 curLeverage = getCurrentLeverage();
        uint256 borrowedToBeFree = _redeem.mul(curLeverage).div(1e18);
        bool useFlashloan = false;

        // If the amount we need to free is > borrowed
        // Just free up all the borrowed amount
        if (borrowedToBeFree > borrowed) {
            useFlashloan = _mushroomsFlashloanApplicable(supplied.sub(getSuppliedUnleveraged()));
            this.deleverageToMin();
        } else {
            // Otherwise just keep freeing up borrowed amounts until
            // we hit a safe number to redeem our underlying
            useFlashloan = _mushroomsFlashloanApplicable(borrowedToBeFree);
            this.deleverageUntil(supplied.sub(borrowedToBeFree));
        }

        // Redeems underlying
        require(ICToken(cwbtc).redeemUnderlying(useFlashloan? _redeem.mul(IVault(mwbtc).loanFeeMax().sub(IVault(mwbtc).loanFee())).div(IVault(mwbtc).loanFeeMax()) : _redeem) == 0, 
                "!redeemInWithdrawSome");
        
        // -- CoinFabrik: calculate tokens redeemed --
        uint256 _reedemed = balanceOfWant();
        _reedemed = _reedemed.sub(_balance);

        return _reedemed;
    }

    // Controller only function for creating additional rewards from dust
    function withdraw(IERC20 _asset) external override returns (uint256 balance) {
        require(cwbtc != address(_asset), "!cToken");
        balance = _withdrawNonWantAsset(_asset);
    }	
	
    // **** Flash Loan related **** //
	
    // callback hook by Mushrooms vault
    function mushroomsFlashloan(address _token, uint256 _amount, uint256 _loanFee, bytes calldata _data) external override {
        require(enableMushroomsFlashloan && mwbtc != address(0), '!noFlashLoan');
        require(_token == want, '!mismatchFlashToken');
        require(balanceOfWant() >= _amount, '!invalidFlashAmount');
		
        uint256 total = _amount.add(_loanFee);
        bytes memory _dataCopy = _data;
		
        chi.approve(address(mwbtc), 0);
        chi.approve(address(mwbtc), uint256(-1));
		
        // 1 -> use flash loan to leverage
        if (_dataCopy[0] == 0x31){
            deposit();
            require(getBorrowable() >= total, '!borrowableFlashRepay');
            require(ICToken(cwbtc).borrow(total) == 0, "!borrowFlashLeverage");
            emit FlashLoanLeverage(_amount, _loanFee, _data, ERC20(mwbtc).name());
        }
        // 2 -> use flash loan to deleverage
        else if(_dataCopy[0] == 0x32){
            IERC20(wbtc).safeApprove(cwbtc, 0);
            IERC20(wbtc).safeApprove(cwbtc, _amount);
            require(ICToken(cwbtc).repayBorrow(_amount) == 0, "!repayFlashDeleverage");			
            // Since we're only leveraging on 1 asset: redeemable = borrowable
            require(getBorrowable() >= total, '!borrowableFlashRepay');
            require(ICToken(cwbtc).redeemUnderlying(total) == 0, "!redeemFlashDeleverage");
            emit FlashLoanDeleverage(_amount, _loanFee, _data, ERC20(mwbtc).name());
        } 
		
        require(balanceOfWant() >= total, '!deficitFlashRepay');
        IERC20(want).transfer(mwbtc, total);
    }
}
function getCompAccrued() public returns (uint256) {
    (, , , uint256 accrued) = ICompoundLens(lens).getCompBalanceMetadataExt(comp, comptroller, address(this));

    return accrued;
}

/* The reason why these exists is because of the nature of the
 interest accruing supply + borrow balance. The "view" methods
 are technically snapshots and don't represent the real value.
 As such there are pseudo view methods where you can retrieve the
 results by calling `callStatic`.
 */