// File: contracts/ProofFeeds.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "./helpers/MerkleProof.sol";

import "./interfaces/public/IProofFeeds.sol";
import "./interfaces/public/ICoreMultidataFeedsReader.sol";
import "./NonProxiedOwnerMultipartyCommons.sol";
import "./AbstractFeedsWithMetrics.sol";


contract ProofFeeds is IProofFeeds, ICoreMultidataFeedsReader, NonProxiedOwnerMultipartyCommons, AbstractFeedsWithMetrics {

    /**
     * @notice Contract version, using SemVer version scheme.
     */
    string public constant override VERSION = "0.1.0";

    bytes32 public constant override MERKLE_TREE_ROOT_TYPE_HASH = keccak256("MerkleTreeRoot(uint32 epoch,bytes32 root)");

    mapping(uint => uint) internal _values;
    mapping(uint => uint) internal _updateTSsPacked;

    ////////////////////////

    constructor (address sourceContractAddr_, uint sourceChainId_)
        NonProxiedOwnerMultipartyCommons(sourceContractAddr_, sourceChainId_) {

    }

    ///////////////////////

    function requireValidProof(
        SignedMerkleTreeRoot calldata signedMerkleTreeRoot_,
        CheckedData calldata checkedData_
    ) public view override {
        require(isProofValid(signedMerkleTreeRoot_, checkedData_), "MultidataFeeds: INVALID_PROOF");
    }

    function isProofValid(
        SignedMerkleTreeRoot calldata signedMerkleTreeRoot_,
        CheckedData calldata checkedData_
    ) public view override returns (bool) {
        return isSignedMerkleTreeRootValid(signedMerkleTreeRoot_)
            && isCheckedDataValid(signedMerkleTreeRoot_, checkedData_);
    }

    function isSignedMerkleTreeRootValid(SignedMerkleTreeRoot calldata signedMerkleTreeRoot_) internal view returns (bool) {
        return isMessageSignatureValid(
            keccak256(
                abi.encode(MERKLE_TREE_ROOT_TYPE_HASH, signedMerkleTreeRoot_.epoch, signedMerkleTreeRoot_.root)
            ),
            signedMerkleTreeRoot_.v, signedMerkleTreeRoot_.r, signedMerkleTreeRoot_.s
        );
    }

    function isCheckedDataValid(SignedMerkleTreeRoot calldata signedMerkleTreeRoot_, CheckedData calldata checkedData_) internal pure returns (bool) {
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(
            signedMerkleTreeRoot_.epoch,
            checkedData_.metricData.name,
            checkedData_.metricData.value,
            checkedData_.metricData.updateTs
        ))));

        return MerkleProof.verifyCalldata(checkedData_.merkleTreeProof, signedMerkleTreeRoot_.root, leaf);
    }

    ////////////////////////////

    function quoteMetrics(string[] calldata names) external view override returns (Quote[] memory quotes) {
        uint length = names.length;
        quotes = new Quote[](length);

        for (uint i; i < length; i++) {
            (bool has, uint id) = hasMetric(names[i]);
            require(has, "MultidataFeeds: INVALID_METRIC_NAME");
            quotes[i] = Quote(_values[id], unpackUpdateTs(id));
        }
    }

    function quoteMetrics(uint256[] calldata ids) external view override returns (Quote[] memory quotes) {
        uint length = ids.length;
        quotes = new Quote[](length);

        uint metricsCount = getMetricsCount();
        for (uint i; i < length; i++) {
            uint id = ids[i];
            require(id < metricsCount, "MultidataFeeds: INVALID_METRIC");
            quotes[i] = Quote(_values[id], unpackUpdateTs(id));
        }
    }

    ////////////////////////////

    /**
     * @notice Upload signed value
     * @dev metric in this instance is created if it is not exists. Important: metric id is different from metric ids from other
     *      instances of ProofFeeds and MedianFeed
     */
    function setValue(SignedMerkleTreeRoot calldata signedMerkleTreeRoot_, CheckedData calldata data_) external {
        requireValidProof(signedMerkleTreeRoot_, data_);

        setMetricValue(data_.metricData);
    }

    /**
     * @notice Upload signed values
     * @dev metric in this instance is created if it is not exists. Important: metric id is different from metric ids from other
     *      instances of ProofFeeds and MedianFeed
     */
    function setValues(SignedMerkleTreeRoot calldata signedMerkleTreeRoot_, CheckedData[] calldata data_) external {
        require(isSignedMerkleTreeRootValid(signedMerkleTreeRoot_), "MultidataFeeds: INVALID_ROOT");

        uint count = data_.length;
        for (uint i = 0; i < count; i++) {
            require(isCheckedDataValid(signedMerkleTreeRoot_, data_[i]), "MultidataFeeds: INVALID_PROOF");

            setMetricValue(data_[i].metricData);
        }
    }

    function setMetricValue(MetricData calldata metricData_) internal {
        (bool has, uint metricId) = hasMetric(metricData_.name);
        if (!has) {
            metricId = addMetric(Metric(metricData_.name, "", "", new string[](0)));
        }

        require(metricData_.updateTs > unpackUpdateTs(metricId), "MultidataFeeds: STALE_UPDATE");

        _values[metricId] = metricData_.value;

        uint tempTsPacked = _updateTSsPacked[metricId / 8];
        uint shift = (metricId % 8) * 32;
        tempTsPacked &= ~(uint(type(uint32).max) << shift);
        tempTsPacked |= (uint(metricData_.updateTs) << shift);
        _updateTSsPacked[metricId / 8] = tempTsPacked;
    }

    function unpackUpdateTs(uint metricId_) internal view returns (uint32) {
        return uint32(
            _updateTSsPacked[metricId_ / 8] >> ((metricId_ % 8) * 32)
        );
    }
}

// File: contracts/helpers/MerkleProof.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (utils/cryptography/MerkleProof.sol)

// extracted from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.3/contracts/utils/cryptography/MerkleProof.sol
// unused multi methods were cut
// the original license https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.9.3/LICENSE

pragma solidity ^0.8.0;

/**
 * @dev These functions deal with verification of Merkle Tree proofs.
 *
 * The tree and the proofs can be generated using our
 * https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
 * You will find a quickstart guide in the readme.
 *
 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.
 * OpenZeppelin's JavaScript library generates merkle trees that are safe
 * against this attack out of the box.
 */
library MerkleProof {
    /**
     * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
     * defined by `root`. For this, a `proof` must be provided, containing
     * sibling hashes on the branch from the leaf to the root of the tree. Each
     * pair of leaves and each pair of pre-images are assumed to be sorted.
     */
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProof(proof, leaf) == root;
    }

    /**
     * @dev Calldata version of {verify}
     *
     * _Available since v4.7._
     */
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
        return processProofCalldata(proof, leaf) == root;
    }

    /**
     * @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
     * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
     * hash matches the root of the tree. When processing the proof, the pairs
     * of leafs & pre-images are assumed to be sorted.
     *
     * _Available since v4.4._
     */
    function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    /**
     * @dev Calldata version of {processProof}
     *
     * _Available since v4.7._
     */
    function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
        bytes32 computedHash = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            computedHash = _hashPair(computedHash, proof[i]);
        }
        return computedHash;
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
    }

    function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, a)
            mstore(0x20, b)
            value := keccak256(0x00, 0x40)
        }
    }
}

// File: contracts/interfaces/public/IProofFeeds.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "./IVersioned.sol";
import "./IProofFeedsCommons.sol";


interface IProofFeeds is IVersioned, IProofFeedsCommons {

    struct MetricData {
        string name;
        uint256 value;
        uint32 updateTs;
    }

    struct CheckedData {
        bytes32[] merkleTreeProof;
        MetricData metricData;
    }

    function requireValidProof(
        SignedMerkleTreeRoot calldata signedMerkleTreeRoot_,
        CheckedData calldata checkedData_
    ) external view;

    function isProofValid(
        SignedMerkleTreeRoot calldata signedMerkleTreeRoot_,
        CheckedData calldata checkedData_
    ) external view returns (bool);
}

// File: contracts/interfaces/public/ICoreMultidataFeedsReader.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "./IVersioned.sol";


/// @title Reader of MultidataFeeds core data.
interface ICoreMultidataFeedsReader is IVersioned {

    struct Metric {
        string name;    // unique, immutable in a contract
        string description;
        string currency;    // USD, ETH, PCT (for percent), BPS (for basis points), etc
        string[] tags;
    }

    struct Quote {
        uint256 value;
        uint32 updateTS;
    }

    event NewMetric(string name);
    event MetricInfoUpdated(string name);
    /// @notice updated one metric or all if metricId=type(uint256).max-1
    event MetricUpdated(uint indexed epochId, uint indexed metricId);


    /**
     * @notice Gets a list of metrics quoted by this oracle.
     * @return A list of metric info indexed by numerical metric ids.
     */
    function getMetrics() external view returns (Metric[] memory);

    /// @notice Gets a count of metrics quoted by this oracle.
    function getMetricsCount() external view returns (uint);

    /// @notice Gets metric info by a numerical id.
    function getMetric(uint256 id) external view returns (Metric memory);

    /**
     * @notice Checks if a metric is quoted by this oracle.
     * @param name Metric codename.
     * @return has `true` if metric exists.
     * @return id Metric numerical id, set if `has` is true.
     */
    function hasMetric(string calldata name) external view returns (bool has, uint256 id);

    /**
     * @notice Gets last known quotes for specified metrics.
     * @param names Metric codenames to query.
     * @return quotes Values and update timestamps for queried metrics.
     */
    function quoteMetrics(string[] calldata names) external view returns (Quote[] memory quotes);

    /**
     * @notice Gets last known quotes for specified metrics by internal numerical ids.
     * @dev Saves one storage lookup per metric.
     * @param ids Numerical metric ids to query.
     * @return quotes Values and update timestamps for queried metrics.
     */
    function quoteMetrics(uint256[] calldata ids) external view returns (Quote[] memory quotes);
}

// File: contracts/NonProxiedOwnerMultipartyCommons.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./MultipartyCommons.sol";


abstract contract NonProxiedOwnerMultipartyCommons is MultipartyCommons {
    event MPOwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    OwnerMultisignature internal ownerMultisignature_; // informational field
    address internal mpOwner_;   // described by ownerMultisignature

    constructor(address verifyingContract, uint256 chainId) MultipartyCommons(verifyingContract, chainId) {
        address[] memory newParticipants = new address[](1);
        newParticipants[0] = msg.sender;
        changeOwner_(msg.sender, 1, newParticipants);
    }

    /**
     * @notice Changes multiparty owner data.
     * @param newOwner Address of the new mp owner.
     * @param quorum New quorum value.
     * @param newParticipants List of the new participants' addresses
     * @param salt Salt value
     * @param deadline Unix ts at which the work must be interrupted.
     */
    function changeOwner(address newOwner, uint quorum, address[] calldata newParticipants, uint salt, uint deadline)
        external
        selfCall
        applicable(salt, deadline)
    {
        changeOwner_(newOwner, quorum, newParticipants);
    }

    /**
     * @notice Changes multiparty owner data. Internal
     * @param newOwner Address of the new mp owner.
     * @param quorum New quorum value.
     * @param newParticipants List of the new participants' addresses
     */
    function changeOwner_(address newOwner, uint quorum, address[] memory newParticipants)
        internal
    {
        require(newOwner != address(0), "MP: ZERO_ADDRESS");
        emit MPOwnershipTransferred(mpOwner_, newOwner);
        address[] memory oldParticipants = ownerMultisignature_.participants;
        onNewOwner(newOwner, quorum, newParticipants, oldParticipants);
        ownerMultisignature_.quorum = quorum;
        ownerMultisignature_.participants = newParticipants;
        mpOwner_ = newOwner;
    }

    /**
     * @notice The new mp owner handler. Empty implementation
     * @param newOwner Address of the new mp owner.
     * @param newQuorum New quorum value.
     * @param newParticipants List of the new participants' addresses.
     * @param oldParticipants List of the old participants' addresses.
     */
    function onNewOwner(address newOwner, uint newQuorum, address[] memory newParticipants, address[] memory oldParticipants) virtual internal {}

    // @inheritdoc IMpOwnable
    function ownerMultisignature() public view virtual override returns (OwnerMultisignature memory) {
        return ownerMultisignature_;
    }

    // @inheritdoc IMpOwnable
    function mpOwner() public view virtual override returns (address) {
        return mpOwner_;
    }
}

// File: contracts/AbstractFeedsWithMetrics.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "./interfaces/public/ICoreMultidataFeedsReader.sol";


abstract contract AbstractFeedsWithMetrics is ICoreMultidataFeedsReader {

    Metric[] internal metrics;
    // Position of the metric in the `metrics` array, plus 1 because index 0
    // means that metric is not exists (to avoid additional checks of existence).
    mapping(string => uint) internal adjustedMetricId;

    /// @inheritdoc ICoreMultidataFeedsReader
    function getMetrics() public view override returns (Metric[] memory) {
        return metrics;
    }

    /// @inheritdoc ICoreMultidataFeedsReader
    function getMetricsCount() public view override returns (uint) {
        return metrics.length;
    }

    /// @inheritdoc ICoreMultidataFeedsReader
    function getMetric(uint256 id) external view override returns (Metric memory) {
        require(id < metrics.length, "MultidataFeeds: METRIC_NOT_FOUND");
        return metrics[id];
    }

    /// @inheritdoc ICoreMultidataFeedsReader
    function hasMetric(string calldata name) public view override returns (bool has, uint256 id) {
        uint adjustedId = adjustedMetricId[name];
        if (adjustedId != 0) {
            return (true, adjustedId - 1);
        }

        return (false, 0);
    }

    function addMetric(Metric memory metric_) internal returns (uint newMetricId_) {
        uint adjustedId = adjustedMetricId[metric_.name];
        require(adjustedId == 0, "MultidataFeeds: METRIC_EXISTS");

        newMetricId_ = metrics.length;
        adjustedMetricId[metric_.name] = newMetricId_ + 1;
        metrics.push(metric_);

        emit NewMetric(metric_.name);
    }

    function updateMetric(Metric memory metric_) internal {
        uint adjustedId = adjustedMetricId[metric_.name];
        require(adjustedId != 0, "MultidataFeeds: METRIC_NOT_FOUND");

        metrics[adjustedId-1] = metric_;
        emit MetricInfoUpdated(metric_.name);
    }
}

// File: contracts/interfaces/public/IVersioned.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;


/// @title Contract supporting versioning using SemVer version scheme.
interface IVersioned {
    /// @notice Contract version, using SemVer version scheme.
    function VERSION() external view returns (string memory);
}

// File: contracts/interfaces/public/IProofFeedsCommons.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "./IVersioned.sol";


interface IProofFeedsCommons {

    struct SignedMerkleTreeRoot {
        uint32 epoch;
        uint8 v;
        bytes32 r;
        bytes32 s;
        bytes32 root;
    }

    /// @dev must be keccak256("MerkleTreeRoot(uint32 epoch,bytes32 root)")
    function MERKLE_TREE_ROOT_TYPE_HASH() external view returns (bytes32);
}

// File: @openzeppelin/contracts/utils/cryptography/ECDSA.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
 *
 * These functions can be used to verify that a message was signed by the holder
 * of the private keys of a given address.
 */
library ECDSA {
    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature`. This address can then be used for verification purposes.
     *
     * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
     * this function rejects them by requiring the `s` value to be in the lower
     * half order, and the `v` value to be either 27 or 28.
     *
     * IMPORTANT: `hash` _must_ be the result of a hash operation for the
     * verification to be secure: it is possible to craft signatures that
     * recover to arbitrary addresses for non-hashed data. A safe way to ensure
     * this is by receiving a hash of the original message (which may otherwise
     * be too long), and then calling {toEthSignedMessageHash} on it.
     */
    function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
        // Divide the signature in r, s and v variables
        bytes32 r;
        bytes32 s;
        uint8 v;

        // Check the signature length
        // - case 65: r,s,v signature (standard)
        // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._
        if (signature.length == 65) {
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
        } else if (signature.length == 64) {
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                let vs := mload(add(signature, 0x40))
                r := mload(add(signature, 0x20))
                s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
                v := add(shr(255, vs), 27)
            }
        } else {
            revert("ECDSA: invalid signature length");
        }

        return recover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `v`,
     * `r` and `s` signature fields separately.
     */
    function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
        // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
        // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
        // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
        // signatures from current libraries generate a unique signature with an s-value in the lower half order.
        //
        // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
        // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
        // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
        // these malleable signatures as well.
        require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
        require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        require(signer != address(0), "ECDSA: invalid signature");

        return signer;
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from a `hash`. This
     * produces hash corresponding to the one signed with the
     * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
     * JSON-RPC method as part of EIP-191.
     *
     * See {recover}.
     */
    function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
    }

    /**
     * @dev Returns an Ethereum Signed Typed Data, created from a
     * `domainSeparator` and a `structHash`. This produces hash corresponding
     * to the one signed with the
     * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
     * JSON-RPC method as part of EIP-712.
     *
     * See {recover}.
     */
    function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
    }
}

// File: contracts/MultipartyCommons.sol



// SPDX-License-Identifier: bsl-1.1

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "./interfaces/IMpOwnable.sol";


abstract contract MultipartyCommons is IMpOwnable {
    bytes32 immutable internal VOTE_TYPE_HASH;
    bytes32 internal DOMAIN_SEPARATOR;

    mapping(uint => bool) public usedSalt;

    // Self-calls are used to engage builtin deserialization facility (abi parsing) and not parse args ourselves
    modifier selfCall virtual {
        require(msg.sender == address(this), "MP: NO_ACCESS");
        _;
    }

    // Checks if a privileged call can be applied
    modifier applicable(uint salt, uint deadline) virtual {
        require(getTimeNow() <= deadline, "MP: DEADLINE");
        require(!usedSalt[salt], "MP: DUPLICATE");
        usedSalt[salt] = true;
        _;
    }

    constructor(address verifyingContract, uint256 chainId) {
        require(verifyingContract != address(0) && chainId != 0, 'MP: Invalid domain parameters');
        VOTE_TYPE_HASH = keccak256("Vote(bytes calldata)");
        setDomainSeparator(chainId, verifyingContract);
    }

    /**
     * @notice DOMAIN_SEPARATOR setter.
     * @param chainId Chain id of the verifying contract
     * @param verifyingContract Address of the verifying contract
     */
    function setDomainSeparator(uint256 chainId, address verifyingContract) internal {
        DOMAIN_SEPARATOR = buildDomainSeparator(chainId, verifyingContract);
    }

    function buildDomainSeparator(uint256 chainId, address verifyingContract) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                keccak256(bytes("Multidata.Multiparty.Protocol")),
                keccak256(bytes("1")),
                chainId,
                verifyingContract
            )
        );
    }

    /**
     * @notice Performs privileged call to the contract.
     * @param privilegedCallData Method calldata
     * @param v Signature v for the call
     * @param r Signature r for the call
     * @param s Signature s for the call
     */
    function privilegedCall(bytes calldata privilegedCallData, uint8 v, bytes32 r, bytes32 s) external
    {
        checkMessageSignature(keccak256(abi.encode(VOTE_TYPE_HASH, keccak256(privilegedCallData))), v, r, s);

        (bool success, bytes memory returnData) = address(this).call(privilegedCallData);
        if (!success) {
            revert(string(returnData));
        }
    }

    /**
     * @notice Checks the message signature.
     * @param hashStruct Hash of a message struct
     * @param v V of the message signature
     * @param r R of the message signature
     * @param s S of the message signature
     */
    function checkMessageSignature(bytes32 hashStruct, uint8 v, bytes32 r, bytes32 s) internal virtual view {
        require(isMessageSignatureValid(hashStruct, v, r, s), "MP: NO_ACCESS");
    }

    function isMessageSignatureValid(bytes32 hashStruct, uint8 v, bytes32 r, bytes32 s) internal virtual view returns (bool) {
        return ECDSA.recover(generateMessageHash(hashStruct), v, r, s) == mpOwner();
    }

    function checkMessageSignatureForDomain(bytes32 domainSeparator, bytes32 hashStruct, uint8 v, bytes32 r, bytes32 s) internal virtual view {
        require(ECDSA.recover(generateMessageHashForDomain(domainSeparator, hashStruct), v, r, s) == mpOwner(), "MP: NO_ACCESS");
    }

    /**
     * @notice Returns hash of the message for the hash of the struct.
     * @param hashStruct Hash of a message struct
     */
    function generateMessageHash(bytes32 hashStruct) internal view returns (bytes32) {
        return generateMessageHashForDomain(DOMAIN_SEPARATOR, hashStruct);
    }

    function generateMessageHashForDomain(bytes32 domainSeparator, bytes32 hashStruct) internal pure returns (bytes32) {
        return keccak256(
            abi.encodePacked(
                "\x19\x01",
                domainSeparator,
                hashStruct
            )
        );
    }

    /**
     * @notice Returns current chain time in unix ts.
     */
    function getTimeNow() virtual internal view returns (uint32) {
        return uint32(block.timestamp);
    }

    // @inheritdoc IMpOwnable
    function ownerMultisignature() public view virtual override returns (OwnerMultisignature memory);

    // @inheritdoc IMpOwnable
    function mpOwner() public view virtual override returns (address);
}

// File: contracts/interfaces/IMpOwnable.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IMpOwnable {
    struct OwnerMultisignature {
        uint quorum;
        address[] participants;
    }

    // @notice Returns OwnerMultisignature data
    function ownerMultisignature() external view returns (OwnerMultisignature memory);

    // @notice Returns address og the multiparty owner
    function mpOwner() external view returns (address);
}