abstract contract PoolShareToken is ERC20, ReentrancyGuard, Pausable {
    IERC20 public token;

    /**
     * @dev Constructor.
     */
    constructor(
        string memory name,
        string memory symbol,
        address _token
    ) public ERC20(name, symbol) {
        token = IERC20(_token);
    }

    /**
     * @dev Calculate and returns price per share of the pool.
     */
    function getPricePerShare() public view returns (uint256) {
        return totalValue().mul(1e18).div(totalSupply());
    }

    /**
     * @dev Convert to 18 decimals from token defined decimals. Default no conversion.
     */
    function convertTo18(uint256 amount) public virtual pure returns (uint256) {
        return amount;
    }

    /**
     * @dev Convert from 18 decimals to token defined decimals. Default no conversion.
     */
    function convertFrom18(uint256 amount) public virtual pure returns (uint256) {
        return amount;
    }

    /**
     * @dev Receives ETH and grants new tokens to the sender  depending on
     * the value of pool's share.
     */
    function deposit() public virtual payable {}

    /**
     * @dev Receives ERC20 token amount and grants new tokens to the sender
     * depending on the value of each contract's share.
     */
    function deposit(uint256 amount) public virtual {}

    /**
     * @dev Returns the value stored in the pool.
     */
    function tokensHere() public virtual view returns (uint256) {}

    /**
     * @dev Returns sum of value locked in other contract and value stored in the pool.
     */
    function totalValue() public virtual view returns (uint256) {}

    /**
     * @dev Burns tokens and retuns deposited tokens or ETH value for those.
     */
    function withdraw(uint256 shares) public virtual {}

    /**
     * @dev Burns tokens and retuns the ETH value, after fee, of those.
     */
    function withdrawETH(uint256 shares) public virtual {}

    /**
     * @dev Hook that is called just before burning tokens. To be used i.e. if
     * collateral is stored in a different contract and needs to be withdrawn.
     */
    function _beforeBurning(uint256 shares) internal virtual {}

    /**
     * @dev Hook that is called just after burning tokens. To be used i.e. if
     * collateral stored in a different/this contract needs to be transferred.
     */
    function _afterBurning(uint256 shares) internal virtual {}

    /**
     * @dev Hook that is called just before minting new tokens. To be used i.e.
     * if the deposited amount is to be transferred to a different contract.
     */
    function _beforeMinting(uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called just after minting new tokens. To be used i.e.
     * if the minted token/share is to be transferred to a different contract.
     */
    function _afterMinting(uint256 amount) internal virtual {}

    /**
     * @dev Get withdraw fee for this pool
     */
    function _getFee() internal virtual view returns (uint256) {}

    /**
     * @dev Get fee collector address
     */
    function _getFeeCollector() internal virtual view returns (address) {}

    /**
     * @dev Calculate share based on share price and given amount.
     */
    function _calculateShares(uint256 amount) internal returns (uint256) {
        require(amount > 0, "Deposit must be greater than 0");

        uint256 _totalSupply = totalSupply();
        uint256 _totalValue = totalValue().sub(msg.value);
        uint256 shares = (_totalSupply == 0 || _totalValue == 0)
            ? amount
            : amount.mul(_totalSupply).div(_totalValue);
        return shares;
    }

    /**
     * @dev Deposit incoming token and mint pool token i.e. shares.
     */
    function _deposit(uint256 amount) internal whenNotPaused {
        uint256 shares = _calculateShares(convertTo18(amount));
        _beforeMinting(amount);
        _mint(_msgSender(), shares);
    }

    /**
     * @dev Handle fee calculation and fee transfer to fee collector.
     */
    function _handleFee(uint256 shares) internal returns (uint256 _sharesAfterFee) {
        if (_getFee() > 0) {
            uint256 _fee = shares.mul(_getFee()).div(1e18);
            _sharesAfterFee = shares.sub(_fee);
            _transfer(_msgSender(), _getFeeCollector(), _fee);
        } else {
            _sharesAfterFee = shares;
        }
    }

    event Withdraw(address owner, uint256 shares, uint256 amount);

    /**
     * @dev Burns tokens and retuns the collateral value, after fee, of those.
     */
    function _withdraw(uint256 shares) internal whenNotShutdown {
        require(shares > 0, "Withdraw must be greater than 0");
        uint256 sharesAfterFee = _handleFee(shares);
        uint256 amount = convertFrom18(sharesAfterFee.mul(totalValue()).div(totalSupply()));
        _burn(_msgSender(), sharesAfterFee);
        _afterBurning(amount);
        emit Withdraw(_msgSender(), shares, amount);
    }
}
/* solhint-disable no-empty-blocks */
function withdraw(uint256 shares) public virtual {}

/**
 * @dev Burns tokens and retuns deposited tokens or ETH value for those.
 */