contract StrategyCmpdZrxV1 is StrategyBase, Exponential {
    address public constant comptroller = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B;
    address public constant lens = 0xd513d22422a3062Bd342Ae374b4b9c20E0a9a074;
    address public constant zrx = 0xE41d2489571d322189246DaFA5ebDe1F4699F498;
    address public constant comp = 0xc00e94Cb662C3520282E6f5717214004A7f26888;
    address public constant czrx = 0xB3319f5D18Bc0D84dD1b4825Dcde5d5f7266d407;

    // Require a 0.025 buffer between market collateral factor and strategy's collateral factor when leveraging
    uint256 public colFactorLeverageBuffer = 25;
    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;

    // Keeper bots
    // Maintain leverage within buffer
    mapping(address => bool) public keepers;

    constructor(address _governance, address _strategist, address _controller, address _timelock)
        public StrategyBase(zrx, _governance, _strategist, _controller, _timelock)
    {
        // Enter cZRX Market
        address[] memory ctokens = new address[](1);
        ctokens[0] = czrx;
        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 "StrategyCmpdZrxV1";
    }

    function getSuppliedView() public view returns (uint256) {
        (, uint256 cTokenBal, , uint256 exchangeRate) = ICToken(czrx).getAccountSnapshot(address(this));
        (, uint256 bal) = mulScalarTruncate(Exp({mantissa: exchangeRate}), cTokenBal);
        return bal;
    }

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

    function currentLeverageView() public view returns (uint256) {
        uint256 netAsset = balanceOfPool();
        uint256 supplied = getSuppliedView();
        return supplied.mul(1e18).div(netAsset);
    }

    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(czrx);
        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);
    }

    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(czrx).balanceOfUnderlying(address(this));
    }

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

    function getBorrowable() public returns (uint256) {
        uint256 supplied = getSupplied();
        uint256 borrowed = getBorrowed();
        (, uint256 colFactor) = IComptroller(comptroller).markets(czrx);
        // 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 setKeeper(address _keeper, bool _enabled) public onlyGovernanceAndStrategist {
        keepers[_keeper] = _enabled;
    }

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

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

    function leverageToMax() public {
        uint256 curLeverage = currentLeverageView();
        uint256 maxLeverage = getMaxLeverage();
        if (curLeverage < maxLeverage){
            uint256 unleveragedSupply = getSuppliedUnleveraged();
            uint256 idealSupply = getLeveragedSupplyTarget(unleveragedSupply);
            leverageUntil(idealSupply);
        }
    }

    // Leverages until we're supplying <x> amount
    // 1. Borrow out <X> ZRX
    // 2. Supply <X> ZRX
    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();
		
        while (supplied < _supplyAmount) {
            _borrowAndSupply = getBorrowable();

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

            ICToken(czrx).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> ZRX
    // 2. Repay <x> ZRX
    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();
		
        do {
            if (supplied.sub(_redeemAndRepay) < _supplyAmount) {
               _redeemAndRepay = supplied.sub(_supplyAmount);
            }

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

            supplied = supplied.sub(_redeemAndRepay);
        } while (supplied > _supplyAmount);	
    }
    
    function _convertWantToBuyback(uint256 _lpAmount) internal override returns (address, uint256){
        return (zrx, _lpAmount);
    }
    
    function harvest() public override onlyBenignCallers {
        address[] memory ctokens = new address[](1);
        ctokens[0] = czrx;
        
        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);
        }
        
        // re-invest to compounding profit
        uint256 _reinvest = IERC20(want).balanceOf(address(this));
        if (_reinvest > 0) {
            lastHarvestBlock = block.number;
            lastHarvestInWant = _reinvest;
            deposit();
            leverageToMax();
        }
    }
    
    function deposit() public override{
        uint256 _want = IERC20(want).balanceOf(address(this));
        if (_want > 0) {
            IERC20(want).safeApprove(czrx, 0);
            IERC20(want).safeApprove(czrx, _want);
            require(ICToken(czrx).mint(_want) == 0, "!deposit");
        }
    }
    
    function _withdrawSome(uint256 _amount) internal override returns (uint256){
        // -- CoinFabrik: save initial balance --
        uint256 _balance = balanceOfWant();
        uint256 maxRedeem = getSuppliedUnleveraged();
        uint256 _redeem = _amount > maxRedeem? maxRedeem : _amount;

        // Make sure market can cover liquidity
        require(ICToken(czrx).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);

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

        // Redeems underlying
        require(ICToken(czrx).redeemUnderlying(_redeem) == 0, "!redeemInWithdrawSome");
        
        // -- CoinFabrik: calculate tokens redeemed --
        uint256 _reedemed = balanceOfWant();
        _reedemed = _reedemed.sub(_balance);

        return _reedemed;
    }
    
    function withdraw(IERC20 _asset) external override returns (uint256 balance) {
        require(czrx != address(_asset), "!cToken");
        balance = _withdrawNonWantAsset(_asset);
    }
    
}
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();
		
    do {
        if (supplied.sub(_redeemAndRepay) < _supplyAmount) {
           _redeemAndRepay = supplied.sub(_supplyAmount);
        }

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

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

// Deleverages until we're supplying <x> amount
// 1. Redeem <x> ZRX
// 2. Repay <x> ZRX