abstract contract GovernanceToken is ERC20Capped, ERC20Burnable, IFungibleToken, AccessControl, Pausable {
    using ECDSA for bytes32;

    // keccak256("mint(address target, uint256 amount, bytes[] signatures)")
    bytes32 constant MINT_TYPEHASH = 0xdaef0006354e6aca5b14786fab16e27867b1ac002611e2fa58e0aa486080141f;

    // keccak256("mintBulk(address[] target, uint256[] amount, bytes[] signatures)")
    bytes32 constant MINT_BULK_TYPEHASH = 0x84bbfaa2e4384c51c0e71108356af77f996f8a1f97dc229b15ad088f887071c7;

    // keccak256("freeze/unfreeze(address target, bytes[] memory signatures)")
    bytes32 constant FREEZE_TYPEHASH = 0x0101de85040f7616ce3d91b0b3b5279925bff5ba3cbdc18c318483eec213aba5;

    // keccak256("freezeBulk/unfreezeBulk(address[] calldata target, bytes[] memory signatures)")
    bytes32 constant FREEZE_BULK_TYPEHASH = 0xfbe23759ad6142178865544766ded4220dd6951de831ca9f926f385026c83a2b;

    // keccak256("burnFrozenTokens(address target, bytes[] memory signatures)")
    bytes32 constant BURN_FROZEN_TYPEHASH = 0x642bcc36d46a724c301cb6a1e74f954db2da04e41cf92613260aa926b0cc663c;

    // keccak256("freezeAndBurnTokens(address target, bytes[] memory signatures)")
    bytes32 constant FREEZE_AND_BURN_TYPEHASH = 0xb17ffba690b680e166aba321cd5d08ac8256fa93afb6a8f0573d02ecbfa33e11;

    // keccak256("pause/unpause(bytes[] memory signatures)")
    bytes32 constant PAUSE_TYPEHASH = 0x4f10db4bd06c1a9ea1a64e78bc5c096dc4b14436b0cdf60a6252f82113e0a57e;

    uint public nonce = 0;

    event SignedBy(address signer);

    constructor (string memory name_, string memory symbol_, uint256 cap_, address[] memory allowedSigners)
        ERC20Capped(cap_)
        ERC20(name_, symbol_)
        AccessControl(allowedSigners) {}

    /**
     * @dev Mint some tokens to target account
     * MultiSig check is used - verifies that contract signers approve minting.
     * During minting applied check for the max token cap.
     */
    function mint(address target, uint256 amount, bytes[] memory signatures) external onlySigner {
        bytes32 operationHash = getOperationHash(MINT_TYPEHASH, target, amount).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);
        _mint(target, amount);
    }

    /**
     * @dev Bulk operation to mint tokens to target accounts. There is a check for the cap inside.
     */
    function mintBulk(address[] calldata target, uint256[] calldata amount, bytes[] memory signatures) external onlySigner {
        require(target.length > 1, "GovernanceToken: cannot perform bulk with single target");
        require(target.length == amount.length, "GovernanceToken: target.length != amount.length");

        bytes32 operationHash = getOperationHash(MINT_BULK_TYPEHASH, target[0], target.length).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        for (uint i = 0; i < target.length; i++) {
            _mint(target[i], amount[i]);
        }
    }

    /**
    * @dev Mark target account as frozen. Frozen accounts can't perform transfers.
    */
    function freeze(address target, bytes[] memory signatures) external onlySigner {
        bytes32 operationHash = getOperationHash(FREEZE_TYPEHASH, target, 1).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        _freeze(target);
    }

    /**
     * @dev Mark target account as unfrozen.
     */
    function unfreeze(address target, bytes[] memory signatures) external onlySigner {
        bytes32 operationHash = getOperationHash(FREEZE_TYPEHASH, target, 1).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        _unfreeze(target);
    }

    function freezeBulk(address[] calldata target, bytes[] memory signatures) external onlySigner {
        require(target.length > 1, "GovernanceToken: cannot perform bulk with single target");

        bytes32 operationHash = getOperationHash(FREEZE_BULK_TYPEHASH, target[0], target.length).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        for (uint i = 0; i < target.length; i++) {
            _freeze(target[i]);
        }
    }

    function unfreezeBulk(address[] calldata target, bytes[] memory signatures) external onlySigner {
        require(target.length > 1, "GovernanceToken: cannot perform bulk with single target");

        bytes32 operationHash = getOperationHash(FREEZE_BULK_TYPEHASH, target[0], target.length).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        for (uint i = 0; i < target.length; i++) {
            _unfreeze(target[i]);
        }
    }

    /**
     * @dev Burn tokens on frozen account.
     */
    function burnFrozenTokens(address target, bytes[] memory signatures) external onlySigner {
        require(isFrozen(target), "GovernanceToken: target account is not frozen");

        bytes32 operationHash = getOperationHash(BURN_FROZEN_TYPEHASH, target, 1).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        _burn(target, balanceOf(target));
    }

    /**
     * @dev Freeze and burn tokens in a single transaction.
     */
    function freezeAndBurnTokens(address target, bytes[] memory signatures) external onlySigner {
        bytes32 operationHash = getOperationHash(FREEZE_AND_BURN_TYPEHASH, target, 1).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        _freeze(target);
        _burn(target, balanceOf(target));
    }

    /**
     * @dev Triggers stopped state.
     * - The contract must not be paused and pause should be allowed.
     */
    function pause(bytes[] memory signatures) external onlySigner {
        bytes32 operationHash = getOperationHash(PAUSE_TYPEHASH, msg.sender, 1).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        _pause();
    }

    /**
     * @dev Returns to normal state.
     * - The contract must be paused.
     */
    function unpause(bytes[] memory signatures) external onlySigner {
        bytes32 operationHash = getOperationHash(PAUSE_TYPEHASH, msg.sender, 1).toEthSignedMessageHash();
        _verifySignatures(signatures, operationHash);

        _unpause();
    }

    /**
    * @dev Get operation hash for multisig operation
    * Nonce used to ensure that signature used only once.
    * Use unique typehash for each operation.
    */
    function getOperationHash(bytes32 typehash, address target, uint256 value) public view returns (bytes32) {
        return keccak256(abi.encodePacked(address(this), typehash, target, value, nonce));
    }

    /**
     * @dev See {ERC20-_beforeTokenTransfer}.
     *
     * Requirements:
     *
     * - do not allow the transfer of funds to the token contract itself. Usually such a call is a mistake.
     * - do not allow transfers when contract is paused.
     * - only allow to burn frozen tokens.
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual override(ERC20Capped, ERC20) {
        super._beforeTokenTransfer(from, to, amount);

        require(to != address(this), "GovernanceToken: can't transfer to token contract self");
        require(!paused(), "GovernanceToken: token transfer while paused");
        require(!isFrozen(from) || to == address(0x0), "GovernanceToken: source address was frozen");
    }

    /**
     * @dev Verify provided signatures according to the operation hash
     * Ensure that each signature belongs to contract known signer and is unique
     */
    function _verifySignatures(bytes[] memory signatures, bytes32 operationHash) internal {
        require(signatures.length >= 2, "AccessControl: not enough confirmations");

        address[] memory recovered = new address[](signatures.length + 1);
        recovered[0] = msg.sender;
        emit SignedBy(msg.sender);

        for (uint i = 0; i < signatures.length; i++) {
            address addr = operationHash.recover(signatures[i]);
            require(isSigner(addr), "AccessControl: recovered address is not signer");

            for (uint j = 0; j < recovered.length; j++) {
                require(recovered[j] != addr, "AccessControl: signer address used more than once");
            }

            recovered[i + 1] = addr;
            emit SignedBy(addr);
        }

        require(recovered.length >= 3, "AccessControl: not enough confirmations");

        nonce++;
    }
}
/**
 * Governance Token contract includes multisig protected actions.
 * It includes:
 * - minting methods
 * - freeze methods
 * - pause methods
 *
 * For each call must be provided valid signatures from contract signers (defined in AccessControl)
 * and the transaction itself must be sent from the signer address.
 * Every succeeded transaction will contain signer addresses for action proof in logs.
 *
 * It is possible to pause contract transfers in case an exchange is hacked and there is a risk for token holders to lose
 * their tokens, delegated to an exchange. After freezing suspicious accounts the contract can be unpaused.
 * Board members can burn tokens on frozen accounts to mint new tokens to holders as a recovery after a hacking attack.
 */
function unfreeze(address target, bytes[] memory signatures) external onlySigner {
    bytes32 operationHash = getOperationHash(FREEZE_TYPEHASH, target, 1).toEthSignedMessageHash();
    _verifySignatures(signatures, operationHash);

    _unfreeze(target);
}

/**
 * @dev Mark target account as unfrozen.
 */