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

import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { TradeInfo, AssetData, Fees, Assets } from "../utils/DataTypes.sol";

contract BrokersSignatureUtils is EIP712 {
    /// -----------------------------------------------------------------------
    /// Errors
    /// -----------------------------------------------------------------------

    enum BrokerSignatureUtilsErrorCodes {
        INVALID_ASSET_INFO_SIGNATURE
    }

    error BrokerSignatureUtilsError(BrokerSignatureUtilsErrorCodes code);

    /// -----------------------------------------------------------------------
    /// Library usage
    /// -----------------------------------------------------------------------
    using ECDSA for bytes32;

    /// -----------------------------------------------------------------------
    /// Storage variables
    /// -----------------------------------------------------------------------

    bytes32 private immutable ASSET_DATA_TYPE_HASH;
    bytes32 private immutable ASSETS_TYPE_HASH;
    bytes32 private immutable FEES_TYPE_HASH;
    bytes32 private immutable TRADE_INFO_TYPE_HASH;

    /* ===== INIT ===== */

    /// @dev Constructor
    /// @dev Calculate and set type hashes for all the structs and nested structs types
    constructor() EIP712("NF3 Broker Swaps", "0.1.0") {
        bytes memory assetDataTypeString = abi.encodePacked(
            "AssetData(",
            "address token,",
            "uint8 assetType,",
            "uint256 tokenId,",
            "uint256 amount",
            ")"
        );

        bytes memory assetsTypeString = abi.encodePacked(
            "Assets(",
            "AssetData[] assets"
            ")"
        );

        bytes memory feesTypeString = abi.encodePacked(
            "Fees(",
            "address token,",
            "address broker,",
            "address platform,",
            "uint256 brokerAmount,",
            "uint256 platformAmount",
            ")"
        );

        bytes memory tradeInfoTyepString = abi.encodePacked(
            "TradeInfo(",
            "Assets makerAssets,",
            "Assets takerAssets,",
            "Fees makerFees,",
            "Fees takerFees,",
            "address maker,",
            "address taker,",
            "uint256 duration,",
            "uint256 makerNonce,",
            "uint256 takerNonce",
            ")"
        );

        ASSET_DATA_TYPE_HASH = keccak256(assetDataTypeString);

        ASSETS_TYPE_HASH = keccak256(
            abi.encodePacked(assetsTypeString, assetDataTypeString)
        );

        FEES_TYPE_HASH = keccak256(feesTypeString);

        TRADE_INFO_TYPE_HASH = keccak256(
            abi.encodePacked(
                tradeInfoTyepString,
                assetDataTypeString,
                assetsTypeString,
                feesTypeString
            )
        );
    }

    /// -----------------------------------------------------------------------
    /// Signature Verification Functions
    /// -----------------------------------------------------------------------

    function _verifyTradeInfoSignature(
        TradeInfo calldata _tradeInfo,
        bytes memory _makerSignature,
        bytes memory _takerSignature
    ) internal view {
        bytes32 tradeInfoHash = _hashTradeInfo(_tradeInfo);

        address _maker = _hashTypedDataV4(tradeInfoHash).recover(
            _makerSignature
        );
        address _taker = _hashTypedDataV4(tradeInfoHash).recover(
            _takerSignature
        );

        if (_tradeInfo.maker != _maker || _tradeInfo.taker != _taker) {
            revert BrokerSignatureUtilsError(
                BrokerSignatureUtilsErrorCodes.INVALID_ASSET_INFO_SIGNATURE
            );
        }
    }

    /// -----------------------------------------------------------------------
    /// Private functions
    /// -----------------------------------------------------------------------

    /// @dev Get eip 712 compliant hash for TradeInfo struct type
    /// @param _tradeInfo TradeInfo struct to be hashed
    function _hashTradeInfo(
        TradeInfo calldata _tradeInfo
    ) private view returns (bytes32) {
        bytes32 tradeInfoHash = keccak256(
            abi.encode(
                TRADE_INFO_TYPE_HASH,
                _hashAssets(_tradeInfo.makerAssets),
                _hashAssets(_tradeInfo.takerAssets),
                _hashFees(_tradeInfo.makerFees),
                _hashFees(_tradeInfo.takerFees),
                _tradeInfo.maker,
                _tradeInfo.taker,
                _tradeInfo.duration,
                _tradeInfo.makerNonce,
                _tradeInfo.takerNonce
            )
        );

        return tradeInfoHash;
    }

    /// @dev Get eip 712 compliant hash for Fees struct type
    /// @param _fees Fees struct to be hashed
    function _hashFees(Fees calldata _fees) private view returns (bytes32) {
        bytes32 feesTypeHash = keccak256(
            abi.encode(
                FEES_TYPE_HASH,
                _fees.token,
                _fees.broker,
                _fees.platform,
                _fees.brokerAmount,
                _fees.platformAmount
            )
        );
        return feesTypeHash;
    }

    /// @dev Get eip 712 compliant hash for Assets struct type
    /// @param _assets Assetes struct to be hashed
    function _hashAssets(
        Assets calldata _assets
    ) private view returns (bytes32) {
        uint256 assetsCount = _assets.assets.length;
        bytes32[] memory assetsHashes = new bytes32[](assetsCount);

        for (uint i; i < assetsCount; ) {
            assetsHashes[i] = _hashAssetData(_assets.assets[i]);
            unchecked {
                ++i;
            }
        }

        bytes32 assetsTypeHash = keccak256(
            abi.encode(
                ASSETS_TYPE_HASH,
                keccak256(abi.encodePacked(assetsHashes))
            )
        );

        return assetsTypeHash;
    }

    /// @dev Get eip 712 compliant hash for AssetData struct type
    /// @param _asset AssetData struct to be hashed
    function _hashAssetData(
        AssetData calldata _asset
    ) private view returns (bytes32) {
        bytes32 assetDataTypeHash = keccak256(
            abi.encode(
                ASSET_DATA_TYPE_HASH,
                _asset.token,
                _asset.assetType,
                _asset.tokenId,
                _asset.amount
            )
        );

        return assetDataTypeHash;
    }
}
[{"inputs":[{"internalType":"enum BrokersSignatureUtils.BrokerSignatureUtilsErrorCodes","name":"code","type":"uint8"}],"name":"BrokerSignatureUtilsError","type":"error"},{"inputs":[{"internalType":"enum IBrokers.BrokersErrorCodes","name":"code","type":"uint8"}],"name":"BrokersError","type":"error"},{"anonymous":false,"inputs":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum AssetType","name":"assetType","type":"uint8"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct AssetData[]","name":"assets","type":"tuple[]"}],"indexed":false,"internalType":"struct Assets","name":"assets","type":"tuple"},{"indexed":false,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"AssetsTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"broker","type":"address"}],"name":"BrokerRegistered","type":"event"},{"anonymous":false,"inputs":[{"components":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum AssetType","name":"assetType","type":"uint8"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct AssetData[]","name":"assets","type":"tuple[]"}],"internalType":"struct Assets","name":"makerAssets","type":"tuple"},{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum AssetType","name":"assetType","type":"uint8"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct AssetData[]","name":"assets","type":"tuple[]"}],"internalType":"struct Assets","name":"takerAssets","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"broker","type":"address"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint256","name":"brokerAmount","type":"uint256"},{"internalType":"uint256","name":"platformAmount","type":"uint256"}],"internalType":"struct Fees","name":"makerFees","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"broker","type":"address"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint256","name":"brokerAmount","type":"uint256"},{"internalType":"uint256","name":"platformAmount","type":"uint256"}],"internalType":"struct Fees","name":"takerFees","type":"tuple"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"makerNonce","type":"uint256"},{"internalType":"uint256","name":"takerNonce","type":"uint256"}],"indexed":false,"internalType":"struct TradeInfo","name":"tradeInfo","type":"tuple"},{"indexed":true,"internalType":"address","name":"broker","type":"address"}],"name":"BrokerSwapped","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"from","type":"address"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"broker","type":"address"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint256","name":"brokerAmount","type":"uint256"},{"internalType":"uint256","name":"platformAmount","type":"uint256"}],"indexed":false,"internalType":"struct Fees","name":"fees","type":"tuple"}],"name":"FeeTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"NonceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"__nonce","type":"uint256"}],"name":"getNonce","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum AssetType","name":"assetType","type":"uint8"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct AssetData[]","name":"assets","type":"tuple[]"}],"internalType":"struct Assets","name":"makerAssets","type":"tuple"},{"components":[{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"enum AssetType","name":"assetType","type":"uint8"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct AssetData[]","name":"assets","type":"tuple[]"}],"internalType":"struct Assets","name":"takerAssets","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"broker","type":"address"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint256","name":"brokerAmount","type":"uint256"},{"internalType":"uint256","name":"platformAmount","type":"uint256"}],"internalType":"struct Fees","name":"makerFees","type":"tuple"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"broker","type":"address"},{"internalType":"address","name":"platform","type":"address"},{"internalType":"uint256","name":"brokerAmount","type":"uint256"},{"internalType":"uint256","name":"platformAmount","type":"uint256"}],"internalType":"struct Fees","name":"takerFees","type":"tuple"},{"internalType":"address","name":"maker","type":"address"},{"internalType":"address","name":"taker","type":"address"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"makerNonce","type":"uint256"},{"internalType":"uint256","name":"takerNonce","type":"uint256"}],"internalType":"struct TradeInfo","name":"tradeInfo","type":"tuple"},{"internalType":"bytes","name":"makerSignature","type":"bytes"},{"internalType":"bool","name":"makerContra","type":"bool"},{"internalType":"bytes","name":"takerSignature","type":"bytes"},{"internalType":"bool","name":"takerContra","type":"bool"}],"name":"swap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]