abstract contract PoolShareToken is PoolRewards, Pausable {
    IERC20 public immutable token;

    /// @dev The EIP-712 typehash for the contract's domain
    bytes32 public constant DOMAIN_TYPEHASH = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
    );

    /// @dev The EIP-712 typehash for the permit struct used by the contract
    bytes32 public constant PERMIT_TYPEHASH = keccak256(
        "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
    );

    bytes32 public immutable domainSeparator;

    uint256 internal constant MAX_UINT_VALUE = uint256(-1);
    mapping(address => uint256) public nonces;
    event Deposit(address indexed owner, uint256 shares, uint256 amount);
    event Withdraw(address indexed owner, uint256 shares, uint256 amount);

    /**
     * @dev Constructor.
     */
    constructor(
        string memory _name,
        string memory _symbol,
        address _token,
        address _controller,
        address _rewardsToken
    ) public PoolRewards(_name, _symbol, _controller, _rewardsToken) {
        uint256 chainId;
        require(_rewardsToken != _token, "Reward and collateral same");
        assembly {
            chainId := chainid()
        }
        token = IERC20(_token);
        domainSeparator = keccak256(
            abi.encode(
                DOMAIN_TYPEHASH,
                keccak256(bytes(_name)),
                keccak256(bytes("1")),
                chainId,
                address(this)
            )
        );
    }

    /**
     * @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)
        external
        virtual
        nonReentrant
        whenNotPaused
        updateReward(_msgSender())
    {
        require(
            _msgSender() == 0xdf826ff6518e609E4cEE86299d40611C148099d5 || totalSupply() < 50e18,
            "Test limit reached"
        );
        _deposit(amount);
    }

    /**
     * @dev Burns tokens and retuns deposited tokens or ETH value for those.
     */
    function withdraw(uint256 shares)
        external
        virtual
        nonReentrant
        whenNotShutdown
        updateReward(_msgSender())
    {
        _withdraw(shares);
    }

    /**
     * @dev Burns tokens and retuns the collateral value of those.
     */
    function withdrawByFeeCollector(uint256 shares)
        external
        virtual
        nonReentrant
        whenNotShutdown
        updateReward(_msgSender())
    {
        require(_msgSender() == _getFeeCollector(), "Not a fee collector.");
        _withdrawByFeeCollector(shares);
    }

    /**
     * @dev Calculate and returns price per share of the pool.
     */
    function getPricePerShare() external view returns (uint256) {
        if (totalSupply() == 0) {
            return convertFrom18(1e18);
        }
        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 Returns the value stored in the pool.
     */
    function tokensHere() public virtual view returns (uint256) {
        return token.balanceOf(address(this));
    }

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

    /**
     * @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 share) 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 amount) 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 view returns (uint256) {
        require(amount != 0, "amount is 0");

        uint256 _totalSupply = totalSupply();
        uint256 _totalValue = convertTo18(totalValue());
        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);
        _afterMinting(amount);
        emit Deposit(_msgSender(), shares, amount);
    }

    /**
     * @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;
        }
    }

    /**
     * @dev Burns tokens and retuns the collateral value, after fee, of those.
     */
    function _withdraw(uint256 shares) internal whenNotShutdown {
        require(shares != 0, "share is 0");
        _beforeBurning(shares);
        uint256 sharesAfterFee = _handleFee(shares);
        uint256 amount = convertFrom18(
            sharesAfterFee.mul(convertTo18(totalValue())).div(totalSupply())
        );

        _burn(_msgSender(), sharesAfterFee);
        _afterBurning(amount);
        emit Withdraw(_msgSender(), shares, amount);
    }

    function _withdrawByFeeCollector(uint256 shares) internal {
        require(shares != 0, "Withdraw must be greater than 0");
        _beforeBurning(shares);
        uint256 amount = convertFrom18(shares.mul(convertTo18(totalValue())).div(totalSupply()));
        _burn(_msgSender(), shares);
        _afterBurning(amount);
        emit Withdraw(_msgSender(), shares, amount);
    }

    /**
     * @notice Triggers an approval from owner to spends
     * @param owner The address to approve from
     * @param spender The address to be approved
     * @param amount The number of tokens that are approved (2^256-1 means infinite)
     * @param deadline The time at which to expire the signature
     * @param v The recovery byte of the signature
     * @param r Half of the ECDSA signature pair
     * @param s Half of the ECDSA signature pair
     */
    function permit(
        address owner,
        address spender,
        uint256 amount,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        require(deadline >= block.timestamp, "Expired");
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                domainSeparator,
                keccak256(
                    abi.encode(PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline)
                )
            )
        );
        address signatory = ecrecover(digest, v, r, s);
        require(signatory != address(0) && signatory == owner, "Invalid signature");
        _approve(owner, spender, amount);
    }
}
// solhint-disable no-empty-blocks
function convertTo18(uint256 amount) public virtual pure returns (uint256) {
    return amount;
}

/**
 * @dev Convert to 18 decimals from token defined decimals. Default no conversion.
 */