contract Vault is ERC20 {
    uint256 public constant underlyingUnit = 1e18;

    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint256;

    IERC20 public token;

    uint256 public min = 9500;
    uint256 public constant max = 10000;

    address public governance;
    address public controller;
    address public constant profitBooster = 0xC24Ecc381faA4C467a373239e8aFb5de940922eA;

    constructor(address _token, address _governance, address _controller)
    public
    ERC20(
        string(abi.encodePacked("btf ", ERC20(_token).name())),
        string(abi.encodePacked("b", ERC20(_token).symbol()))
    )
    {
        _setupDecimals(ERC20(_token).decimals());
        token = IERC20(_token);
        governance = _governance;
        controller = _controller;
    }

    function balance() public view returns (uint256) {
        return
        token.balanceOf(address(this)).add(
            IController(controller).balanceOf(address(token))
        );
    }

    function balanceOfToken() public view returns (uint256) {
        uint256 tokenBalanceWithoutBooster = balance().sub(balanceOf(profitBooster));
        uint256 totalSupplyWithoutBooster = totalSupply().sub(balanceOf(profitBooster));

        if (profitBooster == msg.sender) {
            return balanceOf(profitBooster);
        } else if (tokenBalanceWithoutBooster == 0 || totalSupplyWithoutBooster == 0) {
            return 0;
        } else {
            return tokenBalanceWithoutBooster.mul(balanceOf(msg.sender)).div(totalSupplyWithoutBooster);
        }
    }

    function setMin(uint256 _min) external {
        require(msg.sender == governance, "!governance");
        min = _min;
    }

    function setGovernance(address _governance) public {
        require(msg.sender == governance, "!governance");
        governance = _governance;
    }

    function setController(address _controller) public {
        require(msg.sender == governance, "!governance");
        controller = _controller;
    }

    // Custom logic in here for how much the token allows to be borrowed
    // Sets minimum required on-hand to keep small withdrawals cheap
    function available() public view returns (uint256) {
        return token.balanceOf(address(this)).mul(min).div(max);
    }

    function earn() public {
        uint256 _bal = available();
        token.safeTransfer(controller, _bal);
        IController(controller).earn(address(token), _bal);
    }

    function depositAll() external {
        deposit(token.balanceOf(msg.sender));
    }

    function deposit(uint256 _amount) public {
        uint256 tokenBalanceWithoutBooster = balance().sub(balanceOf(profitBooster));
        uint256 totalSupplyWithoutBooster = totalSupply().sub(balanceOf(profitBooster));

        uint256 _before = token.balanceOf(address(this));
        token.safeTransferFrom(msg.sender, address(this), _amount);
        uint256 _after = token.balanceOf(address(this));
        _amount = _after.sub(_before);
        // Additional check for deflationary tokens
        uint256 shares = 0;
        if (totalSupplyWithoutBooster == 0 || msg.sender == profitBooster) {
            shares = _amount;
        } else {
            shares = _amount.mul(totalSupplyWithoutBooster).div(tokenBalanceWithoutBooster);
        }
        _mint(msg.sender, shares);
    }

    function withdrawAll() external {
        withdraw(balanceOf(msg.sender));
    }

    // Used to swap any borrowed reserve over the debt limit to liquidate to 'token'
    function harvest(address reserve, uint256 amount) external {
        require(msg.sender == controller, "!controller");
        require(reserve != address(token), "token");
        IERC20(reserve).safeTransfer(controller, amount);
    }

    // No rebalance implementation for lower fees and faster swaps
    function withdraw(uint256 _shares) public {
        uint256 tokenBalanceWithoutBooster = balance().sub(balanceOf(profitBooster));
        uint256 totalSupplyWithoutBooster = totalSupply().sub(balanceOf(profitBooster));

        uint256 r = 0;
        if (profitBooster == msg.sender) {
            r = _shares;
        } else {
            r = tokenBalanceWithoutBooster.mul(_shares).div(totalSupplyWithoutBooster);
        }
        _burn(msg.sender, _shares);

        // Check balance
        uint256 b = token.balanceOf(address(this));
        if (b < r) {
            uint256 _withdraw = r.sub(b);
            IController(controller).withdraw(address(token), _withdraw);
            uint256 _after = token.balanceOf(address(this));
            uint256 _diff = _after.sub(b);
            if (_diff < _withdraw) {
                r = b.add(_diff);
            }
        }

        token.safeTransfer(msg.sender, r);
    }

    // price per share
    function getRatio() public view returns (uint256) {
        return totalSupply() == 0
            ? underlyingUnit
            : underlyingUnit.mul(balance().sub(balanceOf(profitBooster))).div(totalSupply().sub(balanceOf(profitBooster)));
    }
}
function available() public view returns (uint256) {
    return token.balanceOf(address(this)).mul(min).div(max);
}

// Custom logic in here for how much the token allows to be borrowed
// Sets minimum required on-hand to keep small withdrawals cheap