APE Price: $1.26 (+1.65%)

Contract Diff Checker

Contract Name:
PaymentProcessorEncoder

Contract Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @dev Storage data struct for stored approvals and order approvals
struct PackedApproval {
    // Only used for partial fill position 1155 transfers
    uint8 state;
    // Amount allowed
    uint200 amount;
    // Permission expiry
    uint48 expiration;
}

/// @dev Calldata data struct for order fill amounts
struct OrderFillAmounts {
    uint256 orderStartAmount;
    uint256 requestedFillAmount;
    uint256 minimumFillAmount;
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {OrderFillAmounts} from "../DataTypes.sol";

interface IPermitC {

    /**
     * =================================================
     * ==================== Events =====================
     * =================================================
     */

    /// @dev Emitted when an approval is stored
    event Approval(
        address indexed owner,
        address indexed token,
        address indexed operator,
        uint256 id,
        uint200 amount,
        uint48 expiration
    );

    /// @dev Emitted when a user increases their master nonce
    event Lockdown(address indexed owner);

    /// @dev Emitted when an order is opened
    event OrderOpened(
        bytes32 indexed orderId,
        address indexed owner,
        address indexed operator,
        uint256 fillableQuantity
    );

    /// @dev Emitted when an order has a fill
    event OrderFilled(
        bytes32 indexed orderId,
        address indexed owner,
        address indexed operator,
        uint256 amount
    );

    /// @dev Emitted when an order has been fully filled or cancelled
    event OrderClosed(
        bytes32 indexed orderId, 
        address indexed owner, 
        address indexed operator, 
        bool wasCancellation);

    /// @dev Emitted when an order has an amount restored due to a failed transfer
    event OrderRestored(
        bytes32 indexed orderId,
        address indexed owner,
        uint256 amountRestoredToOrder
    );

    /**
     * =================================================
     * ============== Approval Transfers ===============
     * =================================================
     */
    function approve(uint256 tokenType, address token, uint256 id, address operator, uint200 amount, uint48 expiration) external;

    function updateApprovalBySignature(
        uint256 tokenType,
        address token,
        uint256 id,
        uint256 nonce,
        uint200 amount,
        address operator,
        uint48 approvalExpiration,
        uint48 sigDeadline,
        address owner,
        bytes calldata signedPermit
    ) external;

    function allowance(
        address owner, 
        address operator, 
        uint256 tokenType,
        address token, 
        uint256 id
    ) external view returns (uint256 amount, uint256 expiration);

    /**
     * =================================================
     * ================ Signed Transfers ===============
     * =================================================
     */
    function registerAdditionalDataHash(string memory additionalDataTypeString) external;

    function permitTransferFromERC721(
        address token,
        uint256 id,
        uint256 nonce,
        uint256 expiration,
        address owner,
        address to,
        bytes calldata signedPermit
    ) external returns (bool isError);

    function permitTransferFromWithAdditionalDataERC721(
        address token,
        uint256 id,
        uint256 nonce,
        uint256 expiration,
        address owner,
        address to,
        bytes32 additionalData,
        bytes32 advancedPermitHash,
        bytes calldata signedPermit
    ) external returns (bool isError);

    function permitTransferFromERC1155(
        address token,
        uint256 id,
        uint256 nonce,
        uint256 permitAmount,
        uint256 expiration,
        address owner,
        address to,
        uint256 transferAmount,
        bytes calldata signedPermit
    ) external returns (bool isError);

    function permitTransferFromWithAdditionalDataERC1155(
        address token,
        uint256 id,
        uint256 nonce,
        uint256 permitAmount,
        uint256 expiration,
        address owner,
        address to,
        uint256 transferAmount,
        bytes32 additionalData,
        bytes32 advancedPermitHash,
        bytes calldata signedPermit
    ) external returns (bool isError);

    function permitTransferFromERC20(
        address token,
        uint256 nonce,
        uint256 permitAmount,
        uint256 expiration,
        address owner,
        address to,
        uint256 transferAmount,
        bytes calldata signedPermit
    ) external returns (bool isError);

    function permitTransferFromWithAdditionalDataERC20(
        address token,
        uint256 nonce,
        uint256 permitAmount,
        uint256 expiration,
        address owner,
        address to,
        uint256 transferAmount,
        bytes32 additionalData,
        bytes32 advancedPermitHash,
        bytes calldata signedPermit
    ) external returns (bool isError);

    function isRegisteredTransferAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered);

    function isRegisteredOrderAdditionalDataHash(bytes32 hash) external view returns (bool isRegistered);

    /**
     * =================================================
     * =============== Order Transfers =================
     * =================================================
     */
    function fillPermittedOrderERC1155(
        bytes calldata signedPermit,
        OrderFillAmounts calldata orderFillAmounts,
        address token,
        uint256 id,
        address owner,
        address to,
        uint256 nonce,
        uint48 expiration,
        bytes32 orderId,
        bytes32 advancedPermitHash
    ) external returns (uint256 quantityFilled, bool isError);

    function fillPermittedOrderERC20(
        bytes calldata signedPermit,
        OrderFillAmounts calldata orderFillAmounts,
        address token,
        address owner,
        address to,
        uint256 nonce,
        uint48 expiration,
        bytes32 orderId,
        bytes32 advancedPermitHash
    ) external returns (uint256 quantityFilled, bool isError);

    function closePermittedOrder(
        address owner,
        address operator,
        uint256 tokenType,
        address token,
        uint256 id,
        bytes32 orderId
    ) external;

    function allowance(
        address owner, 
        address operator, 
        uint256 tokenType,
        address token, 
        uint256 id,
        bytes32 orderId
    ) external view returns (uint256 amount, uint256 expiration);


    /**
     * =================================================
     * ================ Nonce Management ===============
     * =================================================
     */
    function invalidateUnorderedNonce(uint256 nonce) external;

    function isValidUnorderedNonce(address owner, uint256 nonce) external view returns (bool isValid);

    function lockdown() external;

    function masterNonce(address owner) external view returns (uint256);

    /**
     * =================================================
     * ============== Transfer Functions ===============
     * =================================================
     */
    function transferFromERC721(
        address from,
        address to,
        address token,
        uint256 id
    ) external returns (bool isError);

    function transferFromERC1155(
        address from,
        address to,
        address token,
        uint256 id,
        uint256 amount
    ) external returns (bool isError);

    function transferFromERC20(
        address from,
        address to,
        address token,
        uint256 amount
    ) external returns (bool isError);

    /**
     * =================================================
     * ============ Signature Verification =============
     * =================================================
     */
    function domainSeparatorV4() external view returns (bytes32);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (metatx/ERC2771Context.sol)
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/Context.sol";
import "./interfaces/ITrustedForwarderFactory.sol";

/**
 * @title TrustedForwarderERC2771Context
 * @author Limit Break, Inc.
 * @notice Context variant that utilizes the TrustedForwarderFactory contract to determine if the sender is a trusted forwarder.
 */
abstract contract TrustedForwarderERC2771Context is Context {
    ITrustedForwarderFactory private immutable _factory;

    constructor(address factory) {
        _factory = ITrustedForwarderFactory(factory);
    }

    /**
     * @notice Returns true if the sender is a trusted forwarder, false otherwise.
     *
     * @dev    This function is required by ERC2771Context.
     *
     * @param forwarder The address to check.
     * @return True if the provided address is a trusted forwarder, false otherwise.
     */
    function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
        return _factory.isTrustedForwarder(forwarder);
    }

    function _msgSender() internal view virtual override returns (address sender) {
        if (_factory.isTrustedForwarder(msg.sender)) {
            if (msg.data.length >= 20) {
                // The assembly code is more direct than the Solidity version using `abi.decode`.
                /// @solidity memory-safe-assembly
                assembly {
                    sender := shr(96, calldataload(sub(calldatasize(), 20)))
                }
            } else {
                return super._msgSender();
            }
        } else {
            return super._msgSender();
        }
    }

    function _msgData() internal view virtual override returns (bytes calldata data) {
        if (_factory.isTrustedForwarder(msg.sender)) {
            assembly {
                let len := calldatasize()
                // Create a slice that defaults to the entire calldata
                data.offset := 0
                data.length := len
                // If the calldata is > 20 bytes, it contains the sender address at the end
                // and needs to be truncated
                if gt(len, 0x14) {
                    data.length := sub(len, 0x14)
                }
            }
        } else {
            return super._msgData();
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

interface ITrustedForwarderFactory {
    error TrustedForwarderFactory__TrustedForwarderInitFailed(address admin, address appSigner);

    event TrustedForwarderCreated(address indexed trustedForwarder);

    function cloneTrustedForwarder(address admin, address appSigner, bytes32 salt)
        external
        returns (address trustedForwarder);
    function forwarders(address) external view returns (bool);
    function isTrustedForwarder(address sender) external view returns (bool);
    function trustedForwarderImplementation() external view returns (address);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}

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

pragma solidity ^0.8.0;

import "../Strings.sol";

/**
 * @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 {
    enum RecoverError {
        NoError,
        InvalidSignature,
        InvalidSignatureLength,
        InvalidSignatureS,
        InvalidSignatureV // Deprecated in v4.8
    }

    function _throwError(RecoverError error) private pure {
        if (error == RecoverError.NoError) {
            return; // no error: do nothing
        } else if (error == RecoverError.InvalidSignature) {
            revert("ECDSA: invalid signature");
        } else if (error == RecoverError.InvalidSignatureLength) {
            revert("ECDSA: invalid signature length");
        } else if (error == RecoverError.InvalidSignatureS) {
            revert("ECDSA: invalid signature 's' value");
        }
    }

    /**
     * @dev Returns the address that signed a hashed message (`hash`) with
     * `signature` or error string. 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.
     *
     * Documentation for signature generation:
     * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
     * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
        if (signature.length == 65) {
            bytes32 r;
            bytes32 s;
            uint8 v;
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            /// @solidity memory-safe-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
            return tryRecover(hash, v, r, s);
        } else {
            return (address(0), RecoverError.InvalidSignatureLength);
        }
    }

    /**
     * @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) {
        (address recovered, RecoverError error) = tryRecover(hash, signature);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
     *
     * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError) {
        bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
        uint8 v = uint8((uint256(vs) >> 255) + 27);
        return tryRecover(hash, v, r, s);
    }

    /**
     * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
     *
     * _Available since v4.2._
     */
    function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
        (address recovered, RecoverError error) = tryRecover(hash, r, vs);
        _throwError(error);
        return recovered;
    }

    /**
     * @dev Overload of {ECDSA-tryRecover} that receives the `v`,
     * `r` and `s` signature fields separately.
     *
     * _Available since v4.3._
     */
    function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address, RecoverError) {
        // 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 (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): 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.
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            return (address(0), RecoverError.InvalidSignatureS);
        }

        // If the signature is valid (and not malleable), return the signer address
        address signer = ecrecover(hash, v, r, s);
        if (signer == address(0)) {
            return (address(0), RecoverError.InvalidSignature);
        }

        return (signer, RecoverError.NoError);
    }

    /**
     * @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) {
        (address recovered, RecoverError error) = tryRecover(hash, v, r, s);
        _throwError(error);
        return recovered;
    }

    /**
     * @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 message) {
        // 32 is the length in bytes of hash,
        // enforced by the type signature above
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, "\x19Ethereum Signed Message:\n32")
            mstore(0x1c, hash)
            message := keccak256(0x00, 0x3c)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Message, created from `s`. 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(bytes memory s) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s));
    }

    /**
     * @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 data) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(ptr, "\x19\x01")
            mstore(add(ptr, 0x02), domainSeparator)
            mstore(add(ptr, 0x22), structHash)
            data := keccak256(ptr, 0x42)
        }
    }

    /**
     * @dev Returns an Ethereum Signed Data with intended validator, created from a
     * `validator` and `data` according to the version 0 of EIP-191.
     *
     * See {recover}.
     */
    function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19\x00", validator, data));
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity ^0.8.0;

/*
# PolyForm Strict License 1.0.0

<https://polyformproject.org/licenses/strict/1.0.0>

## Acceptance

In order to get any license under these terms, you must agree
to them as both strict obligations and conditions to all
your licenses.

## Copyright License

The licensor grants you a copyright license for the software
to do everything you might do with the software that would
otherwise infringe the licensor's copyright in it for any
permitted purpose, other than distributing the software or
making changes or new works based on the software.

## Patent License

The licensor grants you a patent license for the software that
covers patent claims the licensor can license, or becomes able
to license, that you would infringe by using the software.

## Noncommercial Purposes

Any noncommercial purpose is a permitted purpose.

## Personal Uses

Personal use for research, experiment, and testing for
the benefit of public knowledge, personal study, private
entertainment, hobby projects, amateur pursuits, or religious
observance, without any anticipated commercial application,
is use for a permitted purpose.

## Noncommercial Organizations

Use by any charitable organization, educational institution,
public research organization, public safety or health
organization, environmental protection organization,
or government institution is use for a permitted purpose
regardless of the source of funding or obligations resulting
from the funding.

## Fair Use

You may have "fair use" rights for the software under the
law. These terms do not limit them.

## No Other Rights

These terms do not allow you to sublicense or transfer any of
your licenses to anyone else, or prevent the licensor from
granting licenses to anyone else.  These terms do not imply
any other licenses.

## Patent Defense

If you make any written claim that the software infringes or
contributes to infringement of any patent, your patent license
for the software granted under these terms ends immediately. If
your company makes such a claim, your patent license ends
immediately for work on behalf of your company.

## Violations

The first time you are notified in writing that you have
violated any of these terms, or done anything with the software
not covered by your licenses, your licenses can nonetheless
continue if you come into full compliance with these terms,
and take practical steps to correct past violations, within
32 days of receiving notice.  Otherwise, all your licenses
end immediately.

## No Liability

***As far as the law allows, the software comes as is, without
any warranty or condition, and the licensor will not be liable
to you for any damages arising out of these terms or the use
or nature of the software, under any kind of legal claim.***

## Definitions

The **licensor** is the individual or entity offering these
terms, and the **software** is the software the licensor makes
available under these terms.

**You** refers to the individual or entity agreeing to these
terms.

**Your company** is any legal entity, sole proprietorship,
or other kind of organization that you work for, plus all
organizations that have control over, are under the control of,
or are under common control with that organization.  **Control**
means ownership of substantially all the assets of an entity,
or the power to direct its management and policies by vote,
contract, or otherwise.  Control can be direct or indirect.

**Your licenses** are all the licenses granted to you for the
software under these terms.

**Use** means anything you do with the software requiring one
of your licenses.
*/

pragma solidity ^0.8.4;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the value of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers a `value` amount of tokens of type `id` from `from` to `to`.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155Received} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `value` amount.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * WARNING: This function can potentially allow a reentrancy attack when transferring tokens
     * to an untrusted contract, when invoking {onERC1155BatchReceived} on the receiver.
     * Ensure to follow the checks-effects-interactions pattern and consider employing
     * reentrancy guards when interacting with untrusted contracts.
     *
     * Emits either a {TransferSingle} or a {TransferBatch} event, depending on the length of the array arguments.
     *
     * Requirements:
     *
     * - `ids` and `values` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external;
}

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

library SafeERC20 {
    /**
     * @dev A gas efficient, and fallback-safe method to transfer ERC20 tokens owned by the contract.
     * 
     * @param tokenAddress  The address of the token to transfer.
     * @param to            The address to transfer tokens to.
     * @param amount        The amount of tokens to transfer.
     * 
     * @return isError      True if there was an error transferring, false if the call was successful.
     */
    function safeTransfer(
        address tokenAddress,
        address to,
        uint256 amount
    ) internal returns(bool isError) {
        assembly {
            function _callTransfer(_tokenAddress, _to, _amount) -> _isError {
                let ptr := mload(0x40)
                mstore(0x40, add(ptr, 0x60))
                mstore(ptr, 0xa9059cbb)
                mstore(add(0x20, ptr), _to)
                mstore(add(0x40, ptr), _amount)
                if call(gas(), _tokenAddress, 0, add(ptr, 0x1C), 0x44, 0x00, 0x00) {
                    if lt(returndatasize(), 0x20) {
                        _isError := iszero(extcodesize(_tokenAddress))
                        leave
                    }
                    returndatacopy(0x00, 0x00, 0x20)
                    _isError := iszero(mload(0x00))
                    leave
                }
                _isError := true
            }
            isError := _callTransfer(tokenAddress, to, amount)
        }
    }

    /**
     * @dev A gas efficient, and fallback-safe method to transfer ERC20 tokens owned by another address.
     * 
     * @param tokenAddress  The address of the token to transfer.
     * @param from          The address to transfer tokens from.
     * @param to            The address to transfer tokens to.
     * @param amount        The amount of tokens to transfer.
     * 
     * @return isError      True if there was an error transferring, false if the call was successful.
     */
    function safeTransferFrom(
        address tokenAddress,
        address from,
        address to,
        uint256 amount
    ) internal returns(bool isError) {
        assembly {
            function _callTransferFrom(_tokenAddress, _from, _to, _amount) -> _isError {
                let ptr := mload(0x40)
                mstore(0x40, add(ptr, 0x80))
                mstore(ptr, 0x23b872dd)
                mstore(add(0x20, ptr), _from)
                mstore(add(0x40, ptr), _to)
                mstore(add(0x60, ptr), _amount)
                if call(gas(), _tokenAddress, 0, add(ptr, 0x1C), 0x64, 0x00, 0x00) {
                    if lt(returndatasize(), 0x20) {
                        _isError := iszero(extcodesize(_tokenAddress))
                        leave
                    }
                    returndatacopy(0x00, 0x00, 0x20)
                    _isError := iszero(mload(0x00))
                    leave
                }
                _isError := true
            }
            isError := _callTransferFrom(tokenAddress, from, to, amount)
        }
    }

    /**
     * @dev A gas efficient, and fallback-safe method to set approval on ERC20 tokens.
     * 
     * @param tokenAddress  The address of the token to transfer.
     * @param spender       The address to allow to spend tokens.
     * @param allowance     The amount of tokens to allow `spender` to transfer.
     * 
     * @return isError      True if there was an error setting allowance, false if the call was successful.
     */
    function safeApprove(
        address tokenAddress,
        address spender,
        uint256 allowance
    ) internal returns(bool isError) {
        assembly {
            function _callApprove(_tokenAddress, _spender, _allowance) -> _isError {
                let ptr := mload(0x40)
                mstore(0x40, add(ptr, 0x60))
                mstore(ptr, 0x095ea7b3)
                mstore(add(0x20, ptr), _spender)
                mstore(add(0x40, ptr), _allowance)
                if call(gas(), _tokenAddress, 0, add(ptr, 0x1C), 0x44, 0x00, 0x00) {
                    if lt(returndatasize(), 0x20) {
                        _isError := iszero(extcodesize(_tokenAddress))
                        leave
                    }
                    returndatacopy(0x00, 0x00, 0x20)
                    _isError := iszero(mload(0x00))
                    leave
                }
                _isError := true
            }
            isError := _callApprove(tokenAddress, spender, allowance)
        }
    }

    /**
     * @dev A gas efficient, and fallback-safe method to set approval on ERC20 tokens.
     * @dev If the initial approve fails, it will retry setting the allowance to zero and then
     * @dev to the new allowance.
     * 
     * @param tokenAddress  The address of the token to transfer.
     * @param spender       The address to allow to spend tokens.
     * @param allowance     The amount of tokens to allow `spender` to transfer.
     * 
     * @return isError      True if there was an error setting allowance, false if the call was successful.
     */
    function safeApproveWithRetryAfterZero(
        address tokenAddress,
        address spender,
        uint256 allowance
    ) internal returns(bool isError) {
        assembly {
            function _callApprove(_ptr, _tokenAddress, _spender, _allowance) -> _isError {
                mstore(add(0x40, _ptr), _allowance)
                if call(gas(), _tokenAddress, 0, add(_ptr, 0x1C), 0x44, 0x00, 0x00) {
                    if lt(returndatasize(), 0x20) {
                        _isError := iszero(extcodesize(_tokenAddress))
                        leave
                    }
                    returndatacopy(0x00, 0x00, 0x20)
                    _isError := iszero(mload(0x00))
                    leave
                }
                _isError := true
            }

            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x60))
            mstore(ptr, 0x095ea7b3)
            mstore(add(0x20, ptr), spender)

            isError := _callApprove(ptr, tokenAddress, spender, allowance)
            if isError {
                pop(_callApprove(ptr, tokenAddress, spender, 0x00))
                isError := _callApprove(ptr, tokenAddress, spender, allowance)
            }
        }
    }
}

pragma solidity ^0.8.4;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

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

/**
 * @title  EfficientHash
 * 
 * @author Limit Break
 * 
 * @notice Performs keccak256 hashing of value type parameters more efficiently than 
 * @notice high-level Solidity by utilizing scratch space for one or two values and
 * @notice efficient utilization of memory for parameter counts greater than two.
 * 
 * @notice Gas savings for EfficientHash compared to keccak256(abi.encode(...)):
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 1 / 67 / 67
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 5 / 66 / 66
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 10 / 58 / 58
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 15 / 1549 / 565
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 20 / 3379 / 1027
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 25 / 5807 / 1650
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 50 / 23691 / 10107
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 75 / 69164 / 41620
 * @notice Parameter Count / Gas Savings (Shanghai) / Gas Savings (Cancun): 99 / 172694 / 126646
 * 
 * @dev    Notes:
 * @dev    - `efficientHash` is overloaded for parameter counts between one and eight.
 * @dev    - Parameter counts between nine and sixteen require two functions to avoid
 * @dev        stack too deep errors. Each parameter count has a dedicated set of functions
 * @dev        (`efficientHashNineStep1`/`efficientHashNineStep2` ... `efficientHashSixteenStep1`/`efficientHashSixteenStep2`)
 * @dev        that must both be called to get the hash. 
 * @dev        `Step1` functions take eight parameters and return a memory pointer that is passed to `Step2`
 * @dev        `Step2` functions take the remaining parameters and return the hash of the values
 * @dev        Example: 
 * @dev              bytes32 hash = EfficientHash.efficientHashElevenStep2(
 * @dev                                   EfficientHash.efficientHashElevenStep1(
 * @dev                                       value1,
 * @dev                                       value2,
 * @dev                                       value3,
 * @dev                                       value4,
 * @dev                                       value5,
 * @dev                                       value6,
 * @dev                                       value7,
 * @dev                                       value8
 * @dev                                   ),
 * @dev                                   value9,
 * @dev                                   value10,
 * @dev                                   value11,
 * @dev                               );
 * @dev    - Parameter counts greater than sixteen must use the `Extension` functions.
 * @dev        Extension starts with `efficientHashExtensionStart` which takes the number
 * @dev        of parameters and the first eight parameters as an input and returns a
 * @dev        memory pointer that is passed to the `Continue` and `End` functions.
 * @dev        While the number of parameters remaining is greater than eight, call the
 * @dev        `efficientHashExtensionContinue` function with the pointer value and 
 * @dev        the next eight values.
 * @dev        When the number of parameters remaining is less than or equal to eight
 * @dev        call the `efficientHashExtensionEnd` function with the pointer value
 * @dev        and remaining values.
 * @dev        Example: 
 * @dev            bytes32 hash = EfficientHash.efficientHashExtensionEnd(
 * @dev                             EfficientHash.efficientHashExtensionContinue(
 * @dev                                 EfficientHash.efficientHashExtensionStart(
 * @dev                                     23,
 * @dev                                     value1,
 * @dev                                     value2,
 * @dev                                     value3,
 * @dev                                     value4,
 * @dev                                     value5,
 * @dev                                     value6,
 * @dev                                     value7,
 * @dev                                     value8
 * @dev                                 ), 
 * @dev                                 value9,
 * @dev                                 value10,
 * @dev                                 value11,
 * @dev                                 value12,
 * @dev                                 value13,
 * @dev                                 value14,
 * @dev                                 value15,
 * @dev                                 value16
 * @dev                             ),
 * @dev                             value17,
 * @dev                             value18,
 * @dev                             value19,
 * @dev                             value20,
 * @dev                             value21,
 * @dev                             value22,
 * @dev                             value23
 * @dev                         );
 */
library EfficientHash {
    
    /**
     * @notice Hashes one value type.
     * 
     * @param value The value to be hashed.
     * 
     * @return hash The hash of the value.
     */
    function efficientHash(bytes32 value) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(0x00, value)

            hash := keccak256(0x00, 0x20)
        }
    }
    
    /**
     * @notice Hashes two value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(bytes32 value1, bytes32 value2) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(0x00, value1)
            mstore(0x20, value2)
            
            hash := keccak256(0x00, 0x40)
        }
    }
    
    /**
     * @notice Hashes three value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(bytes32 value1, bytes32 value2, bytes32 value3) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x60))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            
            hash := keccak256(ptr, 0x60)
        }
    }
    
    /**
     * @notice Hashes four value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x80))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            
            hash := keccak256(ptr, 0x80)
        }
    }
    
    /**
     * @notice Hashes five value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0xA0))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            
            hash := keccak256(ptr, 0xA0)
        }
    }
    
    /**
     * @notice Hashes six value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0xC0))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            
            hash := keccak256(ptr, 0xC0)
        }
    }
    
    /**
     * @notice Hashes seven value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0xE0))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            
            hash := keccak256(ptr, 0xE0)
        }
    }
    
    /**
     * @notice Hashes eight value types.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHash(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x100))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
            
            hash := keccak256(ptr, 0x100)
        }
    }
    
    /**
     * @notice Step one of hashing nine values. Must be followed by `efficientHashNineStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashNineStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x120))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing nine values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashNineStep2(
        uint256 ptr,
        bytes32 value9
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)

            hash := keccak256(ptr, 0x120)
        }
    }
    
    /**
     * @notice Step one of hashing ten values. Must be followed by `efficientHashTenStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashTenStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x140))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing ten values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashTenStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)

            hash := keccak256(ptr, 0x140)
        }
    }
    
    /**
     * @notice Step one of hashing eleven values. Must be followed by `efficientHashElevenStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashElevenStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x160))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing eleven values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * @param value11 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashElevenStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10, bytes32 value11
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)
            mstore(add(ptr, 0x140), value11)

            hash := keccak256(ptr, 0x160)
        }
    }
    
    /**
     * @notice Step one of hashing twelve values. Must be followed by `efficientHashTwelveStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashTwelveStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x180))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing twelve values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * @param value11 Value to be hashed.
     * @param value12 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashTwelveStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)
            mstore(add(ptr, 0x140), value11)
            mstore(add(ptr, 0x160), value12)

            hash := keccak256(ptr, 0x180)
        }
    }
    
    /**
     * @notice Step one of hashing thirteen values. Must be followed by `efficientHashThirteenStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashThirteenStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x1A0))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing thirteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * @param value11 Value to be hashed.
     * @param value12 Value to be hashed.
     * @param value13 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashThirteenStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
        bytes32 value13
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)
            mstore(add(ptr, 0x140), value11)
            mstore(add(ptr, 0x160), value12)
            mstore(add(ptr, 0x180), value13)

            hash := keccak256(ptr, 0x1A0)
        }
    }
    
    /**
     * @notice Step one of hashing fourteen values. Must be followed by `efficientHashFourteenStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashFourteenStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x1C0))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing fourteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * @param value11 Value to be hashed.
     * @param value12 Value to be hashed.
     * @param value13 Value to be hashed.
     * @param value14 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashFourteenStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
        bytes32 value13, bytes32 value14
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)
            mstore(add(ptr, 0x140), value11)
            mstore(add(ptr, 0x160), value12)
            mstore(add(ptr, 0x180), value13)
            mstore(add(ptr, 0x1A0), value14)

            hash := keccak256(ptr, 0x1C0)
        }
    }
    
    /**
     * @notice Step one of hashing fifteen values. Must be followed by `efficientHashFifteenStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashFifteenStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x1E0))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing fifteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * @param value11 Value to be hashed.
     * @param value12 Value to be hashed.
     * @param value13 Value to be hashed.
     * @param value14 Value to be hashed.
     * @param value15 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashFifteenStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
        bytes32 value13, bytes32 value14, bytes32 value15
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)
            mstore(add(ptr, 0x140), value11)
            mstore(add(ptr, 0x160), value12)
            mstore(add(ptr, 0x180), value13)
            mstore(add(ptr, 0x1A0), value14)
            mstore(add(ptr, 0x1C0), value15)

            hash := keccak256(ptr, 0x1E0)
        }
    }
    
    /**
     * @notice Step one of hashing sixteen values. Must be followed by `efficientHashSixteenStep2` to hash the values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashSixteenStep1(
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(ptr, 0x200))

            mstore(ptr, value1)
            mstore(add(ptr, 0x20), value2)
            mstore(add(ptr, 0x40), value3)
            mstore(add(ptr, 0x60), value4)
            mstore(add(ptr, 0x80), value5)
            mstore(add(ptr, 0xA0), value6)
            mstore(add(ptr, 0xC0), value7)
            mstore(add(ptr, 0xE0), value8)
        }
    }
    
    /**
     * @notice Step two of hashing sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value9  Value to be hashed.
     * @param value10 Value to be hashed.
     * @param value11 Value to be hashed.
     * @param value12 Value to be hashed.
     * @param value13 Value to be hashed.
     * @param value14 Value to be hashed.
     * @param value15 Value to be hashed.
     * @param value16 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashSixteenStep2(
        uint256 ptr,
        bytes32 value9, bytes32 value10, bytes32 value11, bytes32 value12,
        bytes32 value13, bytes32 value14, bytes32 value15, bytes32 value16
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            mstore(add(ptr, 0x100), value9)
            mstore(add(ptr, 0x120), value10)
            mstore(add(ptr, 0x140), value11)
            mstore(add(ptr, 0x160), value12)
            mstore(add(ptr, 0x180), value13)
            mstore(add(ptr, 0x1A0), value14)
            mstore(add(ptr, 0x1C0), value15)
            mstore(add(ptr, 0x1E0), value16)

            hash := keccak256(ptr, 0x200)
        }
    }
    
    /**
     * @notice Step one of hashing more than sixteen values.
     * @notice Must be followed by at least one call to 
     * @notice `efficientHashExtensionContinue` and completed with
     * @notice a call to `efficientHashExtensionEnd` with the remaining
     * @notice values.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptr The memory pointer location for the values to hash.
     */
    function efficientHashExtensionStart(
        uint256 numberOfValues,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptr) {
        assembly ("memory-safe") {
            ptr := mload(0x40)
            mstore(0x40, add(add(ptr, 0x20), mul(numberOfValues, 0x20)))
            mstore(ptr, 0x100)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)
            mstore(add(ptr, 0xA0), value5)
            mstore(add(ptr, 0xC0), value6)
            mstore(add(ptr, 0xE0), value7)
            mstore(add(ptr, 0x100), value8)
        }
    }
    
    /**
     * @notice Second step of hashing more than sixteen values.
     * @notice Adds another eight values to the values to be hashed.
     * @notice May be called as many times as necessary until the values
     * @notice remaining to be added to the hash is less than or equal to
     * @notice eight.
     * 
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return ptrReturn The memory pointer location for the values to hash.
     */
    function efficientHashExtensionContinue(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(uint256 ptrReturn) {
        assembly ("memory-safe") {
            ptrReturn := ptr
            let length := mload(ptrReturn)
            mstore(ptrReturn, add(length, 0x100))

            ptr := add(ptrReturn, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)
            mstore(add(ptr, 0xA0), value5)
            mstore(add(ptr, 0xC0), value6)
            mstore(add(ptr, 0xE0), value7)
            mstore(add(ptr, 0x100), value8)
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0x20))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0x40))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0x60))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0x80))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)
            mstore(add(ptr, 0xA0), value5)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0xA0))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)
            mstore(add(ptr, 0xA0), value5)
            mstore(add(ptr, 0xC0), value6)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0xC0))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)
            mstore(add(ptr, 0xA0), value5)
            mstore(add(ptr, 0xC0), value6)
            mstore(add(ptr, 0xE0), value7)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0xE0))
        }
    }

    /**
     * @notice Final step of hashing more than sixteen values.
     * 
     * @param ptr    The memory pointer location for the values to hash.
     * @param value1 Value to be hashed.
     * @param value2 Value to be hashed.
     * @param value3 Value to be hashed.
     * @param value4 Value to be hashed.
     * @param value5 Value to be hashed.
     * @param value6 Value to be hashed.
     * @param value7 Value to be hashed.
     * @param value8 Value to be hashed.
     * 
     * @return hash The hash of the values.
     */
    function efficientHashExtensionEnd(
        uint256 ptr,
        bytes32 value1, bytes32 value2, bytes32 value3, bytes32 value4,
        bytes32 value5, bytes32 value6, bytes32 value7, bytes32 value8
    ) internal pure returns(bytes32 hash) {
        assembly ("memory-safe") {
            let ptrStart := ptr
            let length := mload(ptrStart)

            ptr := add(ptrStart, length)
            
            mstore(add(ptr, 0x20), value1)
            mstore(add(ptr, 0x40), value2)
            mstore(add(ptr, 0x60), value3)
            mstore(add(ptr, 0x80), value4)
            mstore(add(ptr, 0xA0), value5)
            mstore(add(ptr, 0xC0), value6)
            mstore(add(ptr, 0xE0), value7)
            mstore(add(ptr, 0x100), value8)

            hash := keccak256(add(ptrStart, 0x20), add(length, 0x100))
        }
    }
}

pragma solidity ^0.8.4;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

pragma solidity ^0.8.4;

/**
 * @dev Library for managing
 * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
 * types.
 *
 * Sets have the following properties:
 *
 * - Elements are added, removed, and checked for existence in constant time
 * (O(1)).
 * - Elements are enumerated in O(n). No guarantees are made on the ordering.
 *
 * ```solidity
 * contract Example {
 *     // Add the library methods
 *     using EnumerableSet for EnumerableSet.AddressSet;
 *
 *     // Declare a set state variable
 *     EnumerableSet.AddressSet private mySet;
 * }
 * ```
 *
 * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
 * and `uint256` (`UintSet`) are supported.
 *
 * [WARNING]
 * ====
 * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
 * unusable.
 * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
 *
 * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
 * array of EnumerableSet.
 * ====
 */
library EnumerableSet {
    // To implement this library for multiple types with as little code
    // repetition as possible, we write it in terms of a generic Set type with
    // bytes32 values.
    // The Set implementation uses private functions, and user-facing
    // implementations (such as AddressSet) are just wrappers around the
    // underlying Set.
    // This means that we can only create new EnumerableSets for types that fit
    // in bytes32.

    struct Set {
        // Storage of set values
        bytes32[] _values;
        // Position is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function _add(Set storage set, bytes32 value) private returns (bool) {
        if (!_contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._positions[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function _remove(Set storage set, bytes32 value) private returns (bool) {
        // We cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 0) {
            // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.

            uint256 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[value];

            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function _contains(Set storage set, bytes32 value) private view returns (bool) {
        return set._positions[value] != 0;
    }

    /**
     * @dev Returns the number of values on the set. O(1).
     */
    function _length(Set storage set) private view returns (uint256) {
        return set._values.length;
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function _at(Set storage set, uint256 index) private view returns (bytes32) {
        return set._values[index];
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function _values(Set storage set) private view returns (bytes32[] memory) {
        return set._values;
    }

    // Bytes32Set

    struct Bytes32Set {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _add(set._inner, value);
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
        return _remove(set._inner, value);
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
        return _contains(set._inner, value);
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(Bytes32Set storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
        return _at(set._inner, index);
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
        bytes32[] memory store = _values(set._inner);
        bytes32[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // AddressSet

    struct AddressSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(AddressSet storage set, address value) internal returns (bool) {
        return _add(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(AddressSet storage set, address value) internal returns (bool) {
        return _remove(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(AddressSet storage set, address value) internal view returns (bool) {
        return _contains(set._inner, bytes32(uint256(uint160(value))));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(AddressSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(AddressSet storage set, uint256 index) internal view returns (address) {
        return address(uint160(uint256(_at(set._inner, index))));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(AddressSet storage set) internal view returns (address[] memory) {
        bytes32[] memory store = _values(set._inner);
        address[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }

    // UintSet

    struct UintSet {
        Set _inner;
    }

    /**
     * @dev Add a value to a set. O(1).
     *
     * Returns true if the value was added to the set, that is if it was not
     * already present.
     */
    function add(UintSet storage set, uint256 value) internal returns (bool) {
        return _add(set._inner, bytes32(value));
    }

    /**
     * @dev Removes a value from a set. O(1).
     *
     * Returns true if the value was removed from the set, that is if it was
     * present.
     */
    function remove(UintSet storage set, uint256 value) internal returns (bool) {
        return _remove(set._inner, bytes32(value));
    }

    /**
     * @dev Returns true if the value is in the set. O(1).
     */
    function contains(UintSet storage set, uint256 value) internal view returns (bool) {
        return _contains(set._inner, bytes32(value));
    }

    /**
     * @dev Returns the number of values in the set. O(1).
     */
    function length(UintSet storage set) internal view returns (uint256) {
        return _length(set._inner);
    }

    /**
     * @dev Returns the value stored at position `index` in the set. O(1).
     *
     * Note that there are no guarantees on the ordering of values inside the
     * array, and it may change when more values are added or removed.
     *
     * Requirements:
     *
     * - `index` must be strictly less than {length}.
     */
    function at(UintSet storage set, uint256 index) internal view returns (uint256) {
        return uint256(_at(set._inner, index));
    }

    /**
     * @dev Return the entire set in an array
     *
     * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
     * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
     * this function has an unbounded cost, and using it as part of a state-changing function may render the function
     * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
     */
    function values(UintSet storage set) internal view returns (uint256[] memory) {
        bytes32[] memory store = _values(set._inner);
        uint256[] memory result;

        /// @solidity memory-safe-assembly
        assembly {
            result := store
        }

        return result;
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

bytes32 constant COSIGNATURE_HASH = keccak256("Cosignature(uint8 v,bytes32 r,bytes32 s,uint256 expiration,address taker)");
bytes32 constant COLLECTION_OFFER_APPROVAL_HASH = keccak256("CollectionOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)");
bytes32 constant ITEM_OFFER_APPROVAL_HASH = keccak256("ItemOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce)");
bytes32 constant TOKEN_SET_OFFER_APPROVAL_HASH = keccak256("TokenSetOfferApproval(uint8 protocol,address cosigner,address buyer,address beneficiary,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 nonce,uint256 masterNonce,bytes32 tokenSetMerkleRoot)");
bytes32 constant SALE_APPROVAL_HASH = keccak256("SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)");
bytes32 constant PERMITTED_TRANSFER_SALE_APPROVAL = keccak256("PermitTransferFromWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 nonce,address operator,uint256 expiration,uint256 masterNonce,SaleApproval approval)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)");
bytes32 constant PERMITTED_ORDER_SALE_APPROVAL = keccak256("PermitOrderWithAdditionalData(uint256 tokenType,address token,uint256 id,uint256 amount,uint256 salt,address operator,uint256 expiration,uint256 masterNonce,SaleApproval approval)SaleApproval(uint8 protocol,address cosigner,address seller,address marketplace,address fallbackRoyaltyRecipient,address paymentMethod,address tokenAddress,uint256 tokenId,uint256 amount,uint256 itemPrice,uint256 expiration,uint256 marketplaceFeeNumerator,uint256 maxRoyaltyFeeNumerator,uint256 nonce,uint256 masterNonce,uint256 protocolFeeVersion)");

// The denominator used when calculating the marketplace fee.
// 0.5% fee numerator is 50, 1% fee numerator is 100, 10% fee numerator is 1,000 and so on.
uint256 constant FEE_DENOMINATOR = 100_00;

// Default Payment Method Whitelist Id
uint32 constant DEFAULT_PAYMENT_METHOD_WHITELIST_ID = 0;

// Convenience to avoid magic number in bitmask get/set logic.
uint256 constant ZERO = uint256(0);
uint256 constant ONE = uint256(1);

/**
 * @dev Defines condition to apply to order execution.
 */
// 0: ERC721 order that must execute in full or not at all.
uint256 constant ORDER_PROTOCOLS_ERC721_FILL_OR_KILL = 0;
// 1: ERC1155 order that must execute in full or not at all.
uint256 constant ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL = 1;
// 2: ERC1155 order that may be partially executed.
uint256 constant ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL = 2;
// 3: Invalid order protocol for type check.
uint256 constant ORDER_PROTOCOLS_INVALID = 3;

/**
 * @dev Defines types of offers to be executed
 */
// 0: Offer for any item in a collection.
uint256 constant OFFER_TYPE_COLLECTION_OFFER = 0;
// 1: Offer for a specific item in a collection.
uint256 constant OFFER_TYPE_ITEM_OFFER = 1;
// 2: Offer for a set of tokens in a collection.
uint256 constant OFFER_TYPE_TOKEN_SET_OFFER = 2;

/// @dev The default protocol fee settings.
uint16 constant DEFAULT_PROTOCOL_FEE_MINIMUM_BPS = 25;
uint16 constant DEFAULT_PROTOCOL_FEE_MARKETPLACE_TAX_BPS = 15_00;
uint16 constant DEFAULT_PROTOCOL_FEE_FEE_ON_TOP_TAX_BPS = 25_00;

// The default admin role for NFT collections using Access Control.
bytes32 constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;

/// @dev The plain text message to sign for cosigner self-destruct signature verification
string constant COSIGNER_SELF_DESTRUCT_MESSAGE_TO_SIGN = "COSIGNER_SELF_DESTRUCT";

/**************************************************************/
/*                   PRECOMPUTED SELECTORS                    */
/**************************************************************/

bytes4 constant SELECTOR_DESTROY_COSIGNER = hex"aa04bf59";
bytes4 constant SELECTOR_REVOKE_MASTER_NONCE = hex"226d4adb";
bytes4 constant SELECTOR_REVOKE_SINGLE_NONCE = hex"b6d7dc33";
bytes4 constant SELECTOR_REVOKE_ORDER_DIGEST = hex"96ae0380";

bytes4 constant SELECTOR_BUY_LISTING = hex"27c46dc4";
bytes4 constant SELECTOR_ACCEPT_OFFER = hex"dbbb7e9d";
bytes4 constant SELECTOR_BULK_BUY_LISTINGS = hex"69bdcb08";
bytes4 constant SELECTOR_BULK_ACCEPT_OFFERS = hex"0aa91514";
bytes4 constant SELECTOR_SWEEP_COLLECTION = hex"6a89f68a";

bytes4 constant SELECTOR_BUY_LISTING_ADVANCED = hex"679d2803";
bytes4 constant SELECTOR_ACCEPT_OFFER_ADVANCED = hex"06814765";
bytes4 constant SELECTOR_BULK_BUY_LISTINGS_ADVANCED = hex"7497030d";
bytes4 constant SELECTOR_BULK_ACCEPT_OFFERS_ADVANCED = hex"088df090";
bytes4 constant SELECTOR_SWEEP_COLLECTION_ADVANCED = hex"b9d8d5a6";

/**************************************************************/
/*                   EXPECTED BASE msg.data LENGTHS           */
/**************************************************************/

uint256 constant PROOF_ELEMENT_SIZE = 32;

// | 4        | 544         | 96              | 192         | 64       | = 900 bytes
// | selector | saleDetails | sellerSignature | cosignature | feeOnTop |
uint256 constant BASE_MSG_LENGTH_BUY_LISTING = 900;

// | 4        | 32                     | 544         |  96             | 32 + (64 + (32 * proof.length)) | 192         | 64       | = 1028 bytes + (32 * proof.length)
// | selector | offerType              | saleDetails |  buyerSignature | tokenSetProof                   | cosignature | feeOnTop |
uint256 constant BASE_MSG_LENGTH_ACCEPT_OFFER = 1028;

// | 4        | 64              | 544 * length      | 64              | 96 * length      | 64              | 192 * length | 64              | 64 * length | = 260 bytes + (896 * saleDetailsArray.length)
// | selector | length + offset | saleDetailsArray  | length + offset | sellerSignatures | length + offset | cosignatures | length + offset | feesOnTop   |
uint256 constant BASE_MSG_LENGTH_BULK_BUY_LISTINGS = 260;
uint256 constant BASE_MSG_LENGTH_BULK_BUY_LISTINGS_PER_ITEM = 896;

// | 4        | 64               | 32 * length      | 32 * length            | 544 * length      | 96 * length  | 192 * length | 64               | 64               | 64 * length | 64               | 32 + (64 + (32 * proof.length)) | = 228 bytes + (1056 * saleDetailsArray.length) + (32 * proof.length [for each element])
// | selector | length + offsets | offsets          | offerType              | saleDetails       | signature    | cosignature  | length + offset  | length + offsets | feesOnTop   | length + offsets | tokenSetProof                   |
uint256 constant BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS = 196;
uint256 constant BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS_PER_ITEM = 1024;

// | 4        | 64       | 128        | 64              | 352 * length | 64              | 96 * length      | 64              | 192 * length | = 388 bytes + (640 * items.length)
// | selector | feeOnTop | sweepOrder | length + offset | items        | length + offset | signedSellOrders | length + offset | cosignatures |
uint256 constant BASE_MSG_LENGTH_SWEEP_COLLECTION = 388;
uint256 constant BASE_MSG_LENGTH_SWEEP_COLLECTION_PER_ITEM = 640;

// Advanced Trade Functions

// | 4        | 544         | 96        | 192         | 64       | 64            | 32 + (96 + (32 * proof.length)) | = 1092 bytes + (32 * proof.length)
// | selector | saleDetails | signature | cosignature | feeOnTop | permitContext | bulkOrderProof                  |
uint256 constant BASE_MSG_LENGTH_BUY_LISTING_ADVANCED = 1092;

// | 4        | 32                     | 544         |  96        | 192         | 64       | 64            | 32 + (96 + (32 * proof.length)) | 32 + (64 + (32 * proof.length)) | 96                    | = 1316 bytes + (32 * tokenSetProof.length) + (32 * bulkOrderProof.length)
// | selector | offerType              | saleDetails |  signature | cosignature | feeOnTop | permitContext | bulkOrderProof                  | tokenSetProof                   | sellerPermitSignature |
uint256 constant BASE_MSG_LENGTH_ACCEPT_OFFER_ADVANCED = 1316;

// | 4        | 64              | 544 * length      | 96 * length  | 192 * length | 64 * length   | 32 + (96 + (32 * proof.length)) | 64              | 64 * length | = 132 bytes + (1088 * advancedListingsArray.length) + (32 * bulkOrderProof.proof.length [for each element])
// | selector | length + offset | saleDetails       | signature    | cosignature  | permitContext | bulkOrderProof                  | length + offset | feeOnTop    |
uint256 constant BASE_MSG_LENGTH_BULK_BUY_LISTINGS_ADVANCED = 196;
uint256 constant BASE_MSG_LENGTH_BULK_BUY_LISTINGS_ADVANCED_PER_ITEM = 1088;

// | 4        | 64              | 32 * length            | 544 * length | 96 * length | 192 * length | 64 * length   | 96 * length           | 64              | 32 + (96 + (32 * proof.length)) | 64              | 64 * length | 64              | 32 + (64 + (32 * proof.length)) | = 260 bytes + (1312 * saleDetailsArray.length) + (32 * proof.length [for each element])
// | selector | length + offset | offerType              | saleDetails  | signature   | cosignatures | permitContext | sellerPermitSignature | length + offset | bulkOrderProof                  | length + offset | feeOnTop    | length + offset | tokenSetProof                   |
uint256 constant BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS_ADVANCED = 260;
uint256 constant BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS_ADVANCED_PER_ITEM = 1312;

// | 4        | 64       | 128        | 64              | 352 * length | 96 * length | 192 * length | 32 + (96 + (32 * proof.length)) | = 292 bytes + (864 * items.length) + (32 * bulkOrderProof.proof.length [for each element])
// | selector | feeOnTop | sweepOrder | length + offset | items        | signature   | cosignatures | bulkOrderProof                  |
uint256 constant BASE_MSG_LENGTH_SWEEP_COLLECTION_ADVANCED = 292;
uint256 constant BASE_MSG_LENGTH_SWEEP_COLLECTION_ADVANCED_PER_ITEM = 864;

/**************************************************************/
/*                           FLAGS                            */
/**************************************************************/

// Flags are used to efficiently store and retrieve boolean values in a single uint8.
// Each flag is a power of 2, so they can be combined using bitwise OR (|) and checked using bitwise AND (&).
// For example, to set the first and third flags, you would use: flags = FLAG1 | FLAG3;
// To check if the first flag is set, you would use: if (flags & FLAG1 != 0) { ... }
uint8 constant FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE = 1 << 0;
uint8 constant FLAG_BLOCK_TRADES_FROM_UNTRUSTED_CHANNELS = 1 << 1;
uint8 constant FLAG_USE_BACKFILL_AS_ROYALTY_SOURCE = 1 << 2;

/**************************************************************/
/*                   BULK ORDER TYPEHASHES                    */
/**************************************************************/

/// @dev Constant lookup tables for bulk order typehashes. The typehash offset is 32 * (height - 1) bytes.
bytes constant BULK_SALE_APPROVAL_TYPEHASHES_LOOKUP = 
    hex"8659f55d4c88f84030fe09241169834754ebf2e61099b616e530c6b65712c6445106f4043cda72e04ca2c760016628ea8d5667309323ba590682ea5254b4e82e71267a8f42e7ccde4ac75848a93b741df7a2f7a58e40274055f2fa51dbaa69ce6e90557aed2a349f8b6efb2b652c4025c56cfa82424c5cd0c6801cc234ebf519caf42fc49ad57705ea6be2ca9e1835c9ccddf7bc6fdea8f851917329b1d53a7f09b32b6b5ffee898d5fe18398652042cc8c46449329c5a1a79e425f4ede9bc2bc2362dea3338b88607906397acfdcc5651f19b40a038ee7c89dc4ab54c736cacf2bbc4f6d7ccfa2b8aab6505f0c8f2868c5a190c6c4cfce8c0de447804904df981f99e15003039550d3597e34e6426b1e230992a5a3727cb5e230754f18f3566012178d0a6b8f8748e48411572bdc391435539737aafc91fcba6f2c24ae156cf";

bytes constant BULK_COLLECTION_OFFER_APPROVAL_TYPEHASHES_LOOKUP = 
    hex"fb1dd652f3a5dcde3cb90b0cbb7ff0d33df15a48f216e6ac0e2306ff3538e33632fc12675167bd2e9c23f1a9b90ce9c0a14daa7e8b3fa5a01105804ad64aa49d22beb101a4a232e5b05a7b5f4727888724dd418194d6c5b1f1ba5ead4cbeed429ba46e6a390d8f8edfb6f752177c215b1cf8b6dcf1f5da395ead6c3e19755698ae93fe4a03206f6bb45035fab04a39964c83e7d265e93fc31521c86dd25a3be4ee3cb9e25b214af21b0d321776864470916324653397b355b267b2894a009a98516d5d5306f95a17cb6d0b84e71f78919a98ac5e3c723fc4b9b1a720193d590df5b1dcad1bd5c8161d6b904513bc658d4c9b8c288b2271e0f27d5938654e418663c21e3ce7e506548c01ef4e6a27a5805f9328e0da83e9a7c765dc28266a5aa19802b2e35d72255f2ae69bed829f0104723aa156059e02dfccfe781ffc955356";

bytes constant BULK_ITEM_OFFER_APPROVAL_TYPEHASHES_LOOKUP =
    hex"243dc86e63d73b10b2764253a38ab8763ded4ac63a632adef4019e482161964a2ed4880b2bfec74882da06ca9f461158c42fa73fa5974b5abec9fcde7cdd43ee8308607f65c90c023abf48563d2c8bd8c783d1db48ffd02ec324105d9f682f961f7a30dcec9205ff30a5290b3b756e1fd1a1585539bccbc817ab4e59d69a055ecfa8c0abdff5fb2704d1818f38dc974f5bdde6dfe1a4ddc3e22a1f3e593e98f324e3124a40922800d1eb1890240fc7d07f257a303bb383e8c0820e9910be1f97c72769ff919066b8d1f586bb8b7e720ad93f735ffa9570f9e9ef0e865d13251d89338818bfd140179260dbdc6d6bf38461d49661c166f2b76d3b56727acab7adf3e381ed4335a28fa612f4068c58de673cad05c8a7fbfb5c1fd557c2c5a2de9f84c94a0cfd84379f92ed5827593e2218a1dc1b975626bbe5c6948b9d2232f7d7";

bytes constant BULK_TOKEN_SET_OFFER_APPROVAL_TYPEHASHES_LOOKUP = 
    hex"0d0b16c0b281480a518e0ae1543bf94c612edc289e45edc04e8510dac617afa4337b9c051f65323200a3580d4917087d4d82e350fcc68b3397c654c9fc7872c38f8f306a409d08a43b82756f57beb839c3bf5bdd388ba0850d9d21f5c1d0c4dc9f510b8ccf7bbda5c9bd4333d7dcfc8e280aac9eaa9427dcd2f2ffca1fa7a8338599a8fc89aae892d7419812435866d931c9da91198b2f48d1d5318acb524461a0f24864e615845dfa14bee5d4fa28950fcdcf9d31893c68c26f4cfa13402a97621dd1d4cc7477f0587a16f5cd40ea8750209653e7275ccaea3b68623860181b51e570aee92287102d49a7493d337e8aa332c1bc639273de546b428973379fa9a9415346b8de2006517a8de68b2b4147a1d6fd91ce600f4040941627c1c8c3dfb75be8e10d720cf92518936f56974bdfa8e4a723902dd12e415ffba6eb6dde12";

/**************************************************************/
/*                           ROLES                            */
/**************************************************************/

bytes32 constant FEE_MANAGER = keccak256("FEE_MANAGER");

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "@limitbreak/tm-core-lib/src/utils/structs/EnumerableSet.sol";

import { 
    OrderFillAmounts
} from "@limitbreak/permit-c/DataTypes.sol";

/**
 * @dev Used internally to indicate which side of the order the taker is on.
 */
enum Sides { 
    // 0: Taker is on buy side of order.
    Buy, 

    // 1: Taker is on sell side of order.
    Sell 
}

/**
 * @dev Defines the rules applied to a collection for payments.
 */
enum PaymentSettings { 
    // 0: Utilize Payment Processor default whitelist.
    DefaultPaymentMethodWhitelist,

    // 1: Allow any payment method.
    AllowAnyPaymentMethod,

    // 2: Use a custom payment method whitelist.
    CustomPaymentMethodWhitelist,

    // 3: Single payment method with floor and ceiling limits on collections only.
    PricingConstraintsCollectionOnly,

    // 4: Single payment method with floor and ceiling limits, allows both token and collection level constraints.
    PricingConstraints,

    // 5: Pauses trading for the collection.
    Paused
}

/**
 * @dev This struct is used internally for the deployment of the Payment Processor contract and 
 * @dev module deployments to define the default payment method whitelist.
 */
struct DefaultPaymentMethods {
    address defaultPaymentMethod1;
    address defaultPaymentMethod2;
    address defaultPaymentMethod3;
    address defaultPaymentMethod4;
}

/**
 * @dev This struct is used internally for the deployment of the Payment Processor contract and 
 * @dev module deployments to define the default trusted permit processors.
 */
struct TrustedPermitProcessors {
    address permitProcessor1;
    address permitProcessor2;
}

/**
 * @dev This struct is used internally for the deployment of the Payment Processor contract to define the
 * @dev module addresses to be used for the contract.
 */
struct PaymentProcessorModules {
    address moduleOnChainCancellation;
    address moduleBuyListings;
    address moduleAcceptOffers;
    address moduleSweeps;
}

/**
 * @dev This struct defines the payment settings parameters for a collection.
 *
 * @dev **paymentSettings**: The general rule definition for payment methods allowed.
 * @dev **paymentMethodWhitelistId**: The list id to be used when paymentSettings is set to CustomPaymentMethodWhitelist.
 * @dev **royaltyBackfillReceiver**: The backfilled royalty receiver for a collection.
 * @dev **royaltyBackfillNumerator**: The royalty fee to apply to the collection when ERC2981 is not supported.
 * @dev **royaltyBountyNumerator**: The percentage of royalties the creator will grant to a marketplace for order fulfillment.
 * @dev **isRoyaltyBountyExclusive**: If true, royalty bounties will only be paid if the order marketplace is the set exclusive marketplace.
 * @dev **blockTradesFromUntrustedChannels**: If true, trades that originate from untrusted channels will not be executed.
 */
struct CollectionPaymentSettings {
    bool initialized;
    PaymentSettings paymentSettings;
    uint32 paymentMethodWhitelistId;
    address royaltyBackfillReceiver;
    uint16 royaltyBackfillNumerator;
    uint16 royaltyBountyNumerator;
    uint8 flags;
}

/**
 * @dev The `v`, `r`, and `s` components of an ECDSA signature.  For more information
 *      [refer to this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7).
 */
struct SignatureECDSA {
    uint256 v;
    bytes32 r;
    bytes32 s;
}

/**
 * @dev This struct defines order execution parameters.
 * 
 * @dev **protocol**: The order protocol to apply to the order.
 * @dev **maker**: The user that created and signed the order to be executed by a taker.
 * @dev **beneficiary**: The account that will receive the tokens.
 * @dev **marketplace**: The fee receiver of the marketplace that the order was created on.
 * @dev **fallbackRoyaltyRecipient**: The address that will receive royalties if ERC2981 
 * @dev is not supported by the collection and the creator has not defined backfilled royalties with Payment Processor.
 * @dev **paymentMethod**: The payment method for the order.
 * @dev **tokenAddress**: The address of the token collection the order is for.
 * @dev **tokenId**: The token id that the order is for.
 * @dev **amount**: The quantity of token the order is for.
 * @dev **itemPrice**: The price for the order in base units for the payment method.
 * @dev **nonce**: The maker's nonce for the order.
 * @dev **expiration**: The time, in seconds since the Unix epoch, that the order will expire.
 * @dev **marketplaceFeeNumerator**: The percentage fee that will be sent to the marketplace.
 * @dev **maxRoyaltyFeeNumerator**: The maximum royalty the maker is willing to accept. This will be used
 * @dev as the royalty amount when ERC2981 is not supported by the collection.
 * @dev **requestedFillAmount**: The amount of tokens for an ERC1155 partial fill order that the taker wants to fill.
 * @dev **minimumFillAmount**: The minimum amount of tokens for an ERC1155 partial fill order that the taker will accept.
 * @dev **protocolFeeVersion**: The protocol fee version to use when applying protocol fees.
 */
struct Order {
    uint256 protocol;
    address maker;
    address beneficiary;
    address marketplace;
    address fallbackRoyaltyRecipient;
    address paymentMethod;
    address tokenAddress;
    uint256 tokenId;
    uint256 amount;
    uint256 itemPrice;
    uint256 nonce;
    uint256 expiration;
    uint256 marketplaceFeeNumerator;
    uint256 maxRoyaltyFeeNumerator;
    uint256 requestedFillAmount;
    uint256 minimumFillAmount;
    uint256 protocolFeeVersion;
}

/**
 * @dev This struct defines the items required to execute an advanced order.
 *
 * @dev **saleDetails**: The order execution parameters.
 * @dev **signature**: The signature of the maker authorizing the order execution.
 * @dev **cosignature**: The cosignature of the maker authorizing the order execution.
 * @dev **permitContext**: Contains the address of the permit processor and the permit nonce to be used.
 */
struct AdvancedOrder {
    Order saleDetails;
    SignatureECDSA signature;
    Cosignature cosignature;
    PermitContext permitContext;
}

/**
 * @dev This struct defines the items required to execute an advanced bid order.
 * 
 * @dev **offerType**: The type of offer to execute.
 * @dev **advancedOrder**: The order execution parameters.
 * @dev **sellerPermitSignature**: The signature of the seller to be used for the permit.
 */
struct AdvancedBidOrder {
    uint256 offerType;
    AdvancedOrder advancedOrder;
    SignatureECDSA sellerPermitSignature;
}

/**
 * @dev This struct defines the items required to execute an advanced sweep order.
 *
 * @dev **feeOnTop**: The additional fee to be paid by the taker.
 * @dev **sweepOrder**: The order execution parameters.
 * @dev **items**: An array of items to be executed as part of the sweep order.
 */
struct AdvancedSweep {
    FeeOnTop feeOnTop;
    SweepOrder sweepOrder;
    AdvancedSweepItem[] items;
}

/**
 * @dev This struct is a wrapper for a sweep order item that includes the permit context, bulk order information, signature and cosignature.
 *
 * @dev **sweepItem**: The sweep order item to be executed.
 * @dev **signature**: The signature of the maker authorizing the order execution.
 * @dev **cosignature**: The cosignature of the maker authorizing the order execution.
 * @dev **permitContext**: Contains the address of the permit processor and the permit nonce to be used.
 * @dev **bulkOrderProof**: The proof data for the bulk order.
 */
struct AdvancedSweepItem {
    SweepItem sweepItem;
    SignatureECDSA signature;
    Cosignature cosignature;
    PermitContext permitContext;
    BulkOrderProof bulkOrderProof;
}

/**
 * @dev This struct defines the cosignature for verifying an order that is a cosigned order.
 *
 * @dev **signer**: The address that signed the cosigned order. This must match the cosigner that is part of the order signature.
 * @dev **taker**: The address of the order taker.
 * @dev **expiration**: The time, in seconds since the Unix epoch, that the cosignature will expire.
 * @dev The `v`, `r`, and `s` components of an ECDSA signature.  For more information
 *      [refer to this article](https://medium.com/mycrypto/the-magic-of-digital-signatures-on-ethereum-98fe184dc9c7).
 */
struct Cosignature {
    address signer;
    address taker;
    uint256 expiration;
    uint256 v;
    bytes32 r;
    bytes32 s;
}

/**
 * @dev This struct defines an additional fee on top of an order, paid by taker.
 *
 * @dev **recipient**: The recipient of the additional fee.
 * @dev **amount**: The amount of the additional fee, in base units of the payment token.
 */
struct FeeOnTop {
    address recipient;
    uint256 amount;
}

/**
 * @dev This struct defines the proof data for accepting an offer that is for a subset
 * @dev of items in a collection. The computed root hash from the proof must match the root
 * @dev hash that was signed by the order maker to recover the maker address.
 * 
 * @dev **proof**: The merkle proofs for the item being supplied to fulfill the offer order.
 */
struct TokenSetProof {
    bytes32[] proof;
}

/**
 * @dev This struct defines the proof data for a bulk order.
 * 
 * @dev **orderIndex**: The index of the order in the bulk order.
 * @dev **proof**: The merkle proofs for the order being supplied to fulfill the bulk order.
 */
struct BulkOrderProof {
    uint256 orderIndex;
    bytes32[] proof;
}

/**
 * @dev Current state of a partially fillable order.
 */
enum PartiallyFillableOrderState { 
    // 0: Order is open and may continue to be filled.
    Open, 

    // 1: Order has been completely filled.
    Filled, 

    // 2: Order has been cancelled.
    Cancelled
}

/**
 * @dev This struct defines the current status of a partially fillable order.
 * 
 * @dev **state**: The current state of the order as defined by the PartiallyFillableOrderState enum.
 * @dev **remainingFillableQuantity**: The remaining quantity that may be filled for the order.
 */
struct PartiallyFillableOrderStatus {
    PartiallyFillableOrderState state;
    uint248 remainingFillableQuantity;
}

/**
 * @dev This struct defines order information that is common to all items in a sweep order.
 * 
 * @dev **protocol**: The order protocol to apply to the order.
 * @dev **tokenAddress**: The address of the token collection the order is for.
 * @dev **paymentMethod**: The payment method for the order.
 * @dev **beneficiary**: The account that will receive the tokens.
 */
struct SweepOrder {
    uint256 protocol;
    address tokenAddress;
    address paymentMethod;
    address beneficiary;
}

/**
 * @dev This struct defines order information that is unique to each item of a sweep order.
 * @dev Combined with the SweepOrder header information to make an Order to execute.
 * 
 * @dev **maker**: The user that created and signed the order to be executed by a taker.
 * @dev **marketplace**: The marketplace that the order was created on.
 * @dev **fallbackRoyaltyRecipient**: The address that will receive royalties if ERC2981 
 * @dev is not supported by the collection and the creator has not defined royalties with Payment Processor.
 * @dev **tokenId**: The token id that the order is for.
 * @dev **amount**: The quantity of token the order is for.
 * @dev **itemPrice**: The price for the order in base units for the payment method.
 * @dev **nonce**: The maker's nonce for the order.
 * @dev **expiration**: The time, in seconds since the Unix epoch, that the order will expire.
 * @dev **marketplaceFeeNumerator**: The percentage fee that will be sent to the marketplace.
 * @dev **maxRoyaltyFeeNumerator**: The maximum royalty the maker is willing to accept. This will be used
 * @dev as the royalty amount when ERC2981 is not supported by the collection.
 * @dev **protocolFeeVersion**: The protocol fee version to use when applying protocol fees.
 */
struct SweepItem {
    address maker;
    address marketplace;
    address fallbackRoyaltyRecipient;
    uint256 tokenId;
    uint256 amount;
    uint256 itemPrice;
    uint256 nonce;
    uint256 expiration;
    uint256 marketplaceFeeNumerator;
    uint256 maxRoyaltyFeeNumerator;
    uint256 protocolFeeVersion;
}

/**
 * @dev This struct is used to define pricing constraints for a collection or individual token.
 *
 * @dev **isSet**: When true, this indicates that pricing constraints are set for the collection or token.
 * @dev **floorPrice**: The minimum price for a token or collection.  This is only enforced when 
 * @dev `enforcePricingConstraints` is `true`.
 * @dev **ceilingPrice**: The maximum price for a token or collection.  This is only enforced when
 * @dev `enforcePricingConstraints` is `true`.
 */
struct PricingBounds {
    bool initialized;
    bool isSet;
    uint120 floorPrice;
    uint120 ceilingPrice;
}

/**
 * @dev This struct defines the parameters for a bulk offer acceptance transaction.
 * 
 * 
 * @dev **offerType**: Offer type to execute.
 * @dev **saleDetails**: Order execution details.
 * @dev **signature**: Maker signature authorizing the order executions.
 * @dev **cosignature**: Additional cosignature for cosigned orders, as applicable.
 */
struct BulkAcceptOffersParams {
    uint256 offerType;
    Order saleDetails;
    SignatureECDSA signature;
    Cosignature cosignature;
}

/**
 * @dev This struct defines the parameters for a sweep execution transaction.
 *
 * @dev **fnPointers**: The function pointers to use for the sweep execution.
 * @dev **accumulator**: The accumulator to use for the sweep execution.
 * @dev **paymentCoin**: The payment token to use for the sweep execution.
 * @dev **paymentSettings**: The payment settings to use for the sweep execution.
 */
struct SweepExecutionParams {
    FulfillOrderFunctionPointers fnPointers;
    PayoutsAccumulator accumulator;
    address paymentCoin;
    PaymentSettings paymentSettings;
}

/** 
 * @dev This struct defines the parameters for the proceeds to be split between the seller, marketplace, and royalty recipient.
 *
 * @dev **royaltyRecipient**: The address of the royalty recipient.
 * @dev **royaltyProceeds**: The amount of proceeds to be sent to the royalty recipient.
 * @dev **marketplaceProceeds**: The amount of proceeds to be sent to the marketplace.
 * @dev **sellerProceeds**: The amount of proceeds to be sent to the seller.
 * @dev **infrastructureProceeds**: The amount of proceeds to be sent to the infrastructure recipient.
 */
struct SplitProceeds {
    address royaltyRecipient;
    uint256 royaltyProceeds;
    uint256 marketplaceProceeds;
    uint256 sellerProceeds;
    uint256 infrastructureProceeds;
}

/** 
 * @dev This struct defines the parameters to be used to accumulate payouts for an order.
 *
 * @dev **lastSeller**: The address of the last seller to receive proceeds.
 * @dev **lastMarketplace**: The address of the last marketplace to receive proceeds.
 * @dev **lastRoyaltyRecipient**: The address of the last royalty recipient to receive proceeds.
 * @dev **lastProtocolFeeRecipient**: The address of the last protocol fee recipient to receive proceeds.
 * @dev **accumulatedSellerProceeds**: The total amount of proceeds accumulated for the seller.
 * @dev **accumulatedMarketplaceProceeds**: The total amount of proceeds accumulated for the marketplace.
 * @dev **accumulatedRoyaltyProceeds**: The total amount of proceeds accumulated for the royalty recipient.
 * @dev **accumulatedInfrastructureProceeds**: The total amount of proceeds accumulated for the infrastructure recipient.
 */
struct PayoutsAccumulator {
    address lastSeller;
    address lastMarketplace;
    address lastRoyaltyRecipient;
    address lastProtocolFeeRecipient;
    uint256 accumulatedSellerProceeds;
    uint256 accumulatedMarketplaceProceeds;
    uint256 accumulatedRoyaltyProceeds;
    uint256 accumulatedInfrastructureProceeds;
}

/** 
 * @dev This struct defines the function pointers to be used for the fulfillment of an order.
 *
 * @dev **funcPayout**: The function pointer to use for the payout.
 * @dev **funcDispenseToken**: The function pointer to use for the token dispensation.
 * @dev **funcEmitOrderExecutionEvent**: The function pointer to use for emitting the order execution event.
 */
 struct FulfillOrderFunctionPointers {
    function(address,address,address,uint256) funcPayout;
    function(address,address,address,uint256,uint256) returns (bool) funcDispenseToken;
    function(TradeContext memory, Order memory) funcEmitOrderExecutionEvent;
 }

 /** 
 * @dev This struct defines the context to be used for a trade execution.
 *
 * @dev **channel**: The address of the channel to use for the trade.
 * @dev **taker**: The address of the taker for the trade.
 * @dev **disablePartialFill**: A flag to indicate if partial fills are disabled.
 * @dev **orderDigest**: The digest of the order to be executed.
 * @dev **backfillNumerator**: The numerator to use for the backfill.
 * @dev **backfillReceiver**: The address of the backfill receiver.
 * @dev **bountyNumerator**: The numerator to use for the bounty.
 * @dev **exclusiveMarketplace**: The address of the exclusive marketplace.
 * @dev **useRoyaltyBackfillAsRoyaltySource**: A flag to indicate if the royalty backfill should be used as the royalty source.
 * @dev **protocolFeeVersion**: The protocol fee version to use when applying protocol fees.
 * @dev **protocolFees**: Struct containing the loaded protocol fees for the current context.
 */
 struct TradeContext {
    address channel;
    address taker;
    bool disablePartialFill;
    bytes32 orderDigest;
    uint16 backfillNumerator;
    address backfillReceiver;
    uint16 bountyNumerator;
    address exclusiveMarketplace;
    bool useRoyaltyBackfillAsRoyaltySource;
    uint256 protocolFeeVersion;
    ProtocolFees protocolFees;
 }

/** 
 * @dev This struct defines the items required to handle a permitted order.
 *
 * @dev **permitProcessor**: The address of the permit processor to use for the order.
 * @dev **permitNonce**: The nonce to use for the permit.
 */
 struct PermitContext {
    address permitProcessor;
    uint256 permitNonce;
 }

/** 
 * @dev This struct defines the protocol fees to be applied to a trade based on the protocol fee version.
 *
 * @dev **protocolFeeReceiver**: The address to receive protocol fees.
 * @dev **minimumProtocolFeeBps**: The minimum fee in BPS that is applied to a trade.
 * @dev **marketplaceFeeProtocolTaxBps**: The fee in BPS that is applied to marketplace fees.
 * @dev **feeOnTopProtocolTaxBps**: The fee in BPS that is applied to a fee on top.
 * @dev **versionExpiration**: The timestamp when the protocol fee version expires.
 */
 struct ProtocolFees {
    address protocolFeeReceiver;
    uint16 minimumProtocolFeeBps;
    uint16 marketplaceFeeProtocolTaxBps;
    uint16 feeOnTopProtocolTaxBps;
    uint48 versionExpiration;
 }

/**
 * @dev This struct defines contract-level storage to be used across all Payment Processor modules.
 * @dev Follows the Diamond storage pattern.
 */
struct PaymentProcessorStorage {
    /**
     * @notice User-specific master nonce that allows buyers and sellers to efficiently cancel all listings or offers
     *         they made previously. The master nonce for a user only changes when they explicitly request to revoke all
     *         existing listings and offers.
     *
     * @dev    When prompting sellers to sign a listing or offer, marketplaces must query the current master nonce of
     *         the user and include it in the listing/offer signature data.
     */
    mapping(address => uint256) masterNonces;

    /**
     * @dev The mapping key is the user address.
     *
     * @dev The mapping value is another nested mapping of "slot" (key) to a bitmap (value) containing boolean flags
     *      indicating whether or not a nonce has been used or invalidated.
     *
     * @dev Marketplaces MUST track their own nonce by user, incrementing it for every signed listing or offer the user
     *      creates.  Listings and purchases may be executed out of order, and they may never be executed if orders
     *      are not matched prior to expriation.
     *
     * @dev The slot and the bit offset within the mapped value are computed as:
     *
     * @dev ```slot = nonce / 256;```
     * @dev ```offset = nonce % 256;```
     */
    mapping(address => mapping(uint256 => uint256)) invalidatedSignatures;
    
    /// @dev Mapping of token contract addresses to the collection payment settings.
    mapping (address => CollectionPaymentSettings) collectionPaymentSettings;

    /// @dev Mapping of payment method whitelist id to a defined list of allowed payment methods.
    mapping (uint32 => EnumerableSet.AddressSet) collectionPaymentMethodWhitelists;

    /// @dev Mapping of token contract addresses to the collection-level pricing boundaries (floor and ceiling price).
    mapping (address => PricingBounds) collectionPricingBounds;

    /// @dev Mapping of token contract addresses to the token-level pricing boundaries (floor and ceiling price).
    mapping (address => mapping (uint256 => PricingBounds)) tokenPricingBounds;

    /// @dev Mapping of token contract addresses to the defined pricing constaint payment method.
    mapping (address => address) collectionConstrainedPricingPaymentMethods;

    /// @dev Mapping of token contract addresses to the defined exclusive bounty receivers.
    mapping (address => address) collectionExclusiveBountyReceivers;

    /// @dev Mapping of maker addresses to a mapping of order digests to the status of the partially fillable order for that digest.
    mapping (address => mapping(bytes32 => PartiallyFillableOrderStatus)) partiallyFillableOrderStatuses;

    /// @dev Mapping of token contract addresses to the defined list of trusted channels for the token contract.
    mapping (address => EnumerableSet.AddressSet) collectionTrustedChannels;

    /// @dev A mapping of all co-signers that have self-destructed and can never be used as cosigners again.
    mapping (address => bool) destroyedCosigners;

    /// @dev An enumerable set of trusted permit processors that may be used.
    EnumerableSet.AddressSet trustedPermitProcessors;

    /// @dev Protocol Fee Current Version.
    uint256 currentProtocolFeeVersion;

    /// @dev Protocol Fee Version History.
    mapping (uint256 => ProtocolFees) protocolFeeVersions;
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

/// @dev Thrown when an order is an ERC721 order and the amount is not one.
error PaymentProcessor__AmountForERC721SalesMustEqualOne();

/// @dev Thrown when an order is an ERC1155 order and the amount is zero.
error PaymentProcessor__AmountForERC1155SalesGreaterThanZero();

/// @dev Thrown when processing a permitted order and the order amount exceeds type(uint248).max
error PaymentProcessor__AmountExceedsMaximum();

/// @dev Thrown when appended data length is not zero or twenty bytes.
error PaymentProcessor__BadCalldataLength();

/// @dev Thrown when an offer is being accepted and the payment method is the chain native token.
error PaymentProcessor__BadPaymentMethod();

/**
 * @dev Thrown when a call is made to a function that must be called by the Collection Settings Registry 
 * @dev or by a self call from Payment Processor and the caller is not either of those addresses.
 */
error PaymentProcessor__CallerIsNotSettingsRegistryOrSelf();

/**
 * @dev Thrown when modifying collection payment settings, pricing bounds, or trusted channels on a collection
 * @dev that the caller is not the owner of or a member of the default admin role for.
 */
error PaymentProcessor__CallerMustHaveElevatedPermissionsForSpecifiedNFT();

/// @dev Thrown when the current block time is greater than the expiration time for the cosignature.
error PaymentProcessor__CosignatureHasExpired();

/// @dev Thrown when the cosigner has self destructed.
error PaymentProcessor__CosignerHasSelfDestructed();

/// @dev Thrown when a token failed to transfer to the beneficiary and partial fills are disabled.
error PaymentProcessor__DispensingTokenWasUnsuccessful();

/// @dev Thrown when a maker is a contract and the contract does not return the correct EIP1271 response to validate the signature.
error PaymentProcessor__EIP1271SignatureInvalid();

/// @dev Thrown when a native token transfer call fails to transfer the tokens.
error PaymentProcessor__FailedToTransferProceeds();

/// @dev Thrown when the additional fee on top exceeds the item price.
error PaymentProcessor__FeeOnTopCannotBeGreaterThanItemPrice();

/// @dev Thrown when the supplied root hash, token and proof do not match.
error PaymentProcessor__IncorrectTokenSetMerkleProof();

/// @dev Thrown when an input array has zero items in a location where it must have items.
error PaymentProcessor__InputArrayLengthCannotBeZero();

/// @dev Thrown when multiple input arrays have different lengths but are required to be the same length.
error PaymentProcessor__InputArrayLengthMismatch();

/// @dev Thrown when the height of the bulk order tree is either 0 or greater than 12.
error PaymentProcessor__InvalidBulkOrderHeight();

/// @dev Thrown when Payment Processor or a module is being deployed with invalid constructor arguments.
error PaymentProcessor__InvalidConstructorArguments();

/// @dev Thrown when an offer type parameter is not a valid offer type.
error PaymentProcessor__InvalidOfferType();

/// @dev Thrown when an order protocol parameter is not a valid order protocol type.
error PaymentProcessor__InvalidOrderProtocol();

/// @dev Thrown when a signature `v` value is greater than 255.
error PaymentProcessor__InvalidSignatureV();

/// @dev Thrown when the combined marketplace and royalty fees will exceed the item price.
error PaymentProcessor__MarketplaceAndRoyaltyFeesWillExceedSalePrice();

/// @dev Thrown when the version expiration grace period is exceeded.
error PaymentProcessor__MaxGracePeriodExceeded();

/// @dev Thrown when the recovered address from a cosignature does not match the order cosigner.
error PaymentProcessor__NotAuthorizedByCosigner();

/// @dev Thrown when the ERC2981 or backfilled royalties exceed the maximum fee specified by the order maker.
error PaymentProcessor__OnchainRoyaltiesExceedMaximumApprovedRoyaltyFee();

/// @dev Thrown when the current block timestamp is greater than the order expiration time.
error PaymentProcessor__OrderHasExpired();

/// @dev Thrown when attempting to fill a partially fillable order that has already been filled or cancelled.
error PaymentProcessor__OrderIsEitherCancelledOrFilled();

/// @dev Thrown when attempting to execute a sweep order for partially fillable orders.
error PaymentProcessor__OrderProtocolERC1155FillPartialUnsupportedInSweeps();

/// @dev Thrown when attempting to partially fill an order where the item price is not equally divisible by the amount of tokens.
error PaymentProcessor__PartialFillsNotSupportedForNonDivisibleItems();

/// @dev Thrown when attempting to execute an order with a payment method that is not allowed by the collection payment settings.
error PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();

/// @dev Thrown when both a permit and a bulk order proof are submitted for an order, as these features are incompatible.
error PaymentProcessor__PermitsAreNotCompatibleWithBulkOrders();

/// @dev Thrown when attempting to use an untrusted permit processing system.
error PaymentProcessor__PermitProcessorNotTrusted();

/// @dev Thrown when the protocol fee is set to a value exceeding the maximum fee cap.
error PaymentProcessor__ProtocolFeeOrTaxExceedsCap();

/// @dev Thrown when the protocol fee version is expired.
error PaymentProcessor__ProtocolFeeVersionExpired();

/// @dev Thrown when distributing payments and fees in native token and the amount remaining is less than the amount to distribute.
error PaymentProcessor__RanOutOfNativeFunds();

/// @dev Thrown when a collection is set to pricing constraints and the item price exceeds the defined maximum price.
error PaymentProcessor__SalePriceAboveMaximumCeiling();

/// @dev Thrown when a collection is set to pricing constraints and the item price is below the defined minimum price.
error PaymentProcessor__SalePriceBelowMinimumFloor();

/// @dev Thrown when a maker's nonce has already been used for an executed order or cancelled by the maker.
error PaymentProcessor__SignatureAlreadyUsedOrRevoked();

/// @dev Thrown when a maker's nonce has not already been used for an executed order but an item with that nonce fails to fill.
error PaymentProcessor__SignatureNotUsedOrRevoked();

/**
 * @dev Thrown when a collection is set to block untrusted channels and the order execution originates from a channel 
 * @dev that is not in the collection's trusted channel list.
 */ 
error PaymentProcessor__TradeOriginatedFromUntrustedChannel();

/// @dev Thrown when a trading of a specific collection has been paused by the collection owner or admin.
error PaymentProcessor__TradingIsPausedForCollection();

/**
 * @dev Thrown when attempting to fill a partially fillable order and the amount available to fill 
 * @dev is less than the specified minimum to fill.
 */
error PaymentProcessor__UnableToFillMinimumRequestedQuantity();

/// @dev Thrown when the recovered signer for an order does not match the order maker.
error PaymentProcessor__UnauthorizedOrder();

/// @dev Thrown when the taker on a cosigned order does not match the taker on the cosignature.
error PaymentProcessor__UnauthorizedTaker();

/// @dev Thrown when the Payment Processor or a module is being deployed with uninitialized configuration values.
error PaymentProcessor__UninitializedConfiguration();

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";
import "./DataTypes.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title Payment Processor
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

import "./modules/ModuleAcceptOffers.sol";
import "./modules/ModuleBuyListings.sol";
import "./modules/ModuleSweeps.sol";
import "./modules/ModuleOnChainCancellation.sol";

contract PaymentProcessorEncoder {

    /**************************************************************/
    /*              ON-CHAIN CANCELLATION OPERATIONS              */
    /**************************************************************/

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `destroyCosigner` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  cosigner   The address of the cosigner to destroy.
     * @param  signature  The signature of the cosigner to destroy.
     */
    function encodeDestroyCosignerCalldata(
        address /*paymentProcessorAddress*/, 
        address cosigner,
        SignatureECDSA memory signature
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleOnChainCancellation.destroyCosigner.selector,
                cosigner,
                signature));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `revokeSingleNonce` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  nonce The nonce that was signed in the revoked listing or offer.
     */
    function encodeRevokeSingleNonceCalldata(address /*paymentProcessorAddress*/, uint256 nonce) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleOnChainCancellation.revokeSingleNonce.selector,
                nonce));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `revokeOrderDigest` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  digest The order digest that was signed in the revoked listing or offer.
     */
    function encodeRevokeOrderDigestCalldata(address /*paymentProcessorAddress*/, bytes32 digest) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleOnChainCancellation.revokeOrderDigest.selector,
                digest));
    }

    /**************************************************************/
    /*                     TRADING OPERATIONS                     */
    /**************************************************************/

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `buyListing` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  saleDetails              The order execution details.
     * @param  signature                The maker's signature authorizing the order execution.
     * @param  cosignature              The additional cosignature for a cosigned order, if applicable.
     * @param  feeOnTop                 The additional fee to add on top of the order, paid by taker.
     */
    function encodeBuyListingCalldata(
        address /*paymentProcessorAddress*/, 
        Order memory saleDetails, 
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        FeeOnTop memory feeOnTop
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleBuyListings.buyListing.selector,
                saleDetails,
                signature,
                cosignature,
                feeOnTop));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `acceptOffer` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  offerType      The type of offer to execute.
     * @param  saleDetails    The order execution details.
     * @param  signature      The maker's signature authorizing the order execution.
     * @param  tokenSetProof  The root hash and merkle proofs for an offer that is a subset of tokens in a collection.
     * @param  cosignature    The additional cosignature for a cosigned order, if applicable.
     * @param  feeOnTop       The additional fee to add on top of the order, paid by taker.
     */
    function encodeAcceptOfferCalldata(
        address /*paymentProcessorAddress*/, 
        uint256 offerType,
        Order memory saleDetails, 
        SignatureECDSA memory signature,
        TokenSetProof memory tokenSetProof,
        Cosignature memory cosignature,
        FeeOnTop memory feeOnTop
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleAcceptOffers.acceptOffer.selector,
                offerType,
                saleDetails,
                signature,
                tokenSetProof,
                cosignature,
                feeOnTop));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `bulkBuyListings` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  saleDetailsArray         An array of order execution details.
     * @param  signatures               An array of maker signatures authorizing the order execution.
     * @param  cosignatures             An array of additional cosignatures for cosigned orders, if applicable.
     * @param  feesOnTop                An array of additional fees to add on top of the orders, paid by taker.
     */
    function encodeBulkBuyListingsCalldata(
        address /*paymentProcessorAddress*/, 
        Order[] calldata saleDetailsArray, 
        SignatureECDSA[] calldata signatures,
        Cosignature[] calldata cosignatures,
        FeeOnTop[] calldata feesOnTop
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleBuyListings.bulkBuyListings.selector,
                saleDetailsArray,
                signatures,
                cosignatures,
                feesOnTop));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `bulkAcceptOffers` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  offerTypeArray       An array of offer types to execute.
     * @param  saleDetailsArray     An array of order execution details.
     * @param  signatures           An array of maker signatures authorizing the order executions.
     * @param  tokenSetProofsArray  An array of root hashes and merkle proofs for offers that are a subset of tokens in a collection.
     * @param  cosignaturesArray    An array of additional cosignatures for cosigned orders, as applicable.
     * @param  feesOnTopArray       An array of additional fees to add on top of the orders, paid by taker.
     */
    function encodeBulkAcceptOffersCalldata(
        address /*paymentProcessorAddress*/, 
        uint256[] memory offerTypeArray,
        Order[] memory saleDetailsArray,
        SignatureECDSA[] memory signatures,
        TokenSetProof[] memory tokenSetProofsArray,
        Cosignature[] memory cosignaturesArray,
        FeeOnTop[] memory feesOnTopArray
    ) external view returns (bytes memory) {
        BulkAcceptOffersParams[] memory params = new BulkAcceptOffersParams[](offerTypeArray.length);
        
        for (uint256 i; i < offerTypeArray.length; ++i) {
            params[i].offerType = offerTypeArray[i];
            params[i].saleDetails = saleDetailsArray[i];
            params[i].signature = signatures[i];
            params[i].cosignature = cosignaturesArray[i];
        }

        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleAcceptOffers.bulkAcceptOffers.selector,
                params,
                feesOnTopArray,
                tokenSetProofsArray));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `sweepCollection` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  feeOnTop                 An array of additional fees to add on top of the orders, paid by taker.
     * @param  sweepOrder               The order information that is common to all items in the sweep.
     * @param  items                    An array of items that contains the order information unique to each item.
     * @param  signatures               An array of maker signatures authorizing the order execution.
     * @param  cosignatures             An array of additional cosignatures for cosigned orders, if applicable.
     */
    function encodeSweepCollectionCalldata(
        address /*paymentProcessorAddress*/, 
        FeeOnTop memory feeOnTop,
        SweepOrder memory sweepOrder,
        SweepItem[] calldata items,
        SignatureECDSA[] calldata signatures,
        Cosignature[] calldata cosignatures
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleSweeps.sweepCollection.selector,
                feeOnTop,
                sweepOrder,
                items,
                signatures,
                cosignatures));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `buyListingAdvanced` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  advancedListing  The advanced listing order details.
     * @param  bulkOrderProof   The merkle proofs and order index for the bulk order.
     * @param  feeOnTop         The additional fee to add on top of the order, paid by taker.
     */
    function encodeBuyListingAdvancedCalldata(
        address /*paymentProcessorAddress*/, 
        AdvancedOrder memory advancedListing,
        BulkOrderProof memory bulkOrderProof,
        FeeOnTop memory feeOnTop
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleBuyListings.buyListingAdvanced.selector,
                advancedListing,
                bulkOrderProof,
                feeOnTop));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `acceptOfferAdvanced` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  advancedBid     The advanced bid order details.
     * @param  bulkOrderProof  The merkle proofs and order index for the bulk order.
     * @param  feeOnTop        The additional fee to add on top of the order, paid by taker.
     * @param  tokenSetProof   The root hash and merkle proofs for an offer that is a subset of tokens in a collection.
     */
    function encodeAcceptOfferAdvancedCalldata(
        address /*paymentProcessorAddress*/, 
        AdvancedBidOrder memory advancedBid,
        BulkOrderProof memory bulkOrderProof,
        FeeOnTop memory feeOnTop,
        TokenSetProof memory tokenSetProof
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleAcceptOffers.acceptOfferAdvanced.selector,
                advancedBid,
                bulkOrderProof,
                feeOnTop,
                tokenSetProof));
    }
    /**
     * @notice Helper function to encode transaction calldata in the format required for a `bulkBuyListingsAdvanced` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  advancedListingsArray  An array of advanced listing order details.
     * @param  bulkOrderProofs        An array of merkle proofs and order indexes for the bulk orders.
     * @param  feesOnTop              An array of additional fees to add on top of the orders, paid by taker.
     */
    function encodeBulkBuyListingsAdvancedCalldata(
        address /*paymentProcessorAddress*/, 
        AdvancedOrder[] memory advancedListingsArray,
        BulkOrderProof[] memory bulkOrderProofs,
        FeeOnTop[] memory feesOnTop
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleBuyListings.bulkBuyListingsAdvanced.selector,
                advancedListingsArray,
                bulkOrderProofs,
                feesOnTop));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `bulkAcceptOffersAdvanced` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  advancedBidsArray  An array of advanced bid order details.
     * @param  bulkOrderProofs    An array of merkle proofs and order indexes for the bulk orders.
     * @param  feesOnTop          An array of additional fees to add on top of the orders, paid by taker.
     * @param  tokenSetProofs     An array of root hashes and merkle proofs for offers that are a subset of tokens in a collection.
     */
    function encodeBulkAcceptOffersAdvancedCalldata(
        address /*paymentProcessorAddress*/, 
        AdvancedBidOrder[] memory advancedBidsArray,
        BulkOrderProof[] memory bulkOrderProofs,
        FeeOnTop[] memory feesOnTop,
        TokenSetProof[] memory tokenSetProofs
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleAcceptOffers.bulkAcceptOffersAdvanced.selector,
                advancedBidsArray,
                bulkOrderProofs,
                feesOnTop,
                tokenSetProofs));
    }

    /**
     * @notice Helper function to encode transaction calldata in the format required for a `sweepCollectionAdvanced` call.
     *
     * @dev    Encoding parameters into a bytes array is performed for gas optimization in Payment Processor.
     * @dev    Payment Processor is separated into multiple module contracts due to contract size limitations.
     * @dev    Calls to the Payment Processor contract are passed along to the corresponding module through a delegate call.
     * @dev    *Note:* This encoding function should **not** be called on-chain as part of a transaction. It is meant to
     * @dev    be called off-chain to prepare the transaction data for a call to Payment Processor.
     *
     * @param  advancedSweep The advanced sweep order details.
     */
    function encodeSweepCollectionAdvancedCalldata(
        address /*paymentProcessorAddress*/, 
        AdvancedSweep memory advancedSweep
    ) external view returns (bytes memory) {
        return _removeFirst4Bytes(
            abi.encodeWithSelector(
                ModuleSweeps.sweepCollectionAdvanced.selector,
                advancedSweep));
    }

    /**************************************************************/
    /*                      HELPER FUNCTIONS                      */
    /**************************************************************/

    /**
    * @notice Helper function to remove the first 4 bytes of a bytes array.
    *
    * @dev    This function is used to remove the function selector from the provided calldata.
    * @dev    Removing the function selector is necessary to prepare the calldata for a delegate call to Payment Processor.
    */
    function _removeFirst4Bytes(bytes memory data) private view returns (bytes memory result) {
        assembly {
            if lt(mload(data), 0x04) {
                revert(0,0)
            }
            mstore(result, sub(mload(data), 0x04))
            if iszero(staticcall(gas(), 0x04, add(data, 0x24), mload(result), add(result, 0x20), mload(result))){
              revert(0,0)
            }
        }
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "../DataTypes.sol";

/** 
* @title IPaymentProcessorConfiguration
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 
interface IPaymentProcessorConfiguration {

    /**
     * @notice Returns the ERC2771 context setup params for payment processor modules.
     */
    function getPaymentProcessorModuleERC2771ContextParams() 
        external 
        view 
        returns (
            address /*trustedForwarderFactory*/
        );

    /**
     * @notice Returns the setup params for payment processor modules.
     */
    function getPaymentProcessorModuleDeploymentParams() 
        external 
        view 
        returns (
            address, /*deterministicPaymentProcessorAddress*/
            address, /*wrappedNativeCoin*/
            DefaultPaymentMethods memory /*defaultPaymentMethods*/,
            TrustedPermitProcessors memory /*trustedPermitProcessors*/,
            address /*collectionPaymentSettings*/,
            address /*infrastructureFeeReceiver*/
        );

    /**
     * @notice Returns the setup params for payment processor.
     */
    function getPaymentProcessorDeploymentParams()
        external
        view
        returns (
            address, /*defaultContractOwner*/
            PaymentProcessorModules memory /*paymentProcessorModules*/
        );
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "../DataTypes.sol";

/** 
* @title IPaymentProcessorEvents
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 
interface IPaymentProcessorEvents {

    /// @notice Emitted when an ERC721 listing is purchased.
    event BuyListingERC721(
        address indexed buyer,
        address indexed seller,
        address indexed tokenAddress,
        address beneficiary,
        address paymentCoin,
        uint256 tokenId,
        uint256 salePrice);

    /// @notice Emitted when an ERC1155 listing is purchased.
    event BuyListingERC1155(
        address indexed buyer,
        address indexed seller,
        address indexed tokenAddress,
        address beneficiary,
        address paymentCoin,
        uint256 tokenId,
        uint256 amount,
        uint256 salePrice);

    /// @notice Emitted when an ERC721 offer is accepted.
    event AcceptOfferERC721(
        address indexed seller,
        address indexed buyer,
        address indexed tokenAddress,
        address beneficiary,
        address paymentCoin,
        uint256 tokenId,
        uint256 salePrice);

    /// @notice Emitted when an ERC1155 offer is accepted.
    event AcceptOfferERC1155(
        address indexed seller,
        address indexed buyer,
        address indexed tokenAddress,
        address beneficiary,
        address paymentCoin,
        uint256 tokenId,
        uint256 amount,
        uint256 salePrice);

    /// @notice Emitted when a cosigner destroys itself.
    event DestroyedCosigner(address indexed cosigner);

    /// @notice Emitted when a user revokes all of their existing listings or offers that share the master nonce.
    event MasterNonceInvalidated(address indexed account, uint256 nonce);

    /// @notice Emitted when a user revokes a single listing or offer nonce for a specific marketplace.
    event NonceInvalidated(
        uint256 indexed nonce, 
        address indexed account, 
        bool wasCancellation);

    /// @notice Emitted when a user fills a single listing or offer nonce but item fails to fill.
    event NonceRestored(
        uint256 indexed nonce, 
        address indexed account);

    /// @notice Emitted when a user revokes a single permitted listing nonce.
    event PermittedOrderNonceInvalidated(
        uint256 indexed permitNonce, 
        uint256 indexed orderNonce, 
        address indexed account, 
        bool wasCancellation);

    /// @notice Emitted when protocol fees are updated.
    event ProtocolFeesUpdated(
        address indexed newProtocolFeeRecipient,
        uint16 minimumProtocolFeeBps,
        uint16 marketplaceFeeProtocolTaxBps,
        uint16 feeOnTopProtocolTaxBps,
        uint48 gracePeriodExpiration);

    /// @notice Emitted the first time a partially fillable 1155 order has items filled on-chain.
    event OrderDigestOpened(
        bytes32 indexed orderDigest, 
        address indexed account, 
        uint256 orderStartAmount);

    /// @notice Emitted when a user fills items on a partially fillable 1155 listing.
    event OrderDigestItemsFilled(
        bytes32 indexed orderDigest, 
        address indexed account, 
        uint256 amountFilled);

    /// @notice Emitted when a user revokes a single partially fillable 1155 listing or offer for a specific marketplace.
    event OrderDigestInvalidated(
        bytes32 indexed orderDigest, 
        address indexed account, 
        bool wasCancellation);

    /// @notice Emitted when a user fills a partially fillable 1155 listing or offer but item fails to fill.
    event OrderDigestItemsRestored(
        bytes32 indexed orderDigest, 
        address indexed account, 
        uint256 amountRestoredToOrder);

    /// @notice Emitted when a coin is added to the approved coins mapping for a whitelist.
    event PaymentMethodAddedToWhitelist(
        uint32 indexed paymentMethodWhitelistId, 
        address indexed paymentMethod);

    /// @notice Emitted when a coin is removed from the approved coins mapping for a whitelist.
    event PaymentMethodRemovedFromWhitelist(
        uint32 indexed paymentMethodWhitelistId, 
        address indexed paymentMethod);

    /// @notice Emitted when a trusted channel is added for a collection
    event TrustedChannelAddedForCollection(
        address indexed tokenAddress, 
        address indexed channel);

    /// @notice Emitted when a trusted channel is removed for a collection.
    event TrustedChannelRemovedForCollection(
        address indexed tokenAddress, 
        address indexed channel);

    /// @notice Emitted when a permit processor is added to the trusted permit processor list.
    event TrustedPermitProcessorAdded(address indexed permitProcessor);

    /// @notice Emitted when a permit processor is removed from the trusted permit processor list.
    event TrustedPermitProcessorRemoved(address indexed permitProcessor);

    /// @notice Emitted whenever pricing bounds change at a collection level for price-constrained collections.
    event UpdatedCollectionLevelPricingBoundaries(
        address indexed tokenAddress, 
        uint256 floorPrice, 
        uint256 ceilingPrice);

    /// @notice Emitted when payment settings are updated for a collection.
    event UpdatedCollectionPaymentSettings(
        address indexed tokenAddress, 
        PaymentSettings paymentSettings, 
        uint32 indexed paymentMethodWhitelistId, 
        address indexed constrainedPricingPaymentMethod,
        uint16 royaltyBackfillNumerator,
        address royaltyBackfillReceiver,
        uint16 royaltyBountyNumerator,
        address exclusiveBountyReceiver,
        bool blockTradesFromUntrustedChannels,
        bool useRoyaltyBackfillAsRoyaltySource);

    /// @notice Emitted whenever pricing bounds change at a token level for price-constrained collections.
    event UpdatedTokenLevelPricingBoundaries(
        address indexed tokenAddress, 
        uint256 indexed tokenId, 
        uint256 floorPrice, 
        uint256 ceilingPrice);
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./PaymentProcessorModuleAdvanced.sol";
import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title ModuleAcceptOffers
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

contract ModuleAcceptOffers is PaymentProcessorModuleAdvanced {

    constructor(address configurationContract) PaymentProcessorModuleAdvanced(configurationContract){}

    /**
     * @notice Executes an offer accept transaction for a single order item.
     *
     * @dev    Throws when the maker's nonce has already been used or has been cancelled.
     * @dev    Throws when the order has expired.
     * @dev    Throws when the combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when the taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when the maker's master nonce does not match the order details.
     * @dev    Throws when the order does not comply with the collection payment settings.
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the maker does not have sufficient funds to complete the purchase.
     * @dev    Throws when the token transfer fails for any reason such as lack of approvals or token not owned by the taker.
     * @dev    Throws when the token the offer is being accepted for does not match the conditions set by the maker.
     * @dev    Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. An `AcceptOfferERC721` event has been emitted for a ERC721 sale.
     * @dev    6. An `AcceptOfferERC1155` event has been emitted for a ERC1155 sale.
     * @dev    7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  offerType       The type of offer being executed.
     * @param  saleDetails     The order execution details.
     * @param  buyerSignature  The maker's signature authorizing the order execution.
     * @param  tokenSetProof   The merkle proofs for an offer that is a subset of tokens in a collection.
     * @param  cosignature     The additional cosignature for a cosigned order, if applicable.
     * @param  feeOnTop        The additional fee to add on top of the order, paid by taker.
     */
    function acceptOffer(
        uint256 offerType, 
        Order memory saleDetails, 
        SignatureECDSA memory buyerSignature,
        TokenSetProof calldata tokenSetProof,
        Cosignature memory cosignature,
        FeeOnTop calldata feeOnTop
    ) public {
        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_ACCEPT_OFFER - 
                (PROOF_ELEMENT_SIZE * tokenSetProof.proof.length);
        }

        _executeOrderSellSide(
            TradeContext({
                channel: msg.sender,
                taker: _getTaker(appendedDataLength),
                disablePartialFill: true,
                orderDigest: bytes32(0),
                backfillNumerator: uint16(0),
                backfillReceiver: address(0),
                bountyNumerator: uint16(0),
                exclusiveMarketplace: address(0),
                useRoyaltyBackfillAsRoyaltySource: false,
                protocolFeeVersion: saleDetails.protocolFeeVersion,
                protocolFees: appStorage().protocolFeeVersions[saleDetails.protocolFeeVersion]
            }),
            offerType, 
            saleDetails, 
            buyerSignature,
            tokenSetProof,
            cosignature,
            feeOnTop);
    }

    /**
     * @notice Executes an advanced offer accept transaction for a single order item.
     *
     * @dev    Throws when the maker's nonce has already been used or has been cancelled.
     * @dev    Throws when the order has expired.
     * @dev    Throws when the combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when the taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when the maker's master nonce does not match the order details.
     * @dev    Throws when the order does not comply with the collection payment settings.
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the maker does not have sufficient funds to complete the purchase.
     * @dev    Throws when the token transfer fails for any reason such as lack of approvals or token not owned by the taker.
     * @dev    Throws when the token the offer is being accepted for does not match the conditions set by the maker.
     * @dev    Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Throws when the provided bulk order merkle proof and order index are invalid.
     * @dev    Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. An `AcceptOfferERC721` event has been emitted for a ERC721 sale.
     * @dev    6. An `AcceptOfferERC1155` event has been emitted for a ERC1155 sale.
     * @dev    7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  advancedBid      The cosigner, maker, and taker signatures, order details and permit details.
     * @param  bulkOrderProof   The merkle proofs and order index for an offer that was created via signing a merkle tree.
     * @param  feeOnTop         The additional fee to add on top of the order, paid by taker.
     * @param  tokenSetProof    The merkle proofs for an offer that is a subset of tokens in a collection.
     */
    function acceptOfferAdvanced(
        AdvancedBidOrder memory advancedBid,
        BulkOrderProof calldata bulkOrderProof,
        FeeOnTop calldata feeOnTop,
        TokenSetProof calldata tokenSetProof
    ) public {
        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_ACCEPT_OFFER_ADVANCED - 
                (PROOF_ELEMENT_SIZE * tokenSetProof.proof.length) -
                (PROOF_ELEMENT_SIZE * bulkOrderProof.proof.length);
        }

        _executeOrderSellSideAdvanced(
            TradeContext({
                channel: msg.sender,
                taker: _getTaker(appendedDataLength),
                disablePartialFill: true,
                orderDigest: bytes32(0),
                backfillNumerator: uint16(0),
                backfillReceiver: address(0),
                bountyNumerator: uint16(0),
                exclusiveMarketplace: address(0),
                useRoyaltyBackfillAsRoyaltySource: false,
                protocolFeeVersion: advancedBid.advancedOrder.saleDetails.protocolFeeVersion,
                protocolFees: appStorage().protocolFeeVersions[advancedBid.advancedOrder.saleDetails.protocolFeeVersion]
            }),
            advancedBid,
            bulkOrderProof,
            feeOnTop,
            tokenSetProof);
    }

    /**
     * @notice Executes an accept offer transaction for multiple order items.
     *
     * @dev    Throws when a maker's nonce has already been used or has been cancelled.
     * @dev    Throws when any order has expired.
     * @dev    Throws when any combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when any taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when a maker's master nonce does not match the order details.
     * @dev    Throws when an order does not comply with the collection payment settings.
     * @dev    Throws when a maker's signature is invalid.
     * @dev    Throws when an order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when a maker does not have sufficient funds to complete the purchase.
     * @dev    Throws when the token an offer is being accepted for does not match the conditions set by the maker.
     * @dev    Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. `AcceptOfferERC721` events have been emitted for each ERC721 sale.
     * @dev    6. `AcceptOfferERC1155` events have been emitted for each ERC1155 sale.
     * @dev    7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for each ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  params          The parameters for the bulk offers being accepted.
     * @param  feesOnTop       The additional fees to add on top of the orders, paid by taker.
     * @param  tokenSetProofs  The merkle proofs for offers that are subsets of tokens in a collection.
     */
    function bulkAcceptOffers(
        BulkAcceptOffersParams[] memory params,
        FeeOnTop[] calldata feesOnTop,
        TokenSetProof[] calldata tokenSetProofs
    ) public {
        if (params.length != feesOnTop.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (feesOnTop.length != tokenSetProofs.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (params.length == 0) {
            revert PaymentProcessor__InputArrayLengthCannotBeZero();
        }

        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS - 
                (BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS_PER_ITEM * params.length);

            for (uint256 i = 0; i < tokenSetProofs.length;) {
                appendedDataLength -= PROOF_ELEMENT_SIZE * tokenSetProofs[i].proof.length;
                ++i;
            }
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: false,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: params[0].saleDetails.protocolFeeVersion,
            protocolFees: appStorage().protocolFeeVersions[params[0].saleDetails.protocolFeeVersion]
        });

        BulkAcceptOffersParams memory offerParams;
        for (uint256 i = 0; i < params.length;) {
            offerParams = params[i];

            if (context.protocolFeeVersion != offerParams.saleDetails.protocolFeeVersion) {
                context.protocolFeeVersion = offerParams.saleDetails.protocolFeeVersion;
                context.protocolFees = appStorage().protocolFeeVersions[offerParams.saleDetails.protocolFeeVersion];
            }

            _executeOrderSellSide(
                context,
                offerParams.offerType, 
                offerParams.saleDetails, 
                offerParams.signature,
                tokenSetProofs[i],
                offerParams.cosignature,
                feesOnTop[i]);

            unchecked {
                ++i;
            }
        }
    }

    /**
     * @notice Executes an advanced offer accept transaction for multiple order items.
     *
     * @dev    Throws when a maker's nonce has already been used or has been cancelled.
     * @dev    Throws when any order has expired.
     * @dev    Throws when any combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when any taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when a maker's master nonce does not match the order details.
     * @dev    Throws when an order does not comply with the collection payment settings.
     * @dev    Throws when a maker's signature is invalid.
     * @dev    Throws when an order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when a maker does not have sufficient funds to complete the purchase.
     * @dev    Throws when any token transfer fails for any reason such as lack of approvals or token not owned by the taker.
     * @dev    Throws when the token an offer is being accepted for does not match the conditions set by the maker.
     * @dev    Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Throws when the provided bulk order merkle proof and order index are invalid.
     * @dev    Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
     * @dev    Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. An `AcceptOfferERC721` event has been emitted for a ERC721 sale.
     * @dev    6. An `AcceptOfferERC1155` event has been emitted for a ERC1155 sale.
     * @dev    7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  advancedBidsArray  The cosigner, maker, and taker signatures, order details and permit details.
     * @param  bulkOrderProofs    The merkle proofs and order index for an offer that was created via signing a merkle tree.
     * @param  feesOnTop          The additional fee to add on top of the order, paid by taker.
     * @param  tokenSetProofs     The merkle proofs for an offer that is a subset of tokens in a collection.
     */
    function bulkAcceptOffersAdvanced(
        AdvancedBidOrder[] calldata advancedBidsArray,
        BulkOrderProof[] calldata bulkOrderProofs,
        FeeOnTop[] calldata feesOnTop,
        TokenSetProof[] calldata tokenSetProofs
    ) public {

        if (advancedBidsArray.length == 0) {
            revert PaymentProcessor__InputArrayLengthCannotBeZero();
        }

        if (advancedBidsArray.length != feesOnTop.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (tokenSetProofs.length != feesOnTop.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (bulkOrderProofs.length != feesOnTop.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS_ADVANCED - 
                (BASE_MSG_LENGTH_BULK_ACCEPT_OFFERS_ADVANCED_PER_ITEM * advancedBidsArray.length);

            for (uint256 i = 0; i < advancedBidsArray.length; ++i) {
                appendedDataLength -= PROOF_ELEMENT_SIZE * tokenSetProofs[i].proof.length;
                appendedDataLength -= PROOF_ELEMENT_SIZE * bulkOrderProofs[i].proof.length;
            }
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: false,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: advancedBidsArray[0].advancedOrder.saleDetails.protocolFeeVersion,
            protocolFees: appStorage().protocolFeeVersions[advancedBidsArray[0].advancedOrder.saleDetails.protocolFeeVersion]
        });

        for (uint256 i = 0; i < advancedBidsArray.length;) {
            if (context.protocolFeeVersion != advancedBidsArray[i].advancedOrder.saleDetails.protocolFeeVersion) {
                context.protocolFeeVersion = advancedBidsArray[i].advancedOrder.saleDetails.protocolFeeVersion;
                context.protocolFees = 
                    appStorage().protocolFeeVersions[advancedBidsArray[i].advancedOrder.saleDetails.protocolFeeVersion];
            }

            _executeOrderSellSideAdvanced(
                context,
                advancedBidsArray[i],
                bulkOrderProofs[i],
                feesOnTop[i],
                tokenSetProofs[i]);

            unchecked {
                ++i;
            }
        }
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./PaymentProcessorModuleAdvanced.sol";
import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title ModuleBuyListings
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

contract ModuleBuyListings is PaymentProcessorModuleAdvanced {

    constructor(address configurationContract) PaymentProcessorModuleAdvanced(configurationContract){}

    /**
     * @notice Executes a buy listing transaction for a single order item.
     *
     * @dev    Throws when the maker's nonce has already been used or has been cancelled.
     * @dev    Throws when the order has expired.
     * @dev    Throws when the combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when the taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when the maker's master nonce does not match the order details.
     * @dev    Throws when the order does not comply with the collection payment settings.
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the taker does not have or did not send sufficient funds to complete the purchase.
     * @dev    Throws when the token transfer fails for any reason such as lack of approvals or token no longer owned by maker.
     * @dev    Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Any unused native token payment will be returned to the taker as wrapped native token.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. An `BuyListingERC721` event has been emitted for a ERC721 purchase.
     * @dev    6. An `BuyListingERC1155` event has been emitted for a ERC1155 purchase.
     * @dev    7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  saleDetails     The order execution details.
     * @param  sellerSignature The maker's signature authorizing the order execution.
     * @param  cosignature     The additional cosignature for a cosigned order, if applicable.
     * @param  feeOnTop        The additional fee to add on top of the order, paid by taker.
     */
    function buyListing(
        Order memory saleDetails, 
        SignatureECDSA memory sellerSignature,
        Cosignature memory cosignature,
        FeeOnTop calldata feeOnTop
    ) public payable {
        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = msg.data.length - BASE_MSG_LENGTH_BUY_LISTING;
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: true,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: saleDetails.protocolFeeVersion,
            protocolFees: appStorage().protocolFeeVersions[saleDetails.protocolFeeVersion]
        });

        uint256 remainingNativeProceeds = 
            _executeOrderBuySide(
                context,
                msg.value,
                saleDetails, 
                sellerSignature,
                cosignature,
                feeOnTop
            );

        _refundUnspentNativeFunds(remainingNativeProceeds, context.taker);
    }

    /**
     * @notice Executes an advanced buy listing transaction for a single order item.
     *
     * @dev    Throws when the maker's nonce has already been used or has been cancelled.
     * @dev    Throws when the order has expired.
     * @dev    Throws when the combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when the taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when the maker's master nonce does not match the order details.
     * @dev    Throws when the order does not comply with the collection payment settings.
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the taker does not have or did not send sufficient funds to complete the purchase.
     * @dev    Throws when the token transfer fails for any reason such as lack of approvals or token no longer owned by maker.
     * @dev    Throws when the maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when the order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Any unused native token payment will be returned to the taker as wrapped native token.
     * @dev    Throws when the provided bulk order merkle proof and order index are invalid.
     * @dev    Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Maker's nonce is marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Maker's partially fillable order state is updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. An `BuyListingERC721` event has been emitted for a ERC721 purchase.
     * @dev    6. An `BuyListingERC1155` event has been emitted for a ERC1155 purchase.
     * @dev    7. A `NonceInvalidated` event has been emitted for a ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for a ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  advancedListing  The cosigner, maker, and taker signatures, order details and permit details.
     * @param  bulkOrderProof   The merkle proofs and order index for an offer that was created via signing a merkle tree.
     * @param  feeOnTop         The additional fee to add on top of the order, paid by taker.
     */
    function buyListingAdvanced(
        AdvancedOrder memory advancedListing,
        BulkOrderProof calldata bulkOrderProof,
        FeeOnTop calldata feeOnTop
    ) public payable {
        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_BUY_LISTING_ADVANCED -
                (PROOF_ELEMENT_SIZE * bulkOrderProof.proof.length);
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: true,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: advancedListing.saleDetails.protocolFeeVersion,
            protocolFees: appStorage().protocolFeeVersions[advancedListing.saleDetails.protocolFeeVersion]
        });

        uint256 remainingNativeProceeds = 
            _executeOrderBuySideAdvanced(
                context,
                msg.value,
                advancedListing,
                bulkOrderProof,
                feeOnTop
            );

        _refundUnspentNativeFunds(remainingNativeProceeds, context.taker);
    }

    /**
     * @notice Executes a buy listing transaction for multiple order items.
     *
     * @dev    Throws when a maker's nonce has already been used or has been cancelled.
     * @dev    Throws when any order has expired.
     * @dev    Throws when any combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when any taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when a maker's master nonce does not match the order details.
     * @dev    Throws when an order does not comply with the collection payment settings.
     * @dev    Throws when a maker's signature is invalid.
     * @dev    Throws when an order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the taker does not have or did not send sufficient funds to complete the purchase.
     * @dev    Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
     * @dev    Any unused native token payment will be returned to the taker as wrapped native token.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
     * @dev    6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
     * @dev    7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for each ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  saleDetailsArray An array of order execution details.
     * @param  sellerSignatures An array of maker signatures authorizing the order execution.
     * @param  cosignatures     An array of additional cosignatures for cosigned orders, if applicable.
     * @param  feesOnTop        An array of additional fees to add on top of the orders, paid by taker.
     */
    function bulkBuyListings(
        Order[] calldata saleDetailsArray,
        SignatureECDSA[] calldata sellerSignatures,
        Cosignature[] calldata cosignatures,
        FeeOnTop[] calldata feesOnTop
    ) public payable {
        if (saleDetailsArray.length != sellerSignatures.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (saleDetailsArray.length != cosignatures.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (saleDetailsArray.length != feesOnTop.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (saleDetailsArray.length == 0) {
            revert PaymentProcessor__InputArrayLengthCannotBeZero();
        }

        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_BULK_BUY_LISTINGS - 
                (BASE_MSG_LENGTH_BULK_BUY_LISTINGS_PER_ITEM * saleDetailsArray.length);
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: false,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: saleDetailsArray[0].protocolFeeVersion,
            protocolFees: appStorage().protocolFeeVersions[saleDetailsArray[0].protocolFeeVersion]
        });

        uint256 remainingNativeProceeds = msg.value;

        Order memory saleDetails;
        SignatureECDSA memory sellerSignature;
        Cosignature memory cosignature;
        FeeOnTop calldata feeOnTop;

        for (uint256 i = 0; i < saleDetailsArray.length;) {
            saleDetails = saleDetailsArray[i];
            sellerSignature = sellerSignatures[i];
            cosignature = cosignatures[i];
            feeOnTop = feesOnTop[i];

            if (context.protocolFeeVersion != saleDetails.protocolFeeVersion) {
                context.protocolFeeVersion = saleDetails.protocolFeeVersion;
                context.protocolFees = appStorage().protocolFeeVersions[saleDetails.protocolFeeVersion];
            }

            remainingNativeProceeds = 
                _executeOrderBuySide(
                    context, 
                    remainingNativeProceeds, 
                    saleDetails, 
                    sellerSignature, 
                    cosignature, 
                    feeOnTop);

            unchecked {
                ++i;
            }
        }

        _refundUnspentNativeFunds(remainingNativeProceeds, context.taker);
    }

    /**
     * @notice Executes an advanced buy listing transaction for multiple order items.
     *
     * @dev    Throws when a maker's nonce has already been used or has been cancelled.
     * @dev    Throws when any order has expired.
     * @dev    Throws when any combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when any taker fee on top exceeds 100% of the item sale price.
     * @dev    Throws when a maker's master nonce does not match the order details.
     * @dev    Throws when an order does not comply with the collection payment settings.
     * @dev    Throws when a maker's signature is invalid.
     * @dev    Throws when an order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the taker does not have or did not send sufficient funds to complete the purchase.
     * @dev    Throws when a maker has revoked the order digest on a ERC1155_PARTIAL_FILL order.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the item price is not evenly divisible by the amount.
     * @dev    Throws when an order is an ERC1155_PARTIAL_FILL order and the remaining fillable quantity is less than the requested minimum fill amount.
     * @dev    Throws when any provided bulk order merkle proof and order index are invalid.
     * @dev    Throws when any provided permit processor does not have approval to move the token or receives an invalid signature.
     * @dev    Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
     * @dev    Any unused native token payment will be returned to the taker as wrapped native token.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
     * @dev    6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
     * @dev    7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     * @dev    8. A `OrderDigestInvalidated` event has been emitted for each ERC1155_PARTIAL_FILL order, if fully filled.
     *
     * @param  advancedListingsArray  An array of cosigner, maker, and taker signatures, order details and permit details.
     * @param  bulkOrderProofs        The merkle proofs and order index for an offer that was created via signing a merkle tree.
     * @param  feesOnTop              An array of additional fees to add on top of the orders, paid by taker.
     */
    function bulkBuyListingsAdvanced( 
        AdvancedOrder[] calldata advancedListingsArray,
        BulkOrderProof[] calldata bulkOrderProofs,
        FeeOnTop[] calldata feesOnTop
    ) public payable {
        if (advancedListingsArray.length == 0) {
            revert PaymentProcessor__InputArrayLengthCannotBeZero();
        }

        if (advancedListingsArray.length != feesOnTop.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (advancedListingsArray.length != bulkOrderProofs.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        uint256 remainingNativeProceeds = msg.value;

        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_BULK_BUY_LISTINGS_ADVANCED - 
                (BASE_MSG_LENGTH_BULK_BUY_LISTINGS_ADVANCED_PER_ITEM * advancedListingsArray.length);

            for (uint256 i = 0; i < advancedListingsArray.length; ++i) {
                appendedDataLength -= PROOF_ELEMENT_SIZE * bulkOrderProofs[i].proof.length;
            }
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: false,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: advancedListingsArray[0].saleDetails.protocolFeeVersion,
            protocolFees: appStorage().protocolFeeVersions[advancedListingsArray[0].saleDetails.protocolFeeVersion]
        });

        AdvancedOrder memory advancedListing;

        for (uint256 i = 0; i < advancedListingsArray.length;) {
            advancedListing = advancedListingsArray[i];
            if (context.protocolFeeVersion != advancedListing.saleDetails.protocolFeeVersion) {
                context.protocolFeeVersion = advancedListing.saleDetails.protocolFeeVersion;
                context.protocolFees = appStorage().protocolFeeVersions[advancedListing.saleDetails.protocolFeeVersion];
            }

            remainingNativeProceeds = 
                _executeOrderBuySideAdvanced(
                    context, 
                    remainingNativeProceeds, 
                    advancedListing,
                    bulkOrderProofs[i],
                    feesOnTop[i]);

            unchecked {
                ++i;
            }
        }

        _refundUnspentNativeFunds(remainingNativeProceeds, context.taker);
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./PaymentProcessorModule.sol";
import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title ModuleOnChainCancellation
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

contract ModuleOnChainCancellation is PaymentProcessorModule {
    
    /// @dev A pre-cached signed message hash used for gas-efficient signature recovery
    bytes32 immutable private signedMessageHash;

    constructor(address configurationContract) PaymentProcessorModule(configurationContract) {
        signedMessageHash = ECDSA.toEthSignedMessageHash(bytes(COSIGNER_SELF_DESTRUCT_MESSAGE_TO_SIGN));
    }

    /**
     * @notice Allows a cosigner to destroy itself, never to be used again.  This is a fail-safe in case of a failure
     *         to secure the co-signer private key in a Web2 co-signing service.  In case of suspected cosigner key
     *         compromise, or when a co-signer key is rotated, the cosigner MUST destroy itself to prevent past listings 
     *         that were cancelled off-chain from being used by a malicious actor.
     *
     * @dev    Throws when the cosigner did not sign an authorization to self-destruct.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. The cosigner can never be used to co-sign orders again.
     * @dev    2. A `DestroyedCosigner` event has been emitted.
     *
     * @param  cosigner  The address of the cosigner to destroy.
     * @param  signature The signature of the cosigner authorizing the destruction of itself.
     */
    function destroyCosigner(address cosigner, SignatureECDSA calldata signature) external {
        if(signature.v > type(uint8).max) {
            revert PaymentProcessor__InvalidSignatureV();
        }
        if(cosigner != ECDSA.recover(signedMessageHash, uint8(signature.v), signature.r, signature.s)) {
            revert PaymentProcessor__NotAuthorizedByCosigner();
        }

        appStorage().destroyedCosigners[cosigner] = true;
        emit DestroyedCosigner(cosigner);
    }

    /**
     * @notice Allows a maker to revoke/cancel all prior signatures of their listings and offers.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. The maker's master nonce has been incremented by `1` in contract storage, rendering all signed
     *            approvals using the prior nonce unusable.
     * @dev    2. A `MasterNonceInvalidated` event has been emitted.
     */
    function revokeMasterNonce() external {
        address caller = _msgSender();
        unchecked {
            emit MasterNonceInvalidated(caller, appStorage().masterNonces[caller]++);
        }
    }

    /**
     * @notice Allows a maker to revoke/cancel a single, previously signed listing or offer by specifying the
     *         nonce of the listing or offer.
     *
     * @dev    Throws when the maker has already revoked the nonce.
     * @dev    Throws when the nonce was already used by the maker to successfully buy or sell an NFT.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. The specified `nonce` for the `_msgSender()` has been revoked and can
     *            no longer be used to execute a sale or purchase.
     * @dev    2. A `NonceInvalidated` event has been emitted.
     *
     * @param  nonce The nonce that was signed in the revoked listing or offer.
     */
    function revokeSingleNonce(uint256 nonce) external {
        _checkAndInvalidateNonce(_msgSender(), nonce, true);
    }

    /**
     * @notice Allows a maker to revoke/cancel a partially fillable order by specifying the order digest hash.
     *
     * @dev    Throws when the maker has already revoked the order digest.
     * @dev    Throws when the order digest was already used by the maker and has been fully filled.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. The specified `orderDigest` for the `_msgSender()` has been revoked and can
     *            no longer be used to execute a sale or purchase.
     * @dev    2. An `OrderDigestInvalidated` event has been emitted.
     *
     * @param  orderDigest The order digest that was signed in the revoked listing or offer.
     */
    function revokeOrderDigest(bytes32 orderDigest) external {
        _revokeOrderDigest(_msgSender(), orderDigest);
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./PaymentProcessorModuleAdvanced.sol";
import "@limitbreak/tm-core-lib/src/licenses/LicenseRef-PolyForm-Strict-1.0.0.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title ModuleSweeps
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

contract ModuleSweeps is PaymentProcessorModuleAdvanced {

    constructor(address configurationContract) PaymentProcessorModuleAdvanced(configurationContract){}

    /**
     * @notice Executes a sweep transaction for buying multiple items from the same collection.
     *
     * @dev    Throws when the sweep order protocol is ERC1155_PARTIAL_FILL (unsupported).
     * @dev    Throws when a maker's nonce has already been used or has been cancelled.
     * @dev    Throws when any order has expired.
     * @dev    Throws when any combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when the taker fee on top exceeds 100% of the combined item sale prices.
     * @dev    Throws when a maker's master nonce does not match the order details.
     * @dev    Throws when an order does not comply with the collection payment settings.
     * @dev    Throws when a maker's signature is invalid.
     * @dev    Throws when an order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the taker does not have or did not send sufficient funds to complete the purchase.
     * @dev    Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
     * @dev    Any unused native token payment will be returned to the taker as wrapped native token.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
     * @dev    6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
     * @dev    7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     *
     * @param  feeOnTop         The additional fee to add on top of the orders, paid by taker.
     * @param  sweepOrder       The order information that is common to all items in the sweep.
     * @param  items            An array of items that contains the order information unique to each item.
     * @param  signedSellOrders An array of maker signatures authorizing the order execution.
     * @param  cosignatures     An array of additional cosignatures for cosigned orders, if applicable.
     */
    function sweepCollection(
        FeeOnTop calldata feeOnTop,
        SweepOrder calldata sweepOrder,
        SweepItem[] calldata items,
        SignatureECDSA[] calldata signedSellOrders,
        Cosignature[] calldata cosignatures
    ) public payable {
        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_SWEEP_COLLECTION - 
                (BASE_MSG_LENGTH_SWEEP_COLLECTION_PER_ITEM * items.length);
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: false,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: type(uint256).max,
            protocolFees: ProtocolFees({
                protocolFeeReceiver: address(0),
                minimumProtocolFeeBps: 0,
                marketplaceFeeProtocolTaxBps: 0,
                feeOnTopProtocolTaxBps: 0,
                versionExpiration: 0
            })
        });

        uint256 remainingNativeProceeds =_executeSweepOrder(
            context,
            msg.value,
            feeOnTop,
            sweepOrder,
            items,
            signedSellOrders,
            cosignatures
        );

        _refundUnspentNativeFunds(remainingNativeProceeds, context.taker);
    }

    /**
     * @notice Executes an advanced sweep transaction for buying multiple items from the same collection.
     *
     * @dev    Throws when the sweep order protocol is ERC1155_PARTIAL_FILL (unsupported).
     * @dev    Throws when a maker's nonce has already been used or has been cancelled.
     * @dev    Throws when any order has expired.
     * @dev    Throws when any combined marketplace and royalty fee exceeds 100%.
     * @dev    Throws when the taker fee on top exceeds 100% of the combined item sale prices.
     * @dev    Throws when a maker's master nonce does not match the order details.
     * @dev    Throws when an order does not comply with the collection payment settings.
     * @dev    Throws when a maker's signature is invalid.
     * @dev    Throws when an order is a cosigned order and the cosignature is invalid.
     * @dev    Throws when the transaction originates from an untrusted channel if untrusted channels are blocked.
     * @dev    Throws when the taker does not have or did not send sufficient funds to complete the purchase.
     * @dev    Throws when the provided bulk order merkle proof and order index are invalid.
     * @dev    Throws when the provided permit processor does not have approval to move the token or receives an invalid signature.
     * @dev    Will NOT throw when a token fails to transfer but also will not disperse payments for failed items.
     * @dev    Any unused native token payment will be returned to the taker as wrapped native token.
     *
     * @dev    <h4>Postconditions:</h4>
     * @dev    1. Payment amounts and fees are sent to their respective recipients.
     * @dev    2. Purchased tokens are sent to the beneficiary.
     * @dev    3. Makers nonces are marked as used for ERC721_FILL_OR_KILL and ERC1155_FILL_OR_KILL orders.
     * @dev    4. Makers partially fillable order states are updated for ERC1155_PARTIAL_FILL orders.
     * @dev    5. `BuyListingERC721` events have been emitted for each ERC721 purchase.
     * @dev    6. `BuyListingERC1155` events have been emitted for each ERC1155 purchase.
     * @dev    7. A `NonceInvalidated` event has been emitted for each ERC721_FILL_OR_KILL or ERC1155_FILL_OR_KILL order.
     *
     * @param  advancedSweep  The cosigner, maker, and taker signatures, order details and permit details.
     */
    function sweepCollectionAdvanced(
        AdvancedSweep calldata advancedSweep
    ) public payable {
        uint256 appendedDataLength;
        unchecked {
            appendedDataLength = 
                msg.data.length - 
                BASE_MSG_LENGTH_SWEEP_COLLECTION_ADVANCED - 
                (BASE_MSG_LENGTH_SWEEP_COLLECTION_ADVANCED_PER_ITEM * advancedSweep.items.length);

            for (uint256 i = 0; i < advancedSweep.items.length; ++i) {
                appendedDataLength -= PROOF_ELEMENT_SIZE * advancedSweep.items[i].bulkOrderProof.proof.length;
            }
        }

        TradeContext memory context = TradeContext({
            channel: msg.sender,
            taker: _getTaker(appendedDataLength),
            disablePartialFill: false,
            orderDigest: bytes32(0),
            backfillNumerator: uint16(0),
            backfillReceiver: address(0),
            bountyNumerator: uint16(0),
            exclusiveMarketplace: address(0),
            useRoyaltyBackfillAsRoyaltySource: false,
            protocolFeeVersion: type(uint256).max,
            protocolFees: ProtocolFees({
                protocolFeeReceiver: address(0),
                minimumProtocolFeeBps: 0,
                marketplaceFeeProtocolTaxBps: 0,
                feeOnTopProtocolTaxBps: 0,
                versionExpiration: 0
            })
        });

        uint256 remainingNativeProceeds =_executeSweepOrderAdvanced(
            context,
            msg.value,
            advancedSweep
        );

        _refundUnspentNativeFunds(remainingNativeProceeds, context.taker);
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "../interfaces/IPaymentProcessorConfiguration.sol";
import "../interfaces/IPaymentProcessorEvents.sol";
import "../storage/PaymentProcessorStorageAccess.sol";
import "../settings-registry/IPaymentProcessorSettings.sol";
import "../Constants.sol";
import "../Errors.sol";

import "../settings-registry/ICollectionSettingsRegistry.sol";

import "@limitbreak/tm-core-lib/src/token/erc20/utils/SafeERC20.sol";
import "@limitbreak/tm-core-lib/src/utils/cryptography/EfficientHash.sol";
import "@limitbreak/tm-core-lib/src/token/erc721/IERC721.sol";
import "@limitbreak/tm-core-lib/src/token/erc1155/IERC1155.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

import {TrustedForwarderERC2771Context} from "@limitbreak/trusted-forwarder/TrustedForwarderERC2771Context.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title PaymentProcessorModule
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

abstract contract PaymentProcessorModule is 
    TrustedForwarderERC2771Context, 
    PaymentProcessorStorageAccess, 
    IPaymentProcessorEvents {

    using EnumerableSet for EnumerableSet.AddressSet;

    // Recommendations For Default Immutable Payment Methods Per Chain
    // Default Payment Method 1: Wrapped Native Coin
    // Default Payment Method 2: Wrapped ETH
    // Default Payment Method 3: USDC (Native)
    // Default Payment Method 4: USDC (Bridged)

    /// @dev The address of the ERC20 contract used for wrapped native token.
    address internal immutable _wrappedNativeCoinAddress;

    /// @dev The first default payment method defined at contract deployment. Immutable to save SLOAD cost.
    address internal immutable defaultPaymentMethod1;

    /// @dev The second default payment method defined at contract deployment. Immutable to save SLOAD cost.
    address internal immutable defaultPaymentMethod2;

    /// @dev The third default payment method defined at contract deployment. Immutable to save SLOAD cost.
    address internal immutable defaultPaymentMethod3;

    /// @dev The fourth default payment method defined at contract deployment. Immutable to save SLOAD cost.
    address internal immutable defaultPaymentMethod4;

    /// @dev The address of the collection settings registry contract.
    address internal immutable collectionSettingsRegistry;

    /// @dev The domain separator for EIP-712 signatures.
    bytes32 internal immutable _cachedDomainSeparator;

    address internal immutable _defaultInfrastructureFeeReceiver;

    constructor(address configurationContract)
    TrustedForwarderERC2771Context(
        IPaymentProcessorConfiguration(configurationContract).getPaymentProcessorModuleERC2771ContextParams()
    ) {
        (
            address paymentProcessorAddress_,
            address wrappedNativeCoinAddress_,
            DefaultPaymentMethods memory defaultPaymentMethods,,
            address collectionSettingsRegistry_,
            address infrastructureFeeReceiver_
        ) = IPaymentProcessorConfiguration(configurationContract).getPaymentProcessorModuleDeploymentParams();
        
        if (wrappedNativeCoinAddress_ == address(0) ||
            collectionSettingsRegistry_ == address(0)) {
            revert PaymentProcessor__InvalidConstructorArguments();
        }

        _wrappedNativeCoinAddress = wrappedNativeCoinAddress_;
        defaultPaymentMethod1 = defaultPaymentMethods.defaultPaymentMethod1;
        defaultPaymentMethod2 = defaultPaymentMethods.defaultPaymentMethod2;
        defaultPaymentMethod3 = defaultPaymentMethods.defaultPaymentMethod3;
        defaultPaymentMethod4 = defaultPaymentMethods.defaultPaymentMethod4;
        collectionSettingsRegistry = collectionSettingsRegistry_;

        _cachedDomainSeparator =
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 
                    keccak256(bytes("PaymentProcessor")), 
                    keccak256(bytes("3.0.0")), 
                    block.chainid, 
                    paymentProcessorAddress_
                )
            );

        _defaultInfrastructureFeeReceiver = infrastructureFeeReceiver_;
    }

    /*************************************************************************/
    /*                        Default Payment Methods                        */
    /*************************************************************************/

    /**
     * @notice Returns true if `paymentMethod` is a default payment method.
     * 
     * @dev    This function will return true if the default payment method was added after contract deployment.
     *
     * @param paymentMethod The address of the payment method to check.
     */
    function _isDefaultPaymentMethod(address paymentMethod) internal returns (bool) {
        if (paymentMethod == address(0)) {
            return true;
        } else if (paymentMethod == _wrappedNativeCoinAddress) {
            return true;
        } else if (paymentMethod == defaultPaymentMethod1) {
            return true;
        } else if (paymentMethod == defaultPaymentMethod2) {
            return true;
        } else if (paymentMethod == defaultPaymentMethod3) {
            return true;
        } else if (paymentMethod == defaultPaymentMethod4) {
            return true;
        } else {
            // If it isn't one of the gas efficient immutable default payment methods,
            // it may have bee added to the fallback default payment method whitelist,
            // but there are SLOAD costs.

            return _isWhitelistedPaymentMethod(DEFAULT_PAYMENT_METHOD_WHITELIST_ID, paymentMethod);
        }
    }

    /**
     * @notice Returns true if `paymentMethod` is whitelisted for the provided `whitelistId`.
     *
     * @dev    This function will cache the payment method to the Payment Processor from the Collection Settings Registry
     * @dev    if it is not already cached.
     *
     * @param  whitelistId   The id of the whitelist to check.
     * @param  paymentMethod The address of the payment method to check.
     */
    function _isWhitelistedPaymentMethod(uint32 whitelistId, address paymentMethod) internal returns(bool) {
        if (appStorage().collectionPaymentMethodWhitelists[whitelistId].contains(paymentMethod)) {
            return true;
        }
        return IPaymentProcessorSettings(address(this)).checkWhitelistedPaymentMethod(whitelistId, paymentMethod);
    }

    /**
     * @notice Adds `paymentMethod` to the provided `whitelistId`.
     *
     * @param  ptrPaymentMethods The storage pointer to the payment method whitelist.
     * @param  whitelistId       The id of the whitelist to add the payment method to.
     * @param  paymentMethod     The address of the payment method to add.
     */
    function _addWhitelistedPaymentMethod(EnumerableSet.AddressSet storage ptrPaymentMethods, uint32 whitelistId, address paymentMethod) internal {
        if (ptrPaymentMethods.add(paymentMethod)) {
            emit PaymentMethodAddedToWhitelist(whitelistId, paymentMethod);
        }
    }

    /**
     * @notice Removes `paymentMethod` from the provided `whitelistId`.
     *
     * @param  ptrPaymentMethods The storage pointer to the payment method whitelist.
     * @param  whitelistId       The id of the whitelist to remove the payment method from.
     * @param  paymentMethod     The address of the payment method to remove.
     */
    function _removeWhitelistedPaymentMethod(EnumerableSet.AddressSet storage ptrPaymentMethods, uint32 whitelistId, address paymentMethod) internal {
        if (ptrPaymentMethods.remove(paymentMethod)) {
            emit PaymentMethodRemovedFromWhitelist(whitelistId, paymentMethod);
        }
    }

    /**
     * @notice Returns true if the provided `channel` is a trusted channel for the provided `tokenAddress`, false otherwise.
     *
     * @dev    This function will cache the channel to the Payment Processor from the Collection Settings Registry
     * @dev    if it is not already cached.
     *
     * @param  tokenAddress The address of the token collection.
     * @param  channel      The address of the channel to check.
     */
    function _isCollectionTrustedChannel(address tokenAddress, address channel) internal returns(bool) {
        if (appStorage().collectionTrustedChannels[tokenAddress].contains(channel)) {
            return true;
        }
        return IPaymentProcessorSettings(address(this)).checkCollectionTrustedChannels(tokenAddress, channel);
    }

    /**
     * @notice Adds `channel` to the trusted channels for the provided `tokenAddress`.
     *
     * @param  ptrTrustedChannels The storage pointer to the trusted channels set.
     * @param  tokenAddress       The address of the token collection.
     * @param  channel            The address of the channel to add.
     */
    function _addCollectionTrustedChannel(EnumerableSet.AddressSet storage ptrTrustedChannels, address tokenAddress, address channel) internal {
        if (ptrTrustedChannels.add(channel)) {
            emit TrustedChannelAddedForCollection(tokenAddress, channel);
        }
    }

    /**
     * @notice Removes `channel` from the trusted channels for the provided `tokenAddress`.
     *
     * @param  ptrTrustedChannels The storage pointer to the trusted channels set.
     * @param  tokenAddress       The address of the token collection.
     * @param  channel            The address of the channel to remove.
     */
    function _removeCollectionTrustedChannel(EnumerableSet.AddressSet storage ptrTrustedChannels, address tokenAddress, address channel) internal {
        if (ptrTrustedChannels.remove(channel)) {
            emit TrustedChannelRemovedForCollection(tokenAddress, channel);
        }
    }

    /**
     * @notice Adds `permitProcessor` to the trusted permit processors set.
     *
     * @param  permitProcessor The address of the permit processor to add.
     */
    function _addTrustedPermitProcessor(address permitProcessor) internal {
        if (appStorage().trustedPermitProcessors.add(permitProcessor)) {
            emit TrustedPermitProcessorAdded(permitProcessor);
        }
    }

    /**
     * @notice Removes `permitProcessor` from the trusted permit processors set.
     *
     * @param  permitProcessor The address of the permit processor to remove.
     */
    function _removeTrustedPermitProcessor(address permitProcessor) internal {
        if (appStorage().trustedPermitProcessors.remove(permitProcessor)) {
            emit TrustedPermitProcessorRemoved(permitProcessor);
        }
    }

    /**
     * @notice Returns an array of the default payment methods defined at contract deployment.
     * 
     * @dev    This array will **NOT** include default payment methods added after contract deployment.
     */
    function _getDefaultPaymentMethods() internal view returns (address[] memory) {
        address[] memory defaultPaymentMethods = new address[](6);
        defaultPaymentMethods[0] = address(0);
        defaultPaymentMethods[1] = _wrappedNativeCoinAddress;
        defaultPaymentMethods[2] = defaultPaymentMethod1;
        defaultPaymentMethods[3] = defaultPaymentMethod2;
        defaultPaymentMethods[4] = defaultPaymentMethod3;
        defaultPaymentMethods[5] = defaultPaymentMethod4;
        return defaultPaymentMethods;
    }

    /*************************************************************************/
    /*                            Order Execution                            */
    /*************************************************************************/

    /**
     * @notice Adjusts the item price and amount for per unit pricing.
     *
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when a partial fill order is not equally divisible by the number of items in the order.
     * 
     * @param quantityToFill The quantity of the item to fill.
     * @param saleDetails    The order execution details.
     */
    function _adjustForUnitPricing(uint256 quantityToFill, Order memory saleDetails) internal pure {
        if (quantityToFill != saleDetails.amount) {
            if (saleDetails.itemPrice % saleDetails.amount != 0) {
                revert PaymentProcessor__PartialFillsNotSupportedForNonDivisibleItems();
            }

            saleDetails.itemPrice = saleDetails.itemPrice / saleDetails.amount * quantityToFill;
            saleDetails.amount = quantityToFill;
        }
    }

    /**
     * @notice Validates the collection payment settings and fulfills an order.
     *
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when a partial fill order is not equally divisible by the number of items in the order.
     * @dev    Throws when a collection is set to block untrusted channels and the transaction originates 
     * @dev    from an untrusted channel.
     * @dev    Throws when the payment method is not an allowed payment method.
     * @dev    Throws when the sweep order is for ERC721 tokens and the amount is set to a value other than one.
     * @dev    Throws when the sweep order is for ERC1155 tokens and the amount is set to zero.
     * @dev    Throws when the marketplace fee and maximum royalty fee will exceed the sales price of an item.
     * @dev    Throws when the current block time is greater than the order expiration.
     * 
     *
     * @param  side                 The side of the order to fulfill.
     * @param  context              The current execution context to determine the taker.
     * @param  startingNativeFunds  The amount of native funds available at the beginning of the order execution.
     * @param  quantityToFill       The quantity of the item to fill.
     * @param  saleDetails          The order execution details.
     * @param  feeOnTop             The additional fee to add on top of the order, paid by taker.
     */
    function _validateAndFulfillOrder(
        Sides side,
        TradeContext memory context,
        uint256 startingNativeFunds,
        uint256 quantityToFill,
        Order memory saleDetails,
        FeeOnTop calldata feeOnTop
    ) internal returns (uint256 endingNativeFunds) {
        _adjustForUnitPricing(quantityToFill, saleDetails);

        _validateBasicOrderDetails(context, saleDetails);

        endingNativeFunds = _fulfillSingleOrderWithFeeOnTop(
            startingNativeFunds,
            context,
            side == Sides.Buy ? context.taker : saleDetails.maker,
            side == Sides.Buy ? saleDetails.maker : context.taker,
            saleDetails.paymentMethod,
            _getOrderFulfillmentFunctionPointers(side, saleDetails.paymentMethod, saleDetails.protocol),
            saleDetails,
            feeOnTop);
    }

    /**
     * @notice Checks order validation and fulfills a buy listing order.
     * 
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when a partial fill order is not equally divisible by the number of items in the order.
     * 
     * @param context             The current execution context to determine the taker.
     * @param startingNativeFunds The amount of native funds available at the beginning of the order execution.
     * @param saleDetails         The order execution details.
     * @param signedSellOrder     The maker's signature authorizing the order execution.
     * @param cosignature         The additional cosignature for a cosigned order, if applicable.
     * @param feeOnTop            The additional fee to add on top of the order, paid by taker.
     * 
     * @return endingNativeFunds  The amount of native funds available at the end of the order execution.
     */
    function _executeOrderBuySide(
        TradeContext memory context,
        uint256 startingNativeFunds,
        Order memory saleDetails,
        SignatureECDSA memory signedSellOrder,
        Cosignature memory cosignature,
        FeeOnTop calldata feeOnTop
    ) internal returns (uint256 endingNativeFunds) {
        saleDetails.itemPrice = uint240(saleDetails.itemPrice);

        uint256 quantityToFill = _verifySaleApproval(
            context, 
            saleDetails, 
            signedSellOrder, 
            cosignature);

        endingNativeFunds = 
            _validateAndFulfillOrder(
                Sides.Buy, 
                context, 
                startingNativeFunds, 
                quantityToFill, 
                saleDetails, 
                feeOnTop);
    }

    /**
     * @notice Checks order validation and fulfills an offer acceptance.
     * 
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when the payment method is the chain native token.
     * @dev    Throws when the supplied token for a token set offer cannot be validated with the root hash and proof.
     * @dev    Throws when a partial fill order is not equally divisible by the number of items in the order.
     * 
     * @param  context                The current execution context to determine the taker.
     * @param  offerType              The offer type to execute.
     * @param  saleDetails            The order execution details.
     * @param  signature              The maker's signature authorizing the order execution.
     * @param  tokenSetProof          The root hash and merkle proofs for an offer that is a subset of tokens in a collection.
     * @param  cosignature            The additional cosignature for a cosigned order, if applicable.
     * @param  feeOnTop               The additional fee to add on top of the order, paid by taker.
     */
    function _executeOrderSellSide(
        TradeContext memory context,
        uint256 offerType, 
        Order memory saleDetails,
        SignatureECDSA memory signature,
        TokenSetProof calldata tokenSetProof,
        Cosignature memory cosignature,
        FeeOnTop calldata feeOnTop
    ) internal {
        if (saleDetails.paymentMethod == address(0)) {
            revert PaymentProcessor__BadPaymentMethod();
        }
        saleDetails.itemPrice = uint240(saleDetails.itemPrice);

        uint256 quantityToFill = 
            _verifyBidOrderSignatures(
                offerType, 
                context, 
                saleDetails, 
                signature, 
                cosignature, 
                tokenSetProof
            );

        _validateAndFulfillOrder(
            Sides.Sell, 
            context, 
            0, 
            quantityToFill, 
            saleDetails, 
            feeOnTop);
    }

    /**
     * @notice Checks order validation and fulfills a sweep order.
     * 
     * @dev    Throws when the order protocol is for ERC1155 partial fills.
     * @dev    Throws when the `items`, `signedSellOrders` and `cosignatures` arrays have different lengths.
     * @dev    Throws when the `items` array length is zero.
     * 
     * @param context             The current execution context to determine the taker.
     * @param startingNativeFunds The amount of native funds available at the beginning of the order execution.
     * @param feeOnTop            The additional fee to add on top of the orders, paid by taker.
     * @param sweepOrder          The order information that is common to all items in the sweep.
     * @param items               An array of items that contains the order information unique to each item.
     * @param signedSellOrders    An array of maker signatures authorizing the order execution.
     * @param cosignatures        An array of additional cosignatures for cosigned orders, if applicable.
     * 
     * @return endingNativeFunds  The amount of native funds available at the end of the order execution.
     */
    function _executeSweepOrder(
        TradeContext memory context,
        uint256 startingNativeFunds,
        FeeOnTop calldata feeOnTop,
        SweepOrder calldata sweepOrder,
        SweepItem[] calldata items,
        SignatureECDSA[] calldata signedSellOrders,
        Cosignature[] calldata cosignatures
    ) internal returns (uint256 endingNativeFunds) {

        if (sweepOrder.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
            revert PaymentProcessor__OrderProtocolERC1155FillPartialUnsupportedInSweeps();
        }

        if (items.length != signedSellOrders.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (items.length != cosignatures.length) {
            revert PaymentProcessor__InputArrayLengthMismatch();
        }

        if (items.length == 0) {
            revert PaymentProcessor__InputArrayLengthCannotBeZero();
        }

        SweepExecutionParams memory params = _validateSweepOrderHeader(context, sweepOrder);

        endingNativeFunds = _validateAndFulfillSweepOrder(
            context,
            startingNativeFunds,
            feeOnTop,
            sweepOrder,
            items,
            signedSellOrders,
            cosignatures,
            params
        );
    }

    /*************************************************************************/
    /*                           Order Hashes                                */
    /*************************************************************************/

    /**
     * @notice Generates the order hash for a listing order.
     *
     * @param saleDetails      The order execution details.
     * @param signer           The address of the signer authorizing the order.
     * @param cosigner         The address of the cosigner authorizing the order.
     * @param makerMasterNonce The master nonce for the maker.
     */
    function _generateListingOrderHash(
        Order memory saleDetails,
        address signer,
        address cosigner,
        uint256 makerMasterNonce
    ) internal pure returns (bytes32 orderHash) {
        orderHash =
            EfficientHash.efficientHashExtensionEnd(
                EfficientHash.efficientHashExtensionContinue(
                    EfficientHash.efficientHashExtensionStart(
                        17,
                        SALE_APPROVAL_HASH,
                        bytes32(uint256(uint8(saleDetails.protocol))),
                        bytes32(uint256(uint160(cosigner))),
                        bytes32(uint256(uint160(signer))),
                        bytes32(uint256(uint160(saleDetails.marketplace))),
                        bytes32(uint256(uint160(saleDetails.fallbackRoyaltyRecipient))),
                        bytes32(uint256(uint160(saleDetails.paymentMethod))),
                        bytes32(uint256(uint160(saleDetails.tokenAddress)))
                    ),
                    bytes32(saleDetails.tokenId),
                    bytes32(saleDetails.amount),
                    bytes32(saleDetails.itemPrice),
                    bytes32(saleDetails.expiration),
                    bytes32(saleDetails.marketplaceFeeNumerator),
                    bytes32(saleDetails.maxRoyaltyFeeNumerator),
                    bytes32(saleDetails.nonce),
                    bytes32(makerMasterNonce)
                ),
                bytes32(saleDetails.protocolFeeVersion)
            );
    }

    /**
     * @notice Generates the order hash for a single item offer order.
     *
     * @param saleDetails      The order execution details.
     * @param signer           The address of the signer authorizing the order.
     * @param cosigner         The address of the cosigner authorizing the order.
     * @param makerMasterNonce The master nonce for the maker.
     */
    function _generateItemOfferOrderHash(
        Order memory saleDetails,
        address signer,
        address cosigner,
        uint256 makerMasterNonce
    ) internal pure returns (bytes32 orderHash) {
        orderHash = 
            EfficientHash.efficientHashSixteenStep2(
                EfficientHash.efficientHashSixteenStep1(
                    ITEM_OFFER_APPROVAL_HASH,
                    bytes32(uint256(uint8(saleDetails.protocol))),
                    bytes32(uint256(uint160(cosigner))),
                    bytes32(uint256(uint160(signer))),
                    bytes32(uint256(uint160(saleDetails.beneficiary))),
                    bytes32(uint256(uint160(saleDetails.marketplace))),
                    bytes32(uint256(uint160(saleDetails.fallbackRoyaltyRecipient))),
                    bytes32(uint256(uint160(saleDetails.paymentMethod)))
                ),
                bytes32(uint256(uint160(saleDetails.tokenAddress))),
                bytes32(saleDetails.tokenId),
                bytes32(saleDetails.amount),
                bytes32(saleDetails.itemPrice),
                bytes32(saleDetails.expiration),
                bytes32(saleDetails.marketplaceFeeNumerator),
                bytes32(saleDetails.nonce),
                bytes32(makerMasterNonce)
            );
    }

    /**
     * @notice Generates the order hash for a collection offer order.
     *
     * @param saleDetails      The order execution details.
     * @param signer           The address of the signer authorizing the order.
     * @param cosigner         The address of the cosigner authorizing the order.
     * @param makerMasterNonce The master nonce for the maker.
     */
    function _generateCollectionOfferOrderHash(
        Order memory saleDetails,
        address signer,
        address cosigner,
        uint256 makerMasterNonce
    ) internal pure returns (bytes32 orderHash) {
        orderHash = 
            EfficientHash.efficientHashFifteenStep2(
                EfficientHash.efficientHashFifteenStep1(
                    COLLECTION_OFFER_APPROVAL_HASH,
                    bytes32(uint256(uint8(saleDetails.protocol))),
                    bytes32(uint256(uint160(cosigner))),
                    bytes32(uint256(uint160(signer))),
                    bytes32(uint256(uint160(saleDetails.beneficiary))),
                    bytes32(uint256(uint160(saleDetails.marketplace))),
                    bytes32(uint256(uint160(saleDetails.fallbackRoyaltyRecipient))),
                    bytes32(uint256(uint160(saleDetails.paymentMethod)))
                ),
                bytes32(uint256(uint160(saleDetails.tokenAddress))),
                bytes32(saleDetails.amount),
                bytes32(saleDetails.itemPrice),
                bytes32(saleDetails.expiration),
                bytes32(saleDetails.marketplaceFeeNumerator),
                bytes32(saleDetails.nonce),
                bytes32(makerMasterNonce)
            );
    }

    /**
     * @notice Generates the order hash for a token set offer order.
     *
     * @param saleDetails      The order execution details.
     * @param signer           The address of the signer authorizing the order.
     * @param cosigner         The address of the cosigner authorizing the order.
     * @param makerMasterNonce The master nonce for the maker.
     * @param tokenSetProof    The token set proof for the order.
     */
    function _generateTokenSetOfferOrderHash(
        TokenSetProof calldata tokenSetProof,
        Order memory saleDetails,
        address signer,
        address cosigner,
        uint256 makerMasterNonce
    ) internal pure returns (bytes32 orderHash) {
        bytes32 tokenSetRootHash = _generateTokenSetRootHash(saleDetails.tokenAddress, saleDetails.tokenId, tokenSetProof.proof);

        orderHash = 
            EfficientHash.efficientHashSixteenStep2(
                EfficientHash.efficientHashSixteenStep1(
                    TOKEN_SET_OFFER_APPROVAL_HASH,
                    bytes32(uint256(uint8(saleDetails.protocol))),
                    bytes32(uint256(uint160(cosigner))),
                    bytes32(uint256(uint160(signer))),
                    bytes32(uint256(uint160(saleDetails.beneficiary))),
                    bytes32(uint256(uint160(saleDetails.marketplace))),
                    bytes32(uint256(uint160(saleDetails.fallbackRoyaltyRecipient))),
                    bytes32(uint256(uint160(saleDetails.paymentMethod)))
                ),
                bytes32(uint256(uint160(saleDetails.tokenAddress))),
                bytes32(saleDetails.amount),
                bytes32(saleDetails.itemPrice),
                bytes32(saleDetails.expiration),
                bytes32(saleDetails.marketplaceFeeNumerator),
                bytes32(saleDetails.nonce),
                bytes32(makerMasterNonce),
                tokenSetRootHash
            );
    }

    function _generateTokenSetRootHash(
        address tokenAddress,
        uint256 tokenId,
        bytes32[] calldata proof
    ) internal pure returns (bytes32 tokenSetRootHash) {
        tokenSetRootHash = EfficientHash.efficientHash(bytes32(uint256(uint160(tokenAddress))), bytes32(tokenId));
        bytes32 currentProof;
        for (uint256 i; i < proof.length; ++i) {
            currentProof = proof[i];
            if (currentProof < tokenSetRootHash) {
                tokenSetRootHash = EfficientHash.efficientHash(currentProof, tokenSetRootHash);
            } else {
                tokenSetRootHash = EfficientHash.efficientHash(tokenSetRootHash, currentProof);
            }
        }
    }

    /*************************************************************************/
    /*                           Order Validation                            */
    /*************************************************************************/

    /**
     * @notice Loads collection payment settings to validate a single item order.
     * 
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when a collection is set to block untrusted channels and the transaction originates 
     * @dev    from an untrusted channel.
     * @dev    Throws when the payment method is not an allowed payment method.
     * @dev    Throws when the sweep order is for ERC721 tokens and the amount is set to a value other than one.
     * @dev    Throws when the sweep order is for ERC1155 tokens and the amount is set to zero.
     * @dev    Throws when the marketplace fee and maximum royalty fee will exceed the sales price of an item.
     * @dev    Throws when the current block time is greater than the order expiration.
     * 
     * @param context     The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     */
    function _validateBasicOrderDetails(
        TradeContext memory context,
        Order memory saleDetails
    ) internal {
        if (saleDetails.protocol == ORDER_PROTOCOLS_ERC721_FILL_OR_KILL) {
            if (saleDetails.amount != ONE) {
                revert PaymentProcessor__AmountForERC721SalesMustEqualOne();
            }
        } else if (saleDetails.protocol < ORDER_PROTOCOLS_INVALID) {
            if (saleDetails.amount == 0) {
                revert PaymentProcessor__AmountForERC1155SalesGreaterThanZero();
            }
        } else {
            revert PaymentProcessor__InvalidOrderProtocol();
        }

        if (block.timestamp > saleDetails.expiration) {
            revert PaymentProcessor__OrderHasExpired();
        }

        if (saleDetails.marketplaceFeeNumerator + saleDetails.maxRoyaltyFeeNumerator > FEE_DENOMINATOR) {
            revert PaymentProcessor__MarketplaceAndRoyaltyFeesWillExceedSalePrice();
        }

        CollectionPaymentSettings storage paymentSettingsForCollection = 
            appStorage().collectionPaymentSettings[saleDetails.tokenAddress];

        if (!paymentSettingsForCollection.initialized) {
            IPaymentProcessorSettings(address(this)).checkSyncCollectionSettings(saleDetails.tokenAddress);
        }

        PaymentSettings paymentSettings = paymentSettingsForCollection.paymentSettings;
        context.backfillReceiver = paymentSettingsForCollection.royaltyBackfillReceiver;
        context.backfillNumerator = paymentSettingsForCollection.royaltyBackfillNumerator;
        context.bountyNumerator = paymentSettingsForCollection.royaltyBountyNumerator;

        uint8 flags = paymentSettingsForCollection.flags;

        if (_isFlagSet(flags, FLAG_BLOCK_TRADES_FROM_UNTRUSTED_CHANNELS)) {
            if (!_isCollectionTrustedChannel(saleDetails.tokenAddress, context.channel)) {
                revert PaymentProcessor__TradeOriginatedFromUntrustedChannel();
            }
        }

        context.useRoyaltyBackfillAsRoyaltySource = _isFlagSet(flags, FLAG_USE_BACKFILL_AS_ROYALTY_SOURCE);

        context.exclusiveMarketplace = 
            _isFlagSet(flags, FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE) ?
                appStorage().collectionExclusiveBountyReceivers[saleDetails.tokenAddress] : address(0);
        
        if (paymentSettings == PaymentSettings.DefaultPaymentMethodWhitelist) {
            if (!_isDefaultPaymentMethod(saleDetails.paymentMethod)) {
                revert PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();
            }
        } else if (paymentSettings == PaymentSettings.CustomPaymentMethodWhitelist) {
            if (!_isWhitelistedPaymentMethod(paymentSettingsForCollection.paymentMethodWhitelistId, saleDetails.paymentMethod)) {
                revert PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();
            }
        } else if (paymentSettings == PaymentSettings.PricingConstraints || paymentSettings == PaymentSettings.PricingConstraintsCollectionOnly) {
            if (appStorage().collectionConstrainedPricingPaymentMethods[saleDetails.tokenAddress] != saleDetails.paymentMethod) {
                revert PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();
            }

            _validateSalePriceInRange(
                paymentSettings,
                saleDetails.tokenAddress, 
                saleDetails.tokenId, 
                saleDetails.amount, 
                saleDetails.itemPrice);
        } else if (paymentSettings == PaymentSettings.Paused) {
            revert PaymentProcessor__TradingIsPausedForCollection();
        }

        if (block.timestamp > context.protocolFees.versionExpiration) {
            revert PaymentProcessor__ProtocolFeeVersionExpired();
        }

        if (context.protocolFees.minimumProtocolFeeBps > FEE_DENOMINATOR) {
            revert PaymentProcessor__ProtocolFeeOrTaxExceedsCap();
        }
    }

    /**
     * @notice Validates the sweep order header and returns the sweep execution parameters.
     *
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when a collection is set to block untrusted channels and the transaction originates
     * @dev    from an untrusted channel.
     * @dev    Throws when the payment method is not an allowed payment method.
     * @dev    Throws when trading is paused for the collection.
     * 
     * @param context    The current execution context to determine the taker.
     * @param sweepOrder The order information that is common to all items in the sweep.
     */
    function _validateSweepOrderHeader(
        TradeContext memory context,
        SweepOrder memory sweepOrder
    ) internal returns (
        SweepExecutionParams memory params
    ) {
        CollectionPaymentSettings storage paymentSettingsForCollection = 
            appStorage().collectionPaymentSettings[sweepOrder.tokenAddress];

        if (!paymentSettingsForCollection.initialized) {
            IPaymentProcessorSettings(address(this)).checkSyncCollectionSettings(sweepOrder.tokenAddress);
        }

        PaymentSettings paymentSettings = paymentSettingsForCollection.paymentSettings;
        context.backfillReceiver = paymentSettingsForCollection.royaltyBackfillReceiver;
        context.backfillNumerator = paymentSettingsForCollection.royaltyBackfillNumerator;
        context.bountyNumerator = paymentSettingsForCollection.royaltyBountyNumerator;

        uint8 flags = paymentSettingsForCollection.flags;

        if (_isFlagSet(flags, FLAG_BLOCK_TRADES_FROM_UNTRUSTED_CHANNELS)) {
            if (!_isCollectionTrustedChannel(sweepOrder.tokenAddress, context.channel)) {
                revert PaymentProcessor__TradeOriginatedFromUntrustedChannel();
            }
        }
        
        context.useRoyaltyBackfillAsRoyaltySource = _isFlagSet(flags, FLAG_USE_BACKFILL_AS_ROYALTY_SOURCE);

        context.exclusiveMarketplace = 
            _isFlagSet(flags, FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE) ?
                appStorage().collectionExclusiveBountyReceivers[sweepOrder.tokenAddress] : address(0);

        if (paymentSettings == PaymentSettings.DefaultPaymentMethodWhitelist) {
            if (!_isDefaultPaymentMethod(sweepOrder.paymentMethod)) {
                revert PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();
            }
        } else if (paymentSettings == PaymentSettings.CustomPaymentMethodWhitelist) {
            if (!_isWhitelistedPaymentMethod(paymentSettingsForCollection.paymentMethodWhitelistId, sweepOrder.paymentMethod)) {
                revert PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();
            }
        } else if (paymentSettings == PaymentSettings.PricingConstraints || paymentSettings == PaymentSettings.PricingConstraintsCollectionOnly) {
            if (appStorage().collectionConstrainedPricingPaymentMethods[sweepOrder.tokenAddress] != sweepOrder.paymentMethod) {
                revert PaymentProcessor__PaymentCoinIsNotAnApprovedPaymentMethod();
            }
            params.paymentSettings = paymentSettings;
        } else if (paymentSettings == PaymentSettings.Paused) {
            revert PaymentProcessor__TradingIsPausedForCollection();
        }

        params.fnPointers = _getOrderFulfillmentFunctionPointers(Sides.Buy, sweepOrder.paymentMethod, sweepOrder.protocol);
        params.paymentCoin = sweepOrder.paymentMethod;
    }

    /**
     * @notice Validates the sales price for a token is within the bounds set.
     * 
     * @dev    Throws when the unit price is above the ceiling bound.
     * @dev    Throws when the unit price is below the floor bound.
     * 
     * @param tokenAddress The contract address for the token.
     * @param tokenId      The token id.
     * @param amount       The quantity of the token being transacted.
     * @param salePrice    The total price for the token quantity.
     */
    function _validateSalePriceInRange(
        PaymentSettings paymentSettings,
        address tokenAddress, 
        uint256 tokenId, 
        uint256 amount, 
        uint256 salePrice
    ) internal {
        (uint256 floorPrice, uint256 ceilingPrice) = _getFloorAndCeilingPrices(paymentSettings, tokenAddress, tokenId);

        unchecked {
            uint256 unitPrice = salePrice / amount;

            if (unitPrice > ceilingPrice) {
                revert PaymentProcessor__SalePriceAboveMaximumCeiling();
            }

            if (unitPrice < floorPrice) {
                revert PaymentProcessor__SalePriceBelowMinimumFloor();
            }
        }
    }

    /**
     * @notice Returns the floor and ceiling price for a token for collections set to use pricing constraints.
     * 
     * @dev    Returns token pricing bounds if token bounds are set. 
     * @dev    If token bounds are not set then returns collection pricing bounds if they are set.
     * @dev    If collection bounds are not set, returns zero floor bound and uint256 max ceiling bound.
     * 
     * @param tokenAddress The contract address for the token.
     * @param tokenId      The token id.
     */
    function _getFloorAndCeilingPrices(
        PaymentSettings paymentSettings,
        address tokenAddress, 
        uint256 tokenId
    ) internal returns (uint256, uint256) {
        if (paymentSettings == PaymentSettings.PricingConstraints) {
            PricingBounds storage tokenLevelPricingBounds = appStorage().tokenPricingBounds[tokenAddress][tokenId];
            if (!tokenLevelPricingBounds.initialized) {
                return IPaymentProcessorSettings(address(this)).checkSyncTokenPricingBounds(tokenAddress, tokenId);
            }
            if (tokenLevelPricingBounds.isSet) {
                return (tokenLevelPricingBounds.floorPrice, tokenLevelPricingBounds.ceilingPrice);
            }
        }
        
        PricingBounds memory collectionLevelPricingBounds = appStorage().collectionPricingBounds[tokenAddress];
        if (collectionLevelPricingBounds.isSet) {
            return (collectionLevelPricingBounds.floorPrice, collectionLevelPricingBounds.ceilingPrice);
        }

        return (0, type(uint256).max);
    }

    /*************************************************************************/
    /*                           Order Fulfillment                           */
    /*************************************************************************/

    /**
     * @notice Dispenses tokens and proceeds for a single order.
     * 
     * @dev    This function may be called multiple times during a bulk execution.
     * @dev    Throws when a token false to dispense AND partial fills are disabled.
     * @dev    Throws when the taker did not supply enough native funds.
     * @dev    Throws when the fee on top amount is greater than the item price.
     * 
     * @param startingNativeFunds      The amount of native funds remaining at the beginning of the function call.
     * @param context                  The current execution context to determine the taker.
     * @param purchaser                The user that is buying the token.
     * @param seller                   The user that is selling the token.
     * @param paymentCoin              The ERC20 token used for payment, will be zero values for chain native token.
     * @param fnPointers               Struct containing the function pointers for dispensing tokens, sending payments
     *                                 and emitting events.
     * @param saleDetails              The order execution details.
     * @param feeOnTop                 The additional fee on top of the item sales price to be paid by the taker.
     *
     * @return endingNativeFunds       The amount of native funds remaining at the end of the function call.
     */
    function _fulfillSingleOrderWithFeeOnTop(
        uint256 startingNativeFunds,
        TradeContext memory context,
        address purchaser,
        address seller,
        address paymentCoin,
        FulfillOrderFunctionPointers memory fnPointers,
        Order memory saleDetails,
        FeeOnTop calldata feeOnTop
    ) internal returns (uint256 endingNativeFunds) {
        endingNativeFunds = startingNativeFunds;

        if (!fnPointers.funcDispenseToken(
                seller, 
                saleDetails.beneficiary, 
                saleDetails.tokenAddress, 
                saleDetails.tokenId, 
                saleDetails.amount)) {
            if (context.disablePartialFill) {
                revert PaymentProcessor__DispensingTokenWasUnsuccessful();
            } 
            else {
                // Nonce or some amount of fillable quantity was burned during order validation, but the item failed to 
                // fill.  Since partial fills are allowed, restore the nonce because we can't make assumptions about the
                // reason the fill failed. Order books should re-simulate there order for removal from order book if it 
                // failed because a fill is impossible.
                if (saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
                    _restoreFillableItems(saleDetails.maker, context.orderDigest, saleDetails.amount);
                } else {
                    _restoreNonce(saleDetails.maker, saleDetails.nonce);
                }
            }
        } else {
            SplitProceeds memory proceeds =
                _computePaymentSplits(
                    context,
                    saleDetails.itemPrice,
                    saleDetails.tokenAddress,
                    saleDetails.tokenId,
                    saleDetails.marketplace,
                    saleDetails.marketplaceFeeNumerator,
                    saleDetails.maxRoyaltyFeeNumerator,
                    saleDetails.fallbackRoyaltyRecipient
                );

            uint256 feeOnTopAmount;
            if (feeOnTop.recipient != address(0)) {
                feeOnTopAmount = feeOnTop.amount;
                
                if (feeOnTopAmount > saleDetails.itemPrice) {
                    revert PaymentProcessor__FeeOnTopCannotBeGreaterThanItemPrice();
                }
            }

            if (saleDetails.paymentMethod == address(0)) {
                unchecked {
                    uint256 nativeProceedsToSpend = saleDetails.itemPrice + feeOnTopAmount;
                    if (endingNativeFunds < nativeProceedsToSpend) {
                        revert PaymentProcessor__RanOutOfNativeFunds();
                    }

                    endingNativeFunds -= nativeProceedsToSpend;
                }
            }

            if (feeOnTopAmount > 0) {
                unchecked {
                    uint256 feeOnTopAmountInfrastructure = feeOnTopAmount * context.protocolFees.feeOnTopProtocolTaxBps / FEE_DENOMINATOR;
                    fnPointers.funcPayout(feeOnTop.recipient, context.taker, paymentCoin, feeOnTopAmount - feeOnTopAmountInfrastructure);
                    
                    if (purchaser == context.taker) {
                        proceeds.infrastructureProceeds += feeOnTopAmountInfrastructure;
                    } else {
                        if (feeOnTopAmountInfrastructure > 0) {
                            fnPointers.funcPayout(context.protocolFees.protocolFeeReceiver, context.taker, paymentCoin, feeOnTopAmountInfrastructure);
                        }
                    }
                }
            }

            if (proceeds.infrastructureProceeds > 0) {
                fnPointers.funcPayout(context.protocolFees.protocolFeeReceiver, purchaser, paymentCoin, proceeds.infrastructureProceeds);
            }

            if (proceeds.royaltyProceeds > 0) {
                fnPointers.funcPayout(proceeds.royaltyRecipient, purchaser, paymentCoin, proceeds.royaltyProceeds);
            }

            if (proceeds.marketplaceProceeds > 0) {
                fnPointers.funcPayout(saleDetails.marketplace, purchaser, paymentCoin, proceeds.marketplaceProceeds);
            }

            if (proceeds.sellerProceeds > 0) {
                fnPointers.funcPayout(seller, purchaser, paymentCoin, proceeds.sellerProceeds);
            }

            fnPointers.funcEmitOrderExecutionEvent(context, saleDetails);
        }
    }

    /**
     * @notice Validates a sweep order and fulfills the order.
     *
     * @dev    Throws when the order protocol is for ERC721 and the amount is not set to one.
     * @dev    Throws when the order protocol is for ERC1155 and the amount is set to zero.
     * @dev    Throws when an invalid order protocol is detected.
     * @dev    Throws when the marketplace fee and maximum royalty fee will exceed the sales price of an item.
     * @dev    Throws when the sale price is outside the pricing constraints.
     * @dev    Throws when the current block time is greater than the order expiration.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     * @dev    Throws when the taker did not supply enough native funds.
     *
     * @param context             The current execution context to determine the taker.
     * @param startingNativeFunds The amount of native funds available at the beginning of the order execution.
     * @param feeOnTop            The additional fee to add on top of the orders, paid by taker.
     * @param sweepOrder          The order information that is common to all items in the sweep.
     * @param items               An array of items that contains the order information unique to each item.
     * @param signedSellOrders    An array of maker signatures authorizing the order execution.
     * @param cosignatures        An array of additional cosignatures for cosigned orders, if applicable.
     * @param params              The sweep execution parameters.
     */
    function _validateAndFulfillSweepOrder(
        TradeContext memory context,
        uint256 startingNativeFunds,
        FeeOnTop calldata feeOnTop,
        SweepOrder calldata sweepOrder,
        SweepItem[] calldata items,
        SignatureECDSA[] memory signedSellOrders,
        Cosignature[] memory cosignatures,
        SweepExecutionParams memory params
    ) internal returns (uint256 endingNativeFunds) {
        endingNativeFunds = startingNativeFunds;

        uint256 sumListingPrices;
        uint256 dispensedPaymentValue;

        for(uint256 itemIndex;itemIndex < items.length;) {
            SweepItem calldata sweepItem = items[itemIndex];
            Order memory saleDetails = Order({
                protocol: sweepOrder.protocol,
                maker: sweepItem.maker,
                beneficiary: sweepOrder.beneficiary,
                marketplace: sweepItem.marketplace,
                fallbackRoyaltyRecipient: sweepItem.fallbackRoyaltyRecipient,
                paymentMethod: sweepOrder.paymentMethod,
                tokenAddress: sweepOrder.tokenAddress,
                tokenId: sweepItem.tokenId,
                amount: sweepItem.amount,
                itemPrice: uint240(sweepItem.itemPrice),
                nonce: sweepItem.nonce,
                expiration: sweepItem.expiration,
                marketplaceFeeNumerator: sweepItem.marketplaceFeeNumerator,
                maxRoyaltyFeeNumerator: sweepItem.maxRoyaltyFeeNumerator,
                requestedFillAmount: sweepItem.amount,
                minimumFillAmount: sweepItem.amount,
                protocolFeeVersion: sweepItem.protocolFeeVersion
            });

            if (context.protocolFeeVersion != saleDetails.protocolFeeVersion) {
                context.protocolFeeVersion = saleDetails.protocolFeeVersion;
                context.protocolFees = appStorage().protocolFeeVersions[saleDetails.protocolFeeVersion];
            }

            sumListingPrices += saleDetails.itemPrice;

            if (saleDetails.protocol == ORDER_PROTOCOLS_ERC721_FILL_OR_KILL) {
                if (saleDetails.amount != ONE) {
                    revert PaymentProcessor__AmountForERC721SalesMustEqualOne();
                }
            } else if (saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL) {
                if (saleDetails.amount == 0) {
                    revert PaymentProcessor__AmountForERC1155SalesGreaterThanZero();
                }
            } else {
                revert PaymentProcessor__InvalidOrderProtocol();
            }

            if (saleDetails.marketplaceFeeNumerator + saleDetails.maxRoyaltyFeeNumerator > FEE_DENOMINATOR) {
                revert PaymentProcessor__MarketplaceAndRoyaltyFeesWillExceedSalePrice();
            }

            if (params.paymentSettings == PaymentSettings.PricingConstraints || params.paymentSettings == PaymentSettings.PricingConstraintsCollectionOnly) {
                _validateSalePriceInRange(
                    params.paymentSettings,
                    saleDetails.tokenAddress, 
                    saleDetails.tokenId, 
                    saleDetails.amount, 
                    saleDetails.itemPrice);
            }

            if (block.timestamp > saleDetails.expiration) {
                    revert PaymentProcessor__OrderHasExpired();
            }

            if (block.timestamp > context.protocolFees.versionExpiration) {
                revert PaymentProcessor__ProtocolFeeVersionExpired();
            }

            if (context.protocolFees.minimumProtocolFeeBps > FEE_DENOMINATOR) {
                revert PaymentProcessor__ProtocolFeeOrTaxExceedsCap();
            }

            _verifySaleApproval(
                context,
                saleDetails,
                signedSellOrders[itemIndex],
                cosignatures[itemIndex]
            );

            (endingNativeFunds, dispensedPaymentValue) = _dispenseAndPayoutSweepItem(
                context,
                saleDetails, 
                params,
                endingNativeFunds,
                dispensedPaymentValue
            );

            unchecked {
                ++itemIndex;
            }
        }

        if (feeOnTop.amount > sumListingPrices) {
            revert PaymentProcessor__FeeOnTopCannotBeGreaterThanItemPrice();
        }

        endingNativeFunds = _finalizePaymentAccumulatorDisbursement(
            context,
            params,
            feeOnTop,
            dispensedPaymentValue,
            sumListingPrices,
            endingNativeFunds
        );
    }

    /**
     * @notice Finalizes the payment accumulator disbursement for a sweep order.
     * 
     * @dev    Throws if the taker did not supply enough native funds.
     *
     * @param context                The current execution context to determine the taker.
     * @param params                 The sweep execution parameters.
     * @param feeOnTop               The additional fee to add on top of the orders, paid by taker.
     * @param dispensedPaymentValue  The total value of dispensed payments.
     * @param sumListingPrices       The total value of all listings.
     * @param startingNativeFunds    The amount of native funds available at the beginning of the order execution.
     */
    function _finalizePaymentAccumulatorDisbursement(
        TradeContext memory context,
        SweepExecutionParams memory params,
        FeeOnTop calldata feeOnTop,
        uint256 dispensedPaymentValue,
        uint256 sumListingPrices,
        uint256 startingNativeFunds
    ) internal returns (uint256 endingNativeFunds) {
        endingNativeFunds = startingNativeFunds;

        if (feeOnTop.recipient != address(0)) {
            if (feeOnTop.amount > 0) {
                uint256 feeOnTopAmountApp;
                uint256 feeOnTopAmount = feeOnTop.amount * dispensedPaymentValue / sumListingPrices;
                uint256 feeOnTopAmountInfrastructure = feeOnTopAmount * context.protocolFees.feeOnTopProtocolTaxBps / FEE_DENOMINATOR;
                unchecked {
                    feeOnTopAmountApp = feeOnTopAmount - feeOnTopAmountInfrastructure;
                }
                
                if (address(params.paymentCoin) == address(0)) {
                    if (endingNativeFunds < feeOnTopAmount) {
                        revert PaymentProcessor__RanOutOfNativeFunds();
                    }
    
                    unchecked {
                        endingNativeFunds -= feeOnTopAmount;
                    }
                }

                params.fnPointers.funcPayout(
                    feeOnTop.recipient, 
                    context.taker, 
                    params.paymentCoin, 
                    feeOnTopAmountApp
                );

                unchecked {
                    params.accumulator.accumulatedInfrastructureProceeds += feeOnTopAmountInfrastructure;
                }
            }
        }

        if(params.accumulator.accumulatedInfrastructureProceeds > 0) {
            params.fnPointers.funcPayout(
                context.protocolFees.protocolFeeReceiver,
                context.taker, 
                params.paymentCoin, 
                params.accumulator.accumulatedInfrastructureProceeds
            );
        }

        if(params.accumulator.accumulatedRoyaltyProceeds > 0) {
            params.fnPointers.funcPayout(
                params.accumulator.lastRoyaltyRecipient, 
                context.taker, 
                params.paymentCoin, 
                params.accumulator.accumulatedRoyaltyProceeds
            );
        }

        if(params.accumulator.accumulatedMarketplaceProceeds > 0) {
            params.fnPointers.funcPayout(
                params.accumulator.lastMarketplace, 
                context.taker, 
                params.paymentCoin, 
                params.accumulator.accumulatedMarketplaceProceeds
            );
        }

        if(params.accumulator.accumulatedSellerProceeds > 0) {
            params.fnPointers.funcPayout(
                params.accumulator.lastSeller, 
                context.taker, 
                params.paymentCoin, 
                params.accumulator.accumulatedSellerProceeds
            );
        }
    }

    /**
     * @notice Dispenses tokens and proceeds for a single sweep item.
     *
     * @dev    Throws when the taker did not supply enough native funds.
     * @dev    If the token transfer fails, the nonce is restored.
     *
     * @param context              The current execution context to determine the taker.
     * @param saleDetails          The order execution details.
     * @param params               The sweep execution parameters.
     * @param startingNativeFunds  The amount of native funds available at the beginning of the order execution.
     */
    function _dispenseAndPayoutSweepItem(
        TradeContext memory context,
        Order memory saleDetails,
        SweepExecutionParams memory params,
        uint256 startingNativeFunds,
        uint256 startingDispensedPaymentValue
    ) internal returns(
        uint256 endingNativeFunds,
        uint256 endingDispensedPaymentValue
    ) {
        endingNativeFunds = startingNativeFunds;
        endingDispensedPaymentValue = startingDispensedPaymentValue;

        if (params.fnPointers.funcDispenseToken(
                saleDetails.maker, 
                saleDetails.beneficiary, 
                saleDetails.tokenAddress, 
                saleDetails.tokenId, 
                saleDetails.amount)) {
            SplitProceeds memory proceeds =
                _computePaymentSplits(
                    context,
                    saleDetails.itemPrice,
                    saleDetails.tokenAddress,
                    saleDetails.tokenId,
                    saleDetails.marketplace,
                    saleDetails.marketplaceFeeNumerator,
                    saleDetails.maxRoyaltyFeeNumerator,
                    saleDetails.fallbackRoyaltyRecipient
                );

            unchecked {
                endingDispensedPaymentValue += saleDetails.itemPrice;
            }

            if (saleDetails.paymentMethod == address(0)) {
                if (endingNativeFunds < saleDetails.itemPrice) {
                    revert PaymentProcessor__RanOutOfNativeFunds();
                }

                unchecked {
                    endingNativeFunds -= saleDetails.itemPrice;
                }
            }

            if (proceeds.royaltyRecipient != params.accumulator.lastRoyaltyRecipient) {
                if(params.accumulator.accumulatedRoyaltyProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastRoyaltyRecipient, context.taker, params.paymentCoin, params.accumulator.accumulatedRoyaltyProceeds);
                }

                params.accumulator.lastRoyaltyRecipient = proceeds.royaltyRecipient;
                params.accumulator.accumulatedRoyaltyProceeds = 0;
            }

            if (saleDetails.marketplace != params.accumulator.lastMarketplace) {
                if(params.accumulator.accumulatedMarketplaceProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastMarketplace, context.taker, params.paymentCoin, params.accumulator.accumulatedMarketplaceProceeds);
                }

                params.accumulator.lastMarketplace = saleDetails.marketplace;
                params.accumulator.accumulatedMarketplaceProceeds = 0;
            }

            if (saleDetails.maker != params.accumulator.lastSeller) {
                if(params.accumulator.accumulatedSellerProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastSeller, context.taker, params.paymentCoin, params.accumulator.accumulatedSellerProceeds);
                }

                params.accumulator.lastSeller = saleDetails.maker;
                params.accumulator.accumulatedSellerProceeds = 0;
            }

            if (context.protocolFees.protocolFeeReceiver != params.accumulator.lastProtocolFeeRecipient) {
                if (params.accumulator.accumulatedInfrastructureProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastProtocolFeeRecipient, context.taker, params.paymentCoin, params.accumulator.accumulatedInfrastructureProceeds);
                }

                params.accumulator.lastProtocolFeeRecipient = context.protocolFees.protocolFeeReceiver;
                params.accumulator.accumulatedInfrastructureProceeds = 0;
            }

            unchecked {
                params.accumulator.accumulatedRoyaltyProceeds += proceeds.royaltyProceeds;
                params.accumulator.accumulatedMarketplaceProceeds += proceeds.marketplaceProceeds;
                params.accumulator.accumulatedSellerProceeds += proceeds.sellerProceeds;
                params.accumulator.accumulatedInfrastructureProceeds += proceeds.infrastructureProceeds;
            }

            params.fnPointers.funcEmitOrderExecutionEvent(context, saleDetails);
        } else {
            if (saleDetails.protocol != ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
                _restoreNonce(saleDetails.maker, saleDetails.nonce);
            }
        }
    }

    /**
     * @notice Calculates the payment splits between seller, creator and marketplace based
     * @notice on on-chain royalty information or backfilled royalty information if on-chain
     * @notice data is unavailable.
     * 
     * @dev    Throws when ERC2981 on-chain royalties are set to an amount greater than the 
     * @dev    maker signed maximum.
     * 
     * @param salePrice                The sale price for the token being sold.
     * @param tokenAddress             The contract address for the token being sold.
     * @param tokenId                  The token id for the token being sold.
     * @param marketplaceFeeRecipient  The address that will receive the marketplace fee. 
     *                                 If zero, no marketplace fee will be applied.
     * @param marketplaceFeeNumerator  The fee numerator for calculating marketplace fees.
     * @param maxRoyaltyFeeNumerator  The maximum royalty fee authorized by the order maker.
     * @param fallbackRoyaltyRecipient The address that will receive royalties if not defined onchain.
     * @param context                  The royalty backfill and bounty information set onchain by the creator.
     *
     * @return proceeds A struct containing the split of payment and receiving addresses for the
     *                  seller, creator and marketplace.
     */
    function _computePaymentSplits(
        TradeContext memory context,
        uint256 salePrice,
        address tokenAddress,
        uint256 tokenId,
        address marketplaceFeeRecipient,
        uint256 marketplaceFeeNumerator,
        uint256 maxRoyaltyFeeNumerator,
        address fallbackRoyaltyRecipient
    ) internal view returns (SplitProceeds memory proceeds) {

        proceeds.sellerProceeds = salePrice;

        bool useBackfill = context.useRoyaltyBackfillAsRoyaltySource;

        address royaltyReceiver;
        uint256 royaltyAmount;
        if(!useBackfill) {
            (
                royaltyReceiver, 
                royaltyAmount, 
                useBackfill
            ) = _safeRoyaltyInfo(tokenAddress, tokenId, salePrice);

            if (royaltyAmount == 0) {
                useBackfill = true;
            }
        }
        unchecked {
            if (useBackfill) {
                // If the token doesn't implement the royaltyInfo function or creator has set
                // the use backfill royalty flag, then check if there are backfilled royalties.
                if (context.backfillReceiver != address(0)) {
                    if (context.backfillNumerator > maxRoyaltyFeeNumerator) {
                        revert PaymentProcessor__OnchainRoyaltiesExceedMaximumApprovedRoyaltyFee();
                    }
        
                    proceeds.royaltyRecipient = context.backfillReceiver;
                    proceeds.royaltyProceeds = 
                        (salePrice * context.backfillNumerator) / FEE_DENOMINATOR;
        
                    proceeds.sellerProceeds -= proceeds.royaltyProceeds;
                } else if (fallbackRoyaltyRecipient != address(0)) {
                    proceeds.royaltyRecipient = fallbackRoyaltyRecipient;
                    proceeds.royaltyProceeds = (salePrice * maxRoyaltyFeeNumerator) / FEE_DENOMINATOR;
        
                    proceeds.sellerProceeds -= proceeds.royaltyProceeds;
                }
            } else {
                if (royaltyReceiver == address(0)) {
                    royaltyAmount = 0;
                }
        
                if (royaltyAmount > 0) {
                    if (royaltyAmount > (salePrice * maxRoyaltyFeeNumerator) / FEE_DENOMINATOR) {
                        revert PaymentProcessor__OnchainRoyaltiesExceedMaximumApprovedRoyaltyFee();
                    }
        
                    proceeds.royaltyRecipient = royaltyReceiver;
                    proceeds.royaltyProceeds = royaltyAmount;
        
                    proceeds.sellerProceeds -= royaltyAmount;
                }
            }

            if (marketplaceFeeRecipient != address(0)) {
                proceeds.marketplaceProceeds = (salePrice * marketplaceFeeNumerator) / FEE_DENOMINATOR;
                proceeds.sellerProceeds -= proceeds.marketplaceProceeds;

                if (context.bountyNumerator > 0) {
                    if (context.exclusiveMarketplace == address(0) || 
                        context.exclusiveMarketplace == marketplaceFeeRecipient) {
                        uint256 royaltyBountyProceeds = 
                            proceeds.royaltyProceeds * context.bountyNumerator / FEE_DENOMINATOR;
                    
                        if (royaltyBountyProceeds > 0) {
                            proceeds.royaltyProceeds -= royaltyBountyProceeds;
                            proceeds.marketplaceProceeds += royaltyBountyProceeds;
                        }
                    }
                }

                proceeds.infrastructureProceeds = 
                    proceeds.marketplaceProceeds * context.protocolFees.marketplaceFeeProtocolTaxBps / FEE_DENOMINATOR;
                proceeds.marketplaceProceeds -= proceeds.infrastructureProceeds;
            }

            uint256 minInfrastructureFee = (salePrice * context.protocolFees.minimumProtocolFeeBps) / FEE_DENOMINATOR;
            if (proceeds.infrastructureProceeds < minInfrastructureFee) {
                uint256 shortfall = minInfrastructureFee - proceeds.infrastructureProceeds;
                if (shortfall > proceeds.sellerProceeds) {
                    proceeds.infrastructureProceeds += proceeds.sellerProceeds;
                    proceeds.sellerProceeds = 0;
                } else {
                    proceeds.sellerProceeds -= shortfall;
                    proceeds.infrastructureProceeds = minInfrastructureFee;
                }
            }
        }
    }

    /**
     * @notice Transfers chain native token to `to`.
     * 
     * @dev    Throws when the native token transfer call reverts.
     *
     * @param to                   The address that will receive chain native tokens.
     * @param proceeds             The amount of chain native token value to transfer.
     */
    function _pushProceeds(address to, uint256 proceeds) internal {
        bool success;

        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, proceeds, 0, 0, 0, 0)
        }

        if (!success) {
            revert PaymentProcessor__FailedToTransferProceeds();
        }
    }

    /**
     * @notice Refunds the taker any unspent native funds.
     * 
     * @param remainingNativeFunds The amount of native funds that were not spent.
     * @param taker                The address that will receive the unspent native funds.
     */
    function _refundUnspentNativeFunds(uint256 remainingNativeFunds, address taker) internal {
        if (remainingNativeFunds > 0) {
            _pushProceeds(taker, remainingNativeFunds);
        }
    }

    /**
     * @notice Transfers chain native token to `payee`.
     * 
     * @dev    Throws when the native token transfer call reverts.
     *
     * @param payee     The address that will receive chain native tokens.
     * @param proceeds  The amount of chain native token value to transfer.
     */
    function _payoutNativeCurrency(
        address payee, 
        address /*payer*/, 
        address /*paymentCoin*/, 
        uint256 proceeds) internal {
        _pushProceeds(payee, proceeds);
    }

    /**
     * @notice Transfers ERC20 tokens to from `payer` to `payee`.
     * 
     * @dev    Throws when the ERC20 transfer call reverts.
     *
     * @param payee The address that will receive ERC20 tokens.
     * @param payer The address the ERC20 tokens will be sent from.
     * @param paymentCoin The ERC20 token being transferred.
     * @param proceeds The amount of token value to transfer.
     */
    function _payoutCoinCurrency(
        address payee, 
        address payer, 
        address paymentCoin, 
        uint256 proceeds) internal {
        if (SafeERC20.safeTransferFrom(paymentCoin, payer, payee, proceeds)) {
            revert PaymentProcessor__FailedToTransferProceeds();
        }
    }

    /**
     * @notice Calls the token contract to transfer an ERC721 token from the seller to the buyer.
     * 
     * @dev    This will **NOT** throw if the transfer fails. It will instead return false
     * @dev    so that the calling function can handle the failed transfer.
     * @dev    Returns true if the transfer does not revert.
     * 
     * @param from         The seller of the token.
     * @param to           The beneficiary of the order execution.
     * @param tokenAddress The contract address for the token being transferred.
     * @param tokenId      The token id for the order.
     */
    function _dispenseERC721Token(
        address from, 
        address to, 
        address tokenAddress, 
        uint256 tokenId, 
        uint256 /*amount*/) internal returns (bool) {
        try IERC721(tokenAddress).transferFrom(from, to, tokenId) {
            return true;
        } catch {
            return false;
        }
    }

    /**
     * @notice Calls the token contract to transfer an ERC1155 token from the seller to the buyer.
     * 
     * @dev    This will **NOT** throw if the transfer fails. It will instead return false
     * @dev    so that the calling function can handle the failed transfer.
     * @dev    Returns true if the transfer does not revert.
     * 
     * @param from         The seller of the token.
     * @param to           The beneficiary of the order execution.
     * @param tokenAddress The contract address for the token being transferred.
     * @param tokenId      The token id for the order.
     * @param amount       The quantity of the token to transfer.
     */
    function _dispenseERC1155Token(
        address from, 
        address to, 
        address tokenAddress, 
        uint256 tokenId, 
        uint256 amount) internal returns (bool) {
        try IERC1155(tokenAddress).safeTransferFrom(from, to, tokenId, amount, "") {
            return true;
        } catch {
            return false;
        }
    }

    /**
     * @notice Emits a a BuyListingERC721 event.
     * 
     * @param context The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     */
    function _emitBuyListingERC721Event(TradeContext memory context, Order memory saleDetails) internal {
        emit BuyListingERC721(
                context.taker,
                saleDetails.maker,
                saleDetails.tokenAddress,
                saleDetails.beneficiary,
                saleDetails.paymentMethod,
                saleDetails.tokenId,
                saleDetails.itemPrice);
    }

    /**
     * @notice Emits a BuyListingERC1155 event.
     * 
     * @param context The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     */
    function _emitBuyListingERC1155Event(TradeContext memory context, Order memory saleDetails) internal {
        emit BuyListingERC1155(
                context.taker,
                saleDetails.maker,
                saleDetails.tokenAddress,
                saleDetails.beneficiary,
                saleDetails.paymentMethod,
                saleDetails.tokenId,
                saleDetails.amount,
                saleDetails.itemPrice);
    }

    /**
     * @notice Emits an AcceptOfferERC721 event.
     * 
     * @param context The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     */
    function _emitAcceptOfferERC721Event(TradeContext memory context, Order memory saleDetails) internal {
        emit AcceptOfferERC721(
                context.taker,
                saleDetails.maker,
                saleDetails.tokenAddress,
                saleDetails.beneficiary,
                saleDetails.paymentMethod,
                saleDetails.tokenId,
                saleDetails.itemPrice);
    }

    /**
     * @notice Emits an AcceptOfferERC1155 event.
     * 
     * @param context The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     */
    function _emitAcceptOfferERC1155Event(TradeContext memory context, Order memory saleDetails) internal {
        emit AcceptOfferERC1155(
                context.taker,
                saleDetails.maker,
                saleDetails.tokenAddress,
                saleDetails.beneficiary,
                saleDetails.paymentMethod,
                saleDetails.tokenId,
                saleDetails.amount,
                saleDetails.itemPrice);
    }

    /**
     * @notice Returns the appropriate function pointers for payouts, dispensing tokens and event emissions.
     *
     * @param side The taker's side of the order.
     * @param paymentMethod The payment method for the order. If address zero, the chain native token.
     * @param orderProtocol The type of token and fill method for the order.
     */
    function _getOrderFulfillmentFunctionPointers(
        Sides side,
        address paymentMethod,
        uint256 orderProtocol
    ) internal pure returns (FulfillOrderFunctionPointers memory orderFulfillmentFunctionPointers) {
        orderFulfillmentFunctionPointers = FulfillOrderFunctionPointers({
            funcPayout: paymentMethod == address(0) ? _payoutNativeCurrency : _payoutCoinCurrency,
            funcDispenseToken: orderProtocol == ORDER_PROTOCOLS_ERC721_FILL_OR_KILL ? _dispenseERC721Token : _dispenseERC1155Token,
            funcEmitOrderExecutionEvent: orderProtocol == ORDER_PROTOCOLS_ERC721_FILL_OR_KILL ? 
                (side == Sides.Buy ? _emitBuyListingERC721Event : _emitAcceptOfferERC721Event) : 
                (side == Sides.Buy ?_emitBuyListingERC1155Event : _emitAcceptOfferERC1155Event)
        });
    }

    /*************************************************************************/
    /*                        Signature Verification                         */
    /*************************************************************************/

    /**
     * @notice Updates the remaining fillable amount and order status for partially fillable orders.
     * @notice Performs checks for minimum fillable amount and order status.
     *
     * @dev    Throws when the remaining fillable amount is less than the minimum fillable amount requested.
     * @dev    Throws when the order status is not open.
     *
     * @param account             The maker account for the order.
     * @param orderDigest         The hash digest of the order execution details.
     * @param orderStartAmount    The original amount for the partially fillable order.
     * @param requestedFillAmount The amount the taker is requesting to fill.
     * @param minimumFillAmount   The minimum amount the taker is willing to fill.
     *
     * @return quantityToFill     Lesser of remainingFillableAmount and requestedFillAmount.
     */
    function _checkAndUpdateRemainingFillableItems(
        address account,
        bytes32 orderDigest, 
        uint256 orderStartAmount,
        uint256 requestedFillAmount,
        uint256 minimumFillAmount
    ) internal returns (uint256 quantityToFill) {
        if(orderStartAmount > type(uint248).max) {
            revert PaymentProcessor__AmountExceedsMaximum();
        }

        quantityToFill = requestedFillAmount;
        PartiallyFillableOrderStatus storage partialFillStatus = 
            appStorage().partiallyFillableOrderStatuses[account][orderDigest];
    
        if (partialFillStatus.state == PartiallyFillableOrderState.Open) {
            if (partialFillStatus.remainingFillableQuantity == 0) {
                partialFillStatus.remainingFillableQuantity = uint248(orderStartAmount);
                emit OrderDigestOpened(orderDigest, account, orderStartAmount);
            }

            if (quantityToFill > partialFillStatus.remainingFillableQuantity) {
                quantityToFill = partialFillStatus.remainingFillableQuantity;
            }

            if (quantityToFill < minimumFillAmount) {
                revert PaymentProcessor__UnableToFillMinimumRequestedQuantity();
            }

            unchecked {
                partialFillStatus.remainingFillableQuantity -= uint248(quantityToFill);
                emit OrderDigestItemsFilled(orderDigest, account, quantityToFill);
            }

            if (partialFillStatus.remainingFillableQuantity == 0) {
                partialFillStatus.state = PartiallyFillableOrderState.Filled;
                emit OrderDigestInvalidated(orderDigest, account, false);
            }
        } else {
            revert PaymentProcessor__OrderIsEitherCancelledOrFilled();
        }
    }

    /**
     * @notice Restored items to a partially fillable order when the items failed to dispense in a bulk order fill.
     *
     * @param account             The maker account for the order.
     * @param orderDigest         The hash digest of the order execution details.
     * @param unfilledAmount      The amount that was subtracted from the order that needs to be added back.
     *
     */
    function _restoreFillableItems(address account, bytes32 orderDigest, uint256 unfilledAmount) internal {
        if (unfilledAmount > 0) {
            PartiallyFillableOrderStatus storage partialFillStatus = 
                appStorage().partiallyFillableOrderStatuses[account][orderDigest];

            unchecked {
                partialFillStatus.remainingFillableQuantity += uint248(unfilledAmount);
            }

            partialFillStatus.state = PartiallyFillableOrderState.Open;

            emit OrderDigestItemsRestored(orderDigest, account, unfilledAmount);
        }
    }

    /**
     * @notice Invalidates a maker's nonce and emits a NonceInvalidated event.
     * 
     * @dev    Throws when the nonce has already been invalidated.
     * 
     * @param account         The maker account to invalidate `nonce` of.
     * @param nonce           The nonce to invalidate.
     * @param wasCancellation If true, the invalidation is the maker cancelling the nonce. 
     *                        If false, from the nonce being used to execute an order.
     */
    function _checkAndInvalidateNonce(
        address account, 
        uint256 nonce, 
        bool wasCancellation) internal returns (uint256) {

        // The following code is equivalent to, but saves 115 gas units:
        // 
        // mapping(uint256 => uint256) storage ptrInvalidatedSignatureBitmap = 
        //     appStorage().invalidatedSignatures[account];

        // unchecked {
        //     uint256 slot = nonce / 256;
        //     uint256 offset = nonce % 256;
        //     uint256 slotValue = ptrInvalidatedSignatureBitmap[slot];
        // 
        //     if (((slotValue >> offset) & ONE) == ONE) {
        //         revert PaymentProcessor__SignatureAlreadyUsedOrRevoked();
        //     }
        // 
        //     ptrInvalidatedSignatureBitmap[slot] = (slotValue | ONE << offset);
        // }

        unchecked {
            if (uint256(appStorage().invalidatedSignatures[account][uint248(nonce >> 8)] ^= (ONE << uint8(nonce))) & 
                (ONE << uint8(nonce)) == ZERO) {
                revert PaymentProcessor__SignatureAlreadyUsedOrRevoked();
            }
        }

        emit NonceInvalidated(nonce, account, wasCancellation);

        return appStorage().masterNonces[account];
    }

    /**
     * @notice After a maker's nonce is invalidated, should the order item fail to transfer
     *         we need to restore the nonce so order cannot be griefed.  Emits a NonceRestored event.
     * 
     * @dev    Throws when the nonce has already been invalidated.
     * 
     * @param account         The maker account to restore `nonce` of.
     * @param nonce           The nonce to restore.
     */
    function _restoreNonce(address account, uint256 nonce) internal {
        unchecked {
            if (uint256(appStorage().invalidatedSignatures[account][uint248(nonce >> 8)] ^= (ONE << uint8(nonce))) & 
                (ONE << uint8(nonce)) != ZERO) {
                revert PaymentProcessor__SignatureNotUsedOrRevoked();
            }
        }

        emit NonceRestored(nonce, account);
    }

    /**
     * @notice Updates the state of a maker's order to cancelled and remaining fillable quantity to zero.
     *
     * @dev    Throws when the current order state is not open.
     *
     * @param account     The maker account to invalid the order for.
     * @param orderDigest The hash digest of the order to invalidate.
     */
    function _revokeOrderDigest(address account, bytes32 orderDigest) internal {
        PartiallyFillableOrderStatus storage partialFillStatus = 
            appStorage().partiallyFillableOrderStatuses[account][orderDigest];
    
        if (partialFillStatus.state == PartiallyFillableOrderState.Open) {
            partialFillStatus.state = PartiallyFillableOrderState.Cancelled;
            partialFillStatus.remainingFillableQuantity = 0;
            emit OrderDigestInvalidated(orderDigest, account, true);
        } else {
            revert PaymentProcessor__OrderIsEitherCancelledOrFilled();
        }
    }

    /**
     * @notice Invalidates a maker's nonce and emits a NonceInvalidated event.
     *
     * @dev    Skips nonce invalidation for partial fillable orders.
     * @dev    Throws when the nonce has already been invalidated.
     *
     * @param saleDetails The order execution details.
     */
    function _checkAndInvalidateNonceForFillOrKillOrders(
        Order memory saleDetails
    ) internal returns (uint256 masterNonce) {
        masterNonce = 
            saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL ? 
                appStorage().masterNonces[saleDetails.maker] :
                _checkAndInvalidateNonce(saleDetails.maker, saleDetails.nonce, false);
    }

    /**
     * Verifies offer is approved by the maker.
     *
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     * 
     * @param offerType               The type of offer to execute.
     * @param context                 The current execution context to determine the taker.
     * @param saleDetails             The order execution details.
     * @param signature               The order maker's signature.
     * @param cosignature             The cosignature from the order cosigner, if applicable.
     * @param tokenSetProof           The token set proof that contains the root hash for the merkle
     *                                tree of allowed tokens for accepting the maker's offer.
     */
    function _verifyBidOrderSignatures(
        uint256 offerType,
        TradeContext memory context,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        TokenSetProof calldata tokenSetProof
    ) internal returns (uint256 quantityToFill) {
        if (offerType == OFFER_TYPE_COLLECTION_OFFER) {
            quantityToFill = _verifyCollectionOffer(
                context, 
                saleDetails, 
                signature, 
                cosignature);
        } else if (offerType == OFFER_TYPE_ITEM_OFFER) {
            quantityToFill = _verifyItemOffer(
                context,
                saleDetails, 
                signature, 
                cosignature);
        } else if (offerType == OFFER_TYPE_TOKEN_SET_OFFER) {
            quantityToFill = _verifyTokenSetOffer(
                context, 
                saleDetails, 
                signature, 
                cosignature,
                tokenSetProof);
        } else {
            revert PaymentProcessor__InvalidOfferType();
        }
    }

    /**
     * @notice Verifies a token offer is approved by the maker.
     *
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     *
     * @param context       The current execution context to determine the taker.
     * @param saleDetails   The order execution details.
     * @param signature     The order maker's signature.
     * @param cosignature   The cosignature from the order cosigner, if applicable.
     * 
     * @return quantityToFill The amount of the token that will be filled for this order.
     */
    function _verifyItemOffer(
        TradeContext memory context, 
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature
    ) internal returns (uint256 quantityToFill) {
        quantityToFill = _verifyOrderSignatures(
            context, 
            saleDetails, 
            signature, 
            cosignature, 
            _generateItemOfferOrderHash(
                saleDetails,
                saleDetails.maker,
                cosignature.signer,
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            )
        );
    }

    /**
     * @notice Verifies a collection offer is approved by the maker.
     *
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     *
     * @param context       The current execution context to determine the taker.
     * @param saleDetails   The order execution details.
     * @param signature     The order maker's signature.
     * @param cosignature   The cosignature from the order cosigner, if applicable.
     * 
     * @return quantityToFill The amount of the token that will be filled for this order.
     */
    function _verifyCollectionOffer(
        TradeContext memory context, 
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature
    ) internal returns (uint256 quantityToFill) {
        quantityToFill = _verifyOrderSignatures(
            context, 
            saleDetails, 
            signature, 
            cosignature, 
            _generateCollectionOfferOrderHash(
                saleDetails,
                saleDetails.maker,
                cosignature.signer,
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            )
        );
    }

    /**
     * @notice Verifies a token set offer is approved by the maker.
     *
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     *
     * @param context       The current execution context to determine the taker.
     * @param saleDetails   The order execution details.
     * @param signature     The order maker's signature.
     * @param tokenSetProof The token set proof that contains the root hash for the merkle  
     *                      tree of allowed tokens for accepting the maker's offer.
     * @param cosignature   The cosignature from the order cosigner, if applicable.
     * 
     * @return quantityToFill The amount of the token that will be filled for this order.
     */
    function _verifyTokenSetOffer(
        TradeContext memory context, 
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        TokenSetProof calldata tokenSetProof
    ) internal returns (uint256 quantityToFill) {
        bytes32 orderHash = 
            _generateTokenSetOfferOrderHash(
                tokenSetProof,
                saleDetails,
                saleDetails.maker,
                cosignature.signer,
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            );

        quantityToFill = _verifyOrderSignatures(
            context, 
            saleDetails, 
            signature, 
            cosignature, 
            orderHash
        );
    }

    /**
     * @notice Verifies a listing is approved by the maker.
     *
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     *
     * @param context     The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     * @param signature   The order maker's signature.
     * @param cosignature The cosignature from the order cosigner, if applicable.
     * 
     * @return quantityToFill The amount of the token that will be filled for this order.
     */
    function _verifySaleApproval(
        TradeContext memory context, 
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature
    ) internal returns (uint256 quantityToFill) {
        quantityToFill = _verifyOrderSignatures(
            context, 
            saleDetails, 
            signature, 
            cosignature, 
            _generateListingOrderHash(
                saleDetails,
                saleDetails.maker,
                cosignature.signer,
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            )
        );
    }

    /**
     * @notice Verifies a listing is approved by the maker.
     *
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or
     * @dev    cannot be filled with the minimum fillable amount.
     *
     * @param context     The current execution context to determine the taker.
     * @param saleDetails The order execution details.
     * @param signature   The order maker's signature.
     * @param cosignature The cosignature from the order cosigner, if applicable.
     * @param orderHash   The hash digest of the order execution details.
     */
    function _verifyOrderSignatures(
        TradeContext memory context,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        bytes32 orderHash
    ) internal returns (uint256 quantityToFill) {
        if (cosignature.signer != address(0)) {
            _verifyCosignature(context, signature, cosignature);
        }

         bytes32 orderDigest = _hashTypedDataV4(_cachedDomainSeparator, orderHash);

        _verifyMakerSignature(saleDetails.maker, signature, orderDigest);

        if (saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
            quantityToFill = _checkAndUpdateRemainingFillableItems(
                saleDetails.maker, 
                orderHash, 
                saleDetails.amount, 
                saleDetails.requestedFillAmount,
                saleDetails.minimumFillAmount);
            context.orderDigest = orderHash;
        } else {
            quantityToFill = saleDetails.amount;
        }
    }

    /**
     * @notice Reverts a transaction when the recovered signer is not the order maker.
     *
     * @dev Throws when the recovered signer for the `signature` and `digest` does not match the order maker AND
     * @dev - The maker address does not have deployed code, OR 
     * @dev - The maker contract does not return the correct ERC1271 value to validate the signature.
     *
     * @param maker The adress for the order maker.
     * @param signature The order maker's signature.
     * @param digest The hash digest of the order.
     */
    function _verifyMakerSignature(address maker, SignatureECDSA memory signature, bytes32 digest ) internal view {
        (bool isError, address signer) = _tryEcdsaRecover(digest, signature.v, signature.r, signature.s);
        if (maker != signer || isError) {
            if (maker.code.length > 0) {
                _verifyEIP1271Signature(maker, digest, signature);
            } else {
                revert PaymentProcessor__UnauthorizedOrder();
            }
        }
    }

    /**
     * @notice Reverts the transaction when a supplied cosignature is not valid.
     *
     * @dev    Throws when the current block timestamp is greater than the cosignature expiration.
     * @dev    Throws when the order taker does not match the cosignature taker.
     * @dev    Throws when the cosigner has self-destructed their account.
     * @dev    Throws when the recovered address for the cosignature does not match the cosigner address.
     * 
     * @param context     The current execution context to determine the order taker.
     * @param signature   The order maker's signature.
     * @param cosignature The cosignature from the order cosigner.
     */
    function _verifyCosignature(
        TradeContext memory context, 
        SignatureECDSA memory signature, 
        Cosignature memory cosignature
    ) internal view {
        if (block.timestamp > cosignature.expiration) {
            revert PaymentProcessor__CosignatureHasExpired();
        }

        if (context.taker != cosignature.taker) {
            revert PaymentProcessor__UnauthorizedTaker();
        }

        if (appStorage().destroyedCosigners[cosignature.signer]) {
            revert PaymentProcessor__CosignerHasSelfDestructed();
        }

        if (cosignature.signer != _ecdsaRecover(
            _hashTypedDataV4(
                _cachedDomainSeparator, 
                EfficientHash.efficientHash(
                    COSIGNATURE_HASH,
                    bytes32(signature.v),
                    signature.r,
                    signature.s,
                    bytes32(cosignature.expiration),
                    bytes32(uint256(uint160(cosignature.taker)))
                )
            ), 
            cosignature.v, 
            cosignature.r, 
            cosignature.s)) {
            revert PaymentProcessor__NotAuthorizedByCosigner();
        }
    }

    /**
     * @notice Reverts the transaction if the contract at `signer` does not return the ERC1271
     * @notice isValidSignature selector when called with `hash`.
     * 
     * @dev    Throws when attempting to verify a signature from an address that has deployed
     * @dev    contract code using ERC1271 and the contract does not return the isValidSignature
     * @dev    function selector as its return value.
     *
     * @param signer The signer address for a maker order that has deployed contract code.
     * @param hash The ERC712 hash value of the order.
     * @param signature The signature for the order hash.
     */
    function _verifyEIP1271Signature(
        address signer, 
        bytes32 hash, 
        SignatureECDSA memory signature
    ) internal view {
        if (!_safeIsValidSignature(signer, hash, signature.v, signature.r, signature.s)) {
            revert PaymentProcessor__EIP1271SignatureInvalid();
        }
    }

    /**
     * @notice A gas efficient, and fallback-safe way to call the isValidSignature function for EIP-1271.
     *
     * @param signer     The EIP-1271 signer to call to check for a valid signature.
     * @param hash       The hash digest to verify with the EIP-1271 signer.
     * @param v         The `v` value of the signature
     * @param r         The `r` value of the signature
     * @param s         The `s` value of the signature
     * 
     * @return isValid   True if the EIP-1271 signer returns the EIP-1271 magic value.
     */
    function _safeIsValidSignature(
        address signer,
        bytes32 hash,
        uint256 v,
        bytes32 r,
        bytes32 s
    ) private view returns(bool isValid) {
        if (v > type(uint8).max) {
            revert PaymentProcessor__InvalidSignatureV();
        }

        assembly {
            function _callIsValidSignature(_signer, _hash, _r, _s, _v) -> _isValid {
                let ptr := mload(0x40)
                // store isValidSignature(bytes32,bytes) selector
                mstore(ptr, hex"1626ba7e")
                // store bytes32 hash value in abi encoded location
                mstore(add(ptr, 0x04), _hash)
                // store abi encoded location of the bytes signature data
                mstore(add(ptr, 0x24), 0x40)
                // store bytes signature length
                mstore(add(ptr, 0x44), 0x41)
                // store the signature `_r`, `_v` and `_s` in memory
                // `_v` is stored before `_s` to avoid shifting
                mstore(add(ptr, 0x64), _r)
                mstore(add(ptr, 0x85), _v)
                mstore(add(ptr, 0x84), _s)
                // update free memory pointer, 0xC4 is the length of calldata
                mstore(0x40, add(ptr, 0xC4))

                // static call _signer with abi encoded data
                // skip return data check if call failed or return data size is not at least 32 bytes
                if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _signer, ptr, 0xC4, 0x00, 0x20)) {
                    // check if return data is equal to isValidSignature magic value
                    _isValid := eq(mload(0x00), hex"1626ba7e")
                    leave
                }
            }
            isValid := _callIsValidSignature(signer, hash, r, s, v)
        }
    }

    /**
     * @notice Recovers the signer address from a hash and signature.
     * 
     * @dev Throws when `v` is greater than `type(uint8).max`.
     * @dev Throws when the recovered address is zero.
     *
     * @param digest The hash digest that was signed.
     * @param v The v-value of the signature.
     * @param r The r-value of the signature.
     * @param s The s-value of the signature.
     *
     * @return signer The recovered signer address from the signature.
     */
    function _ecdsaRecover(
        bytes32 digest, 
        uint256 v, 
        bytes32 r, 
        bytes32 s
    ) internal pure returns (address signer) {
        bool isError;
        (isError, signer) = _tryEcdsaRecover(digest, v, r, s);
        if(isError) {
            revert PaymentProcessor__UnauthorizedOrder();
        }
    }

    /**
     * @notice Recovers the signer address from a hash and signature.
     * 
     * @dev     Does **NOT** revert if invalid input values are provided or `signer` is recovered as address(0)
     * @dev     Returns an `isError` value in those conditions that is handled upstream
     *
     * @param digest The hash digest that was signed.
     * @param v The v-value of the signature.
     * @param r The r-value of the signature.
     * @param s The s-value of the signature.
     *
     * @return isError  True if the ECDSA function is provided invalid inputs
     * @return signer   The recovered signer address from the signature.
     */
    function _tryEcdsaRecover(
        bytes32 digest, 
        uint256 v, 
        bytes32 r, 
        bytes32 s
    ) internal pure returns (bool isError, address signer) {
        if (v > type(uint8).max) {
            return (true, address(0));
        }

        signer = ecrecover(digest, uint8(v), r, s);
        isError = (signer == address(0));
    }

    /**
     * @notice Returns the EIP-712 hash digest for `domainSeparator` and `structHash`.
     * 
     * @param domainSeparator The domain separator for the EIP-712 hash.
     * @param structHash The hash of the EIP-712 struct.
     */
    function _hashTypedDataV4(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
        return ECDSA.toTypedDataHash(domainSeparator, structHash);
    }

    /*************************************************************************/
    /*                             Miscellaneous                             */
    /*************************************************************************/

    /**
     * @notice Pulls settings from the CollectionSettingsRegistry contract and updates the
     * @notice applicable cached collection settings in the PaymentProcessor contract.
     * 
     * @param tokenAddress The contract address for the token to update settings for.
     */
    function _syncCollectionSettings(address tokenAddress) internal {
        bytes32[] memory noExtensions;

        (
            CollectionRegistryPaymentSettings memory collectionCoreSettings,
            RegistryPricingBounds memory collectionPricingBounds,
            address constrainedPricingPaymentMethod,
            address exclusiveBountyReceiver,,
        ) = ICollectionSettingsRegistry(collectionSettingsRegistry).getCollectionSettings(tokenAddress, noExtensions, noExtensions);

        uint8 flags = uint8(collectionCoreSettings.extraData);

        appStorage().collectionPaymentSettings[tokenAddress] = CollectionPaymentSettings({
            initialized: true,
            paymentSettings: PaymentSettings(collectionCoreSettings.paymentSettingsType),
            paymentMethodWhitelistId: collectionCoreSettings.paymentMethodWhitelistId,
            royaltyBackfillReceiver: collectionCoreSettings.royaltyBackfillReceiver,
            royaltyBackfillNumerator: collectionCoreSettings.royaltyBackfillNumerator,
            royaltyBountyNumerator: collectionCoreSettings.royaltyBountyNumerator,
            flags: flags
        });

        if (PaymentSettings(collectionCoreSettings.paymentSettingsType) == PaymentSettings.PricingConstraints ||
            PaymentSettings(collectionCoreSettings.paymentSettingsType) == PaymentSettings.PricingConstraintsCollectionOnly) {
            appStorage().collectionConstrainedPricingPaymentMethods[tokenAddress] = constrainedPricingPaymentMethod;
            PricingBounds memory currentPricingBounds = appStorage().collectionPricingBounds[tokenAddress];
            
            if(!(currentPricingBounds.isSet == collectionPricingBounds.isSet &&
                currentPricingBounds.floorPrice == collectionPricingBounds.floorPrice &&
                currentPricingBounds.ceilingPrice == collectionPricingBounds.ceilingPrice)
            ) {
                appStorage().collectionPricingBounds[tokenAddress] = PricingBounds({
                    initialized: true,
                    isSet: collectionPricingBounds.isSet,
                    floorPrice: collectionPricingBounds.floorPrice,
                    ceilingPrice: collectionPricingBounds.ceilingPrice
                });

                emit UpdatedCollectionLevelPricingBoundaries(
                    tokenAddress, 
                    collectionPricingBounds.floorPrice, 
                    collectionPricingBounds.ceilingPrice);
            }
        }

        if (_isFlagSet(flags, FLAG_IS_ROYALTY_BOUNTY_EXCLUSIVE)) {
            appStorage().collectionExclusiveBountyReceivers[tokenAddress] = exclusiveBountyReceiver;
        }

        emit UpdatedCollectionPaymentSettings(
            tokenAddress, 
            PaymentSettings(collectionCoreSettings.paymentSettingsType), 
            collectionCoreSettings.paymentMethodWhitelistId,
            constrainedPricingPaymentMethod,
            collectionCoreSettings.royaltyBackfillNumerator,
            collectionCoreSettings.royaltyBackfillReceiver,
            collectionCoreSettings.royaltyBountyNumerator,
            exclusiveBountyReceiver,
            _isFlagSet(flags, FLAG_BLOCK_TRADES_FROM_UNTRUSTED_CHANNELS),
            _isFlagSet(flags, FLAG_USE_BACKFILL_AS_ROYALTY_SOURCE));
    }

    /**
     * @notice Reverts the transaction if the caller is not the CollectionSettingsRegistry or the contract itself.
     */
    function _requireCallerIsCollectionSettingsRegistryOrSelf() internal view {
        if(!(msg.sender == collectionSettingsRegistry || msg.sender == address(this))) {
            revert PaymentProcessor__CallerIsNotSettingsRegistryOrSelf();
        }
    }

    /**
     * @notice Returns true if the `flagValue` has the `flag` set, false otherwise.
     *
     * @dev    This function uses the bitwise AND operator to check if the `flag` is set in `flagValue`.
     *
     * @param flagValue  The value to check for the presence of the `flag`.
     * @param flag       The flag to check for in the `flagValue`.
     */
    function _isFlagSet(uint8 flagValue, uint8 flag) internal pure returns (bool flagSet) {
        flagSet = (flagValue & flag) != 0;
    }

    /**
     * @notice Sets the `flag` in `flagValue` to `flagSet` and returns the updated value.
     * 
     * @dev    This function uses the bitwise OR and AND operators to set or unset the `flag` in `flagValue`.
     *
     * @param flagValue The value to set the `flag` in.
     * @param flag      The flag to set in the `flagValue`.
     * @param flagSet   True to set the `flag`, false to unset the `flag`.
     */
    function _setFlag(uint8 flagValue, uint8 flag, bool flagSet) internal pure returns (uint8) {
        if(flagSet) {
            return (flagValue | flag);
        } else {
            unchecked {
                return (flagValue & (255 - flag));
            }
        }
    }

    /**
     * @notice A gas efficient, and fallback-safe way to call the royaltyInfo function on a token contract.
     *
     * @dev    This will get the royaltyInfo if it exists - and when the function is unimplemented, the
     * @dev    presence of a fallback function will not result in halted execution.
     * 
     * @param  tokenAddress The contract address of the token to check for royalties.
     * @param  tokenId      The token ID to check for royalties.
     * @param  salePrice    The sale price of the token.
     */
    function _safeRoyaltyInfo(
        address tokenAddress, 
        uint256 tokenId, 
        uint256 salePrice
    ) internal view returns(address receiver, uint256 royaltyAmount, bool isError) {
        assembly {
            function _callRoyaltyInfo(_tokenAddress, _tokenId, _salePrice) -> _receiver, _royaltyAmount, _isError {
                let ptr := mload(0x40)
                mstore(0x40, add(ptr, 0x60))
                mstore(ptr, 0x2a55205a)
                mstore(add(0x20, ptr), _tokenId)
                mstore(add(0x40, ptr), _salePrice)
                if and(iszero(lt(returndatasize(), 0x40)), staticcall(gas(), _tokenAddress, add(ptr, 0x1C), 0x44, 0x00, 0x40)) {
                    _receiver := mload(0x00)
                    _royaltyAmount := mload(0x20)
                    leave
                }
                _isError := true
            }
            receiver, royaltyAmount, isError := _callRoyaltyInfo(tokenAddress, tokenId, salePrice)
        }
    }

    /**
     * @notice A gas efficient, and fallback-safe way to call the owner function on a token contract.
     *
     * @dev    This will get the owner if it exists - and when the function is unimplemented, the
     * @dev    presence of a fallback function will not result in halted execution.
     *
     * @param tokenAddress The contract address of the token to check for the owner.
     */
    function _safeOwner(
        address tokenAddress
    ) internal view returns(address owner, bool isError) {
        assembly {
            function _callOwner(_tokenAddress) -> _owner, _isError {
                mstore(0x00, 0x8da5cb5b)
                if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _tokenAddress, 0x1C, 0x04, 0x00, 0x20)) {
                    _owner := mload(0x00)
                    leave
                }
                _isError := true
            }
            owner, isError := _callOwner(tokenAddress)
        }
    }
    
    /**
     * @notice A gas efficient, and fallback-safe way to call the hasRole function on a token contract.
     *
     * @dev    This will check if the account `hasRole` if `hasRole` exists - and when the function is unimplemented, the
     * @dev    presence of a fallback function will not result in halted execution.
     *
     * @param tokenAddress The contract address of the token to check for the role.
     * @param role         The role to check for.
     * @param account      The account to check for the role.
     */
    function _safeHasRole(
        address tokenAddress,
        bytes32 role,
        address account
    ) internal view returns(bool hasRole, bool isError) {
        assembly {
            function _callHasRole(_tokenAddress, _role, _account) -> _hasRole, _isError {
                let ptr := mload(0x40)
                mstore(0x40, add(ptr, 0x60))
                mstore(ptr, 0x91d14854)
                mstore(add(0x20, ptr), _role)
                mstore(add(0x40, ptr), _account)
                if and(iszero(lt(returndatasize(), 0x20)), staticcall(gas(), _tokenAddress, add(ptr, 0x1C), 0x44, 0x00, 0x20)) {
                    _hasRole := mload(0x00)
                    leave
                }
                _isError := true
            }
            hasRole, isError := _callHasRole(tokenAddress, role, account)
        }
    }

    
    /**
     * @notice Returns the taker for a transaction with trusted forwarder context if appended data length is 20 bytes.
     *
     * @dev    Throws when appended data length is not zero or 20 bytes.
     * 
     * @param appendedDataLength  The length of extra calldata sent with a transaction.
     * 
     * @return taker  The address of the taker for the transaction.
     */
    function _getTaker(
        uint256 appendedDataLength
    ) internal view returns(address taker) {
        taker = msg.sender;
        if (appendedDataLength > 0) {
            if (appendedDataLength != 20) revert PaymentProcessor__BadCalldataLength();
            taker = _msgSender();
        }
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./PaymentProcessorModulePermits.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title PaymentProcessorModuleAdvanced
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

abstract contract PaymentProcessorModuleAdvanced is PaymentProcessorModulePermits {
    using EnumerableSet for EnumerableSet.AddressSet;

    constructor(address configurationContract) PaymentProcessorModulePermits(configurationContract) {}

    /*************************************************************************/
    /*                            Order Execution                            */
    /*************************************************************************/

    /**
     * @notice Executes an advanced buy side order. An advanced order includes an optional permit context and optional bulk order proof.
     *
     * @dev    Throws when the permit processor is not trusted.
     * @dev    Throws when the permit processor is provided and the maker created the order by signing a merkle tree root.
     *
     * @param tradeContext         The execution context of the trade.
     * @param startingNativeFunds  The starting native funds.
     * @param advancedListing      Order details including the permit context, signatures and sale details.
     * @param bulkOrderProof       Optional bulk order proof.
     * @param feeOnTop             The additional fee to add on top of the orders, paid by taker.
     */
    function _executeOrderBuySideAdvanced(
        TradeContext memory tradeContext,
        uint256 startingNativeFunds,
        AdvancedOrder memory advancedListing,
        BulkOrderProof calldata bulkOrderProof,
        FeeOnTop calldata feeOnTop
    ) internal returns (uint256 endingNativeFunds) {
        if (advancedListing.permitContext.permitProcessor == address(0)) {
            if (bulkOrderProof.proof.length == 0) {
                endingNativeFunds = _executeOrderBuySide(
                    tradeContext,
                    startingNativeFunds,
                    advancedListing.saleDetails,
                    advancedListing.signature,
                    advancedListing.cosignature,
                    feeOnTop);
            } else {
                endingNativeFunds = _executeOrderBuySideBulk(
                    tradeContext,
                    startingNativeFunds,
                    advancedListing,
                    bulkOrderProof,
                    feeOnTop);
            }
        } else {
            if (bulkOrderProof.proof.length == 0) {
                if (!_isTrustedPermitProcessor(advancedListing.permitContext.permitProcessor)) {
                    revert PaymentProcessor__PermitProcessorNotTrusted();
                }
                advancedListing.saleDetails.itemPrice = uint240(advancedListing.saleDetails.itemPrice);
    
                endingNativeFunds = _validateAndFulfillSinglePermittedOrder(
                    startingNativeFunds,
                    Sides.Buy,
                    advancedListing,
                    tradeContext,
                    feeOnTop);
            } else {
                revert PaymentProcessor__PermitsAreNotCompatibleWithBulkOrders();
            }
        }
    }

    /**
     * @notice Verifies the proof and signature of an order that was generated by a signed merkle tree root
     * @notice and executes the order.
     *
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable 
     * @dev    amount is less than the minimum fillable amount requested.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker's signature is invalid.
     *
     * @param tradeContext         The execution context of the trade.
     * @param startingNativeFunds  The starting native funds.
     * @param advancedListing      Order details including the permit context, signatures and sale details.
     * @param bulkOrderProof       The bulk order proof and order index.
     * @param feeOnTop             The additional fee to add on top of the orders, paid by taker. 
     */
    function _executeOrderBuySideBulk(
        TradeContext memory tradeContext,
        uint256 startingNativeFunds,
        AdvancedOrder memory advancedListing,
        BulkOrderProof calldata bulkOrderProof,
        FeeOnTop calldata feeOnTop
    ) internal returns (uint256 endingNativeFunds) {
        Order memory saleDetails = advancedListing.saleDetails;
        saleDetails.itemPrice = uint240(saleDetails.itemPrice);

        uint256 quantityToFill = _verifyBulkSaleApprovalProofAndSignedTree(
                tradeContext,
                saleDetails,
                advancedListing.signature,
                advancedListing.cosignature,
                bulkOrderProof);

        endingNativeFunds = 
            _validateAndFulfillOrder(
                Sides.Buy, 
                tradeContext, 
                startingNativeFunds, 
                quantityToFill, 
                saleDetails, 
                feeOnTop);
    }

    /**
     * @notice Executes an advanced sell side order. An advanced order includes an optional permit context and optional bulk order proof.
     *
     * @dev    Throws when the permit processor is provided and is not trusted.
     * @dev    Throws when the permit processor is provided and the maker created the order by signing a merkle tree root.
     * @dev    Throws when the permit processor is set and the order is paid with the native token.
     * 
     * @param tradeContext         The execution context of the trade.
     * @param advancedBid          Order details including the permit context, signatures and sale details.
     * @param bulkOrderProof       Optional bulk order proof.
     * @param feeOnTop             The additional fee to add on top of the orders, paid by taker.
     */
    function _executeOrderSellSideAdvanced(
        TradeContext memory tradeContext,
        AdvancedBidOrder memory advancedBid,
        BulkOrderProof calldata bulkOrderProof,
        FeeOnTop calldata feeOnTop,
        TokenSetProof calldata tokenSetProof
    ) internal {
        if (advancedBid.advancedOrder.permitContext.permitProcessor == address(0)) {
            if (bulkOrderProof.proof.length == 0) { 
                _executeOrderSellSide(
                    tradeContext,
                    advancedBid.offerType,
                    advancedBid.advancedOrder.saleDetails,
                    advancedBid.advancedOrder.signature,
                    tokenSetProof,
                    advancedBid.advancedOrder.cosignature,
                    feeOnTop);
            } else {
                _executeOrderSellSideBulk(
                    tradeContext,
                    advancedBid,
                    bulkOrderProof,
                    feeOnTop,
                    tokenSetProof);
            }
        } else {
            if (bulkOrderProof.proof.length == 0) { 
                if (!_isTrustedPermitProcessor(advancedBid.advancedOrder.permitContext.permitProcessor)) {
                    revert PaymentProcessor__PermitProcessorNotTrusted();
                }
        
                if (advancedBid.advancedOrder.saleDetails.paymentMethod == address(0)) {
                    revert PaymentProcessor__BadPaymentMethod();
                }
                advancedBid.advancedOrder.saleDetails.itemPrice = uint240(advancedBid.advancedOrder.saleDetails.itemPrice);

                uint256 quantityToFill = 
                    _verifyBidOrderSignatures(
                        advancedBid.offerType, 
                        tradeContext, 
                        advancedBid.advancedOrder.saleDetails, 
                        advancedBid.advancedOrder.signature, 
                        advancedBid.advancedOrder.cosignature, 
                        tokenSetProof);

                _adjustForUnitPricing(quantityToFill, advancedBid.advancedOrder.saleDetails);
        
                advancedBid.advancedOrder.signature = advancedBid.sellerPermitSignature;
        
                _validateAndFulfillSinglePermittedOrder(0, Sides.Sell, advancedBid.advancedOrder, tradeContext, feeOnTop);
            } else {
                revert PaymentProcessor__PermitsAreNotCompatibleWithBulkOrders();
            }
        }
    }

    /**
     * @notice Verifies the proof and signature of an order that was generated by a signed merkle tree root
     * @notice and executes the order.
     *
     * @dev    Throws when the payment method is the native token.
     * @dev    Throws when the token set proof is provided and is invalid.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable
     * @dev    amount is less than the minimum fillable amount requested.
     * 
     * @param tradeContext         The execution context of the trade.
     * @param advancedBid          Order details including the permit context, signatures and sale details.
     * @param bulkOrderProof       The bulk order proof and order index.
     * @param feeOnTop             The additional fee to add on top of the orders, paid by taker.
     * @param tokenSetProof        The token set proof that contains the root hash for the merkle
     *                             tree of allowed tokens for accepting the maker's offer.
     */
    function _executeOrderSellSideBulk(
        TradeContext memory tradeContext,
        AdvancedBidOrder memory advancedBid,
        BulkOrderProof calldata bulkOrderProof,
        FeeOnTop calldata feeOnTop,
        TokenSetProof calldata tokenSetProof
    ) internal {
        Order memory saleDetails = advancedBid.advancedOrder.saleDetails;
        saleDetails.itemPrice = uint240(saleDetails.itemPrice);

        if (saleDetails.paymentMethod == address(0)) {
            revert PaymentProcessor__BadPaymentMethod();
        }

        uint256 quantityToFill;

        uint256 offerType = advancedBid.offerType;
        if (offerType == OFFER_TYPE_COLLECTION_OFFER) {
            quantityToFill = _verifyBulkCollectionOfferApprovalProofAndSignedTree(
                tradeContext, 
                saleDetails, 
                advancedBid.advancedOrder.signature, 
                advancedBid.advancedOrder.cosignature,
                bulkOrderProof);
        } else if (offerType == OFFER_TYPE_ITEM_OFFER) {
            quantityToFill = _verifyBulkItemOfferApprovalProofAndSignedTree(
                tradeContext,
                saleDetails, 
                advancedBid.advancedOrder.signature, 
                advancedBid.advancedOrder.cosignature,
                bulkOrderProof);
        } else if (offerType == OFFER_TYPE_TOKEN_SET_OFFER) {
            quantityToFill = _verifyBulkTokenSetOfferApprovalProofAndSignedTree(
                tradeContext, 
                saleDetails, 
                advancedBid.advancedOrder.signature, 
                tokenSetProof, 
                advancedBid.advancedOrder.cosignature,
                bulkOrderProof);
        } else {
            revert PaymentProcessor__InvalidOfferType();
        }

        _validateAndFulfillOrder(
            Sides.Sell, 
            tradeContext, 
            0, 
            quantityToFill, 
            saleDetails, 
            feeOnTop);
    }

    /**
     * @notice Executes a sweep order. A sweep order is a collection of items that are purchased in a single transaction.
     * 
     * @dev    Throws when the sweep order protocol is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL.
     * @dev    Throws when the sweep order items array is empty.
     * @dev    Throws when a collection is set to block untrusted channels and the transaction originates
     * @dev    from an untrusted channel.
     * @dev    Throws when the payment method is not an allowed payment method.
     * @dev    Throws when trading is paused for the collection.
     * @dev    Throws when any protocol is ORDER_PROTOCOLS_ERC721_FILL_OR_KILL and the amount is not 1.
     * @dev    Throws when any protocol is ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL and the amount is 0.
     * @dev    Throws when any protocol is not ORDER_PROTOCOLS_ERC721_FILL_OR_KILL or ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL.
     * @dev    Throws when the sum of the marketplace fee and the max royalty fee exceeds the fee denominator.
     * @dev    Throws when the sale price is not within the allowed price range.
     * @dev    Throws when the order has expired.
     * @dev    Throws when the fee on top is greater than the sum of the listing prices.
     * @dev    Throws when the payment method is not an allowed payment method.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     *
     * @param tradeContext         The execution context of the trade.
     * @param startingNativeFunds  The starting native funds.
     * @param advancedSweep        Order details including the permit context, signatures and sweep details.
     */
    function _executeSweepOrderAdvanced(
        TradeContext memory tradeContext,
        uint256 startingNativeFunds,
        AdvancedSweep calldata advancedSweep
    ) internal returns (uint256 endingNativeFunds) {
        if (advancedSweep.sweepOrder.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
            revert PaymentProcessor__OrderProtocolERC1155FillPartialUnsupportedInSweeps();
        }

        if (advancedSweep.items.length == 0) {
            revert PaymentProcessor__InputArrayLengthCannotBeZero();
        }

        SweepExecutionParams memory params = _validateSweepOrderHeader(tradeContext, advancedSweep.sweepOrder);

        endingNativeFunds = _validateAndFulfillSweepOrderAdvanced(
            startingNativeFunds,
            advancedSweep,
            tradeContext,
            params
        );
    }

    /**
     * @notice Validates and fulfills a sweep order.
     *
     * @dev    Throws when any protocol is ORDER_PROTOCOLS_ERC721_FILL_OR_KILL and the amount is not 1.
     * @dev    Throws when any protocol is ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL and the amount is 0.
     * @dev    Throws when any protocol is not ORDER_PROTOCOLS_ERC721_FILL_OR_KILL or ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL.
     * @dev    Throws when the sum of the marketplace fee and the max royalty fee exceeds the fee denominator.
     * @dev    Throws when the sale price is not within the allowed price range.
     * @dev    Throws when the order has expired.
     * @dev    Throws when the fee on top is greater than the sum of the listing prices.
     * @dev    Throws when the payment method is not an allowed payment method.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the maker signature is invalid.
     * @dev    Throws when the maker's order nonce has already been used or was cancelled.
     * @dev    Throws when a partially fillable order has already been filled, cancelled or 
     * @dev    cannot be filled with the minimum fillable amount.
     * 
     * @param startingNativeFunds  The starting native funds.
     * @param advancedSweep        Order details including the permit context, signatures and sweep details.
     * @param context              The current exectuion context.
     * @param params               The sweep execution parameters.
     */
    function _validateAndFulfillSweepOrderAdvanced(
        uint256 startingNativeFunds,
        AdvancedSweep calldata advancedSweep,
        TradeContext memory context,
        SweepExecutionParams memory params
    ) internal returns (uint256 endingNativeFunds) {
        endingNativeFunds = startingNativeFunds;

        uint256 sumListingPrices;
        uint256 dispensedPaymentValue; 

        SweepOrder calldata sweepOrder = advancedSweep.sweepOrder;
        AdvancedSweepItem[] calldata items = advancedSweep.items;

        for(uint256 itemIndex;itemIndex < items.length;) {
            AdvancedSweepItem calldata advancedSweepItem = items[itemIndex];
            SweepItem calldata sweepItem = advancedSweepItem.sweepItem;
            AdvancedOrder memory advancedOrder;

            advancedOrder.permitContext = advancedSweepItem.permitContext;
            advancedOrder.signature = advancedSweepItem.signature;
            advancedOrder.cosignature = advancedSweepItem.cosignature;
            advancedOrder.saleDetails = Order({
                protocol: sweepOrder.protocol,
                maker: sweepItem.maker,
                beneficiary: sweepOrder.beneficiary,
                marketplace: sweepItem.marketplace,
                fallbackRoyaltyRecipient: sweepItem.fallbackRoyaltyRecipient,
                paymentMethod: sweepOrder.paymentMethod,
                tokenAddress: sweepOrder.tokenAddress,
                tokenId: sweepItem.tokenId,
                amount: sweepItem.amount,
                itemPrice: uint240(sweepItem.itemPrice),
                nonce: sweepItem.nonce,
                expiration: sweepItem.expiration,
                marketplaceFeeNumerator: sweepItem.marketplaceFeeNumerator,
                maxRoyaltyFeeNumerator: sweepItem.maxRoyaltyFeeNumerator,
                requestedFillAmount: sweepItem.amount,
                minimumFillAmount: sweepItem.amount,
                protocolFeeVersion: sweepItem.protocolFeeVersion
            });

            Order memory saleDetails = advancedOrder.saleDetails;

            if (context.protocolFeeVersion != saleDetails.protocolFeeVersion) {
                context.protocolFeeVersion = saleDetails.protocolFeeVersion;
                context.protocolFees = appStorage().protocolFeeVersions[saleDetails.protocolFeeVersion];
            }

            sumListingPrices += saleDetails.itemPrice;

            if (saleDetails.protocol == ORDER_PROTOCOLS_ERC721_FILL_OR_KILL) {
                if (saleDetails.amount != ONE) {
                    revert PaymentProcessor__AmountForERC721SalesMustEqualOne();
                }
            } else if (saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_OR_KILL) {
                if (saleDetails.amount == 0) {
                    revert PaymentProcessor__AmountForERC1155SalesGreaterThanZero();
                }
            } else {
                revert PaymentProcessor__InvalidOrderProtocol();
            }

            if (saleDetails.marketplaceFeeNumerator + saleDetails.maxRoyaltyFeeNumerator > FEE_DENOMINATOR) {
                revert PaymentProcessor__MarketplaceAndRoyaltyFeesWillExceedSalePrice();
            }

            if (params.paymentSettings == PaymentSettings.PricingConstraints || params.paymentSettings == PaymentSettings.PricingConstraintsCollectionOnly) {
                _validateSalePriceInRange(
                    params.paymentSettings,
                    saleDetails.tokenAddress, 
                    saleDetails.tokenId, 
                    saleDetails.amount, 
                    saleDetails.itemPrice);
            }

            if (block.timestamp > saleDetails.expiration) {
                    revert PaymentProcessor__OrderHasExpired();
            }

            if (block.timestamp > context.protocolFees.versionExpiration) {
                revert PaymentProcessor__ProtocolFeeVersionExpired();
            }

            if (context.protocolFees.minimumProtocolFeeBps > FEE_DENOMINATOR) {
                revert PaymentProcessor__ProtocolFeeOrTaxExceedsCap();
            }

            if (advancedOrder.permitContext.permitProcessor == address(0)) {
                if (advancedSweepItem.bulkOrderProof.proof.length == 0) {
                    _verifySaleApproval(
                        context,
                        saleDetails,
                        advancedOrder.signature,
                        advancedOrder.cosignature
                    );
                } else {
                    _verifyBulkSaleApprovalProofAndSignedTree(
                        context,
                        saleDetails,
                        advancedOrder.signature,
                        advancedOrder.cosignature,
                        advancedSweepItem.bulkOrderProof
                    );
                }
            }

            (endingNativeFunds, dispensedPaymentValue) = _dispenseAndPayoutSweepItemAdvanced(
                advancedOrder, 
                context,
                params,
                endingNativeFunds,
                dispensedPaymentValue
            );

            unchecked {
                ++itemIndex;
            }
        }

        if (advancedSweep.feeOnTop.amount > sumListingPrices) {
            revert PaymentProcessor__FeeOnTopCannotBeGreaterThanItemPrice();
        }

        endingNativeFunds = _finalizePaymentAccumulatorDisbursement(
            context,
            params,
            advancedSweep.feeOnTop,
            dispensedPaymentValue,
            sumListingPrices,
            endingNativeFunds
        );
    }

    /**
     * @notice Disburses the accumulated proceeds to the respective recipients.
     *
     * @dev    Throws when the provided permit processor is not trusted.
     * @dev    Throws when the order has run out of native funds.
     */
    function _dispenseAndPayoutSweepItemAdvanced(
        AdvancedOrder memory advancedOrder,
        TradeContext memory context,
        SweepExecutionParams memory params,
        uint256 startingNativeFunds,
        uint256 startingDispensedPaymentValue
    ) internal returns(
        uint256 endingNativeFunds,
        uint256 endingDispensedPaymentValue
    ) {
        endingNativeFunds = startingNativeFunds;
        endingDispensedPaymentValue = startingDispensedPaymentValue;

        Order memory saleDetails = advancedOrder.saleDetails;
        bool failedToDispense;

        if (advancedOrder.permitContext.permitProcessor == address(0)) {
            failedToDispense = !params.fnPointers.funcDispenseToken(
                saleDetails.maker, 
                saleDetails.beneficiary, 
                saleDetails.tokenAddress, 
                saleDetails.tokenId, 
                saleDetails.amount);

            if(failedToDispense) {
                if (saleDetails.protocol != ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
                    _restoreNonce(saleDetails.maker, saleDetails.nonce);
                }
            }
        } else {
            if (!_isTrustedPermitProcessor(advancedOrder.permitContext.permitProcessor)) {
                revert PaymentProcessor__PermitProcessorNotTrusted();
            }

            uint256 quantityToFill;
            (quantityToFill, failedToDispense) = 
                _verifyPermittedSaleApprovalAndDispense(true, saleDetails.maker, advancedOrder, context);

            _adjustForUnitPricing(quantityToFill, saleDetails);
        }

        if (!failedToDispense) {
            SplitProceeds memory proceeds =
                _computePaymentSplits(
                    context,
                    saleDetails.itemPrice,
                    saleDetails.tokenAddress,
                    saleDetails.tokenId,
                    saleDetails.marketplace,
                    saleDetails.marketplaceFeeNumerator,
                    saleDetails.maxRoyaltyFeeNumerator,
                    saleDetails.fallbackRoyaltyRecipient
                );

            unchecked {
                endingDispensedPaymentValue += saleDetails.itemPrice;
            }

            if (saleDetails.paymentMethod == address(0)) {
                if (endingNativeFunds < saleDetails.itemPrice) {
                    revert PaymentProcessor__RanOutOfNativeFunds();
                }

                unchecked {
                    endingNativeFunds -= saleDetails.itemPrice;
                }
            }

            if (proceeds.royaltyRecipient != params.accumulator.lastRoyaltyRecipient) {
                if(params.accumulator.accumulatedRoyaltyProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastRoyaltyRecipient, context.taker, params.paymentCoin, params.accumulator.accumulatedRoyaltyProceeds);
                }

                params.accumulator.lastRoyaltyRecipient = proceeds.royaltyRecipient;
                params.accumulator.accumulatedRoyaltyProceeds = 0;
            }

            if (saleDetails.marketplace != params.accumulator.lastMarketplace) {
                if(params.accumulator.accumulatedMarketplaceProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastMarketplace, context.taker, params.paymentCoin, params.accumulator.accumulatedMarketplaceProceeds);
                }

                params.accumulator.lastMarketplace = saleDetails.marketplace;
                params.accumulator.accumulatedMarketplaceProceeds = 0;
            }

            if (saleDetails.maker != params.accumulator.lastSeller) {
                if(params.accumulator.accumulatedSellerProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastSeller, context.taker, params.paymentCoin, params.accumulator.accumulatedSellerProceeds);
                }

                params.accumulator.lastSeller = saleDetails.maker;
                params.accumulator.accumulatedSellerProceeds = 0;
            }

            if (context.protocolFees.protocolFeeReceiver != params.accumulator.lastProtocolFeeRecipient) {
                if (params.accumulator.accumulatedInfrastructureProceeds > 0) {
                    params.fnPointers.funcPayout(params.accumulator.lastProtocolFeeRecipient, context.taker, params.paymentCoin, params.accumulator.accumulatedInfrastructureProceeds);
                }

                params.accumulator.lastProtocolFeeRecipient = context.protocolFees.protocolFeeReceiver;
                params.accumulator.accumulatedInfrastructureProceeds = 0;
            }

            unchecked {
                params.accumulator.accumulatedRoyaltyProceeds += proceeds.royaltyProceeds;
                params.accumulator.accumulatedMarketplaceProceeds += proceeds.marketplaceProceeds;
                params.accumulator.accumulatedSellerProceeds += proceeds.sellerProceeds;
                params.accumulator.accumulatedInfrastructureProceeds += proceeds.infrastructureProceeds;
            }

            params.fnPointers.funcEmitOrderExecutionEvent(context, saleDetails);
        }

    }

    /**
     * @notice Verifies the proof and signature of an order that was generated by a signed merkle tree root.
     *
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable
     * @dev    amount is less than the minimum fillable amount requested.
     *
     * @param  context                            The execution context of the trade.
     * @param  saleDetails                        The details of the sale.
     * @param  signature                          The maker's signature authorizing the order execution.
     * @param  cosignature                        The additional cosignature for a cosigned order, if applicable.
     * @param  bulkOrderProof                     The bulk order proof and order index.
     * @param  orderHash                          The digest generated from the order details.
     * @param  _getBulkOrderTypehashByTreeHeight  The function to get the bulk order typehash by tree height.
     */
    function _verifyBulkOrderSignatures(
        TradeContext memory context,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        BulkOrderProof calldata bulkOrderProof,
        bytes32 orderHash,
        function(uint256) internal pure returns(bytes32) _getBulkOrderTypehashByTreeHeight
    ) internal returns (uint256 quantityToFill) {
        uint256 orderIndex = bulkOrderProof.orderIndex;
        bytes32 bulkOrderRootHash = orderHash;
        bytes32[] calldata proof = bulkOrderProof.proof;
        for (uint256 i; i < proof.length;) {
            if (orderIndex & 1 == 0) {
                bulkOrderRootHash = EfficientHash.efficientHash(bulkOrderRootHash, proof[i]);
            } else {
                bulkOrderRootHash = EfficientHash.efficientHash(proof[i], bulkOrderRootHash);
            }
            orderIndex >>= 1;
            unchecked {
                ++i;
            }
        }

        if (cosignature.signer != address(0)) {
            _verifyCosignature(context, signature, cosignature);
        }

        _verifyMakerSignature(
            saleDetails.maker, 
            signature, 
            _hashTypedDataV4(
                _cachedDomainSeparator, 
                EfficientHash.efficientHash(
                    _getBulkOrderTypehashByTreeHeight(bulkOrderProof.proof.length),
                    bulkOrderRootHash
                )
            )
        );

        if (saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
            quantityToFill = _checkAndUpdateRemainingFillableItems(
                saleDetails.maker, 
                orderHash, 
                saleDetails.amount, 
                saleDetails.requestedFillAmount,
                saleDetails.minimumFillAmount);
            context.orderDigest = orderHash;
        } else {
            quantityToFill = saleDetails.amount;
        }
    }

    /**
     * @notice Generate the order digest and verify the bulk order proof and signatures for an item.
     *
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable
     * @dev    amount is less than the minimum fillable amount requested.
     *
     * @param  tradeContext     The execution context of the trade.
     * @param  saleDetails      The details of the sale.
     * @param  signature        The maker's signature authorizing the order execution.
     * @param  cosignature      The additional cosignature for a cosigned order, if applicable.
     * @param  bulkOrderProof   The bulk order proof and order index.
     */
    function _verifyBulkItemOfferApprovalProofAndSignedTree(
        TradeContext memory tradeContext,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        BulkOrderProof calldata bulkOrderProof
    ) internal returns (uint256 quantityToFill) {
        bytes32 orderHash = 
            _generateItemOfferOrderHash(
                saleDetails, 
                saleDetails.maker, 
                cosignature.signer, 
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            );

        quantityToFill = _verifyBulkOrderSignatures(
            tradeContext, 
            saleDetails, 
            signature, 
            cosignature, 
            bulkOrderProof, 
            orderHash,
            _getBulkItemOfferApprovalTypehash
        );
    }

    /**
     * @notice Generate the order digest and verify the bulk order proof and signatures for a collection.
     *
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable
     * @dev    amount is less than the minimum fillable amount requested.
     *
     * @param  tradeContext     The execution context of the trade.
     * @param  saleDetails      The details of the sale.
     * @param  signature        The maker's signature authorizing the order execution.
     * @param  cosignature      The additional cosignature for a cosigned order, if applicable.
     * @param  bulkOrderProof   The bulk order proof and order index.
     */
    function _verifyBulkCollectionOfferApprovalProofAndSignedTree(
        TradeContext memory tradeContext,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        BulkOrderProof calldata bulkOrderProof
    ) internal returns (uint256 quantityToFill) {
        bytes32 orderHash = 
            _generateCollectionOfferOrderHash(
                saleDetails, 
                saleDetails.maker, 
                cosignature.signer, 
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            );

        quantityToFill = _verifyBulkOrderSignatures(
            tradeContext, 
            saleDetails, 
            signature, 
            cosignature, 
            bulkOrderProof, 
            orderHash,
            _getBulkCollectionOfferApprovalTypehash
        );
    }

    /**
     * @notice Generate the order digest and verify the bulk order proof and signatures for a token set order.
     *
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable
     * @dev    amount is less than the minimum fillable amount requested.
     *
     * @param  tradeContext     The execution context of the trade.
     * @param  saleDetails      The details of the sale.
     * @param  signature        The maker's signature authorizing the order execution.
     * @param  tokenSetProof    The merkle proofs for an offer that is a subset of tokens in a collection.
     * @param  cosignature      The additional cosignature for a cosigned order, if applicable.
     * @param  bulkOrderProof   The bulk order proof and order index.
     */
    function _verifyBulkTokenSetOfferApprovalProofAndSignedTree(
        TradeContext memory tradeContext,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        TokenSetProof calldata tokenSetProof,
        Cosignature memory cosignature,
        BulkOrderProof calldata bulkOrderProof
    ) internal returns (uint256 quantityToFill) {
        bytes32 orderHash = 
            _generateTokenSetOfferOrderHash(
                tokenSetProof,
                saleDetails, 
                saleDetails.maker, 
                cosignature.signer, 
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            );

        quantityToFill = _verifyBulkOrderSignatures(
            tradeContext, 
            saleDetails, 
            signature, 
            cosignature, 
            bulkOrderProof, 
            orderHash,
            _getBulkTokenSetOfferApprovalTypehash
        );
    }

    /**
     * @notice Generate the order digest and verify the bulk order proof and signatures for a sale approval.
     *
     * @dev    Throws when the maker's signature is invalid.
     * @dev    Throws when the order status is not open.
     * @dev    Throws when the nonce has already been invalidated.
     * @dev    Throws when a cosignature is required and the cosignature is invalid.
     * @dev    Throws when the order type is ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL and the remaining fillable
     * @dev    amount is less than the minimum fillable amount requested.
     *
     * @param  tradeContext     The execution context of the trade.
     * @param  saleDetails      The details of the sale.
     * @param  signature        The maker's signature authorizing the order execution.
     * @param  cosignature      The additional cosignature for a cosigned order, if applicable.
     * @param  bulkOrderProof   The bulk order proof and order index.
     */
    function _verifyBulkSaleApprovalProofAndSignedTree(
        TradeContext memory tradeContext,
        Order memory saleDetails,
        SignatureECDSA memory signature,
        Cosignature memory cosignature,
        BulkOrderProof calldata bulkOrderProof
    ) internal returns (uint256 quantityToFill) {
        bytes32 orderHash = 
            _generateListingOrderHash(
                saleDetails, 
                saleDetails.maker, 
                cosignature.signer, 
                _checkAndInvalidateNonceForFillOrKillOrders(saleDetails)
            );

        quantityToFill = _verifyBulkOrderSignatures(
            tradeContext, 
            saleDetails, 
            signature, 
            cosignature, 
            bulkOrderProof, 
            orderHash,
            _getBulkSaleApprovalTypehash
        );
    }

    /**
     * @notice Helper function which returns the bulk sale approval typehash for a provided height.
     *
     * @param height  The height of the typehash.
     */
    function _getBulkSaleApprovalTypehash(uint256 height) internal pure returns(bytes32 hash) {
        if(height > 10 || height == 0) {
            revert PaymentProcessor__InvalidBulkOrderHeight();
        }
        bytes memory memTypehashes = BULK_SALE_APPROVAL_TYPEHASHES_LOOKUP;
        assembly {
            hash := mload(add(memTypehashes, shl(5, height)))
        }
    }

    /**
     * @notice Helper function which returns the bulk collection offer approval typehash for a provided height.
     *
     * @param height  The height of the typehash.
     */
    function _getBulkCollectionOfferApprovalTypehash(uint256 height) internal pure returns(bytes32 hash) {
        if(height > 10 || height == 0) {
            revert PaymentProcessor__InvalidBulkOrderHeight();
        }
        bytes memory memTypehashes = BULK_COLLECTION_OFFER_APPROVAL_TYPEHASHES_LOOKUP;
        assembly {
            hash := mload(add(memTypehashes, shl(5, height)))
        }
    }

    /**
     * @notice Helper function which returns the bulk item offer approval typehash for a provided height.
     *
     * @param height  The height of the typehash.
     */
    function _getBulkItemOfferApprovalTypehash(uint256 height) internal pure returns(bytes32 hash) {
        if(height > 10 || height == 0) {
            revert PaymentProcessor__InvalidBulkOrderHeight();
        }
        bytes memory memTypehashes = BULK_ITEM_OFFER_APPROVAL_TYPEHASHES_LOOKUP;
        assembly {
            hash := mload(add(memTypehashes, shl(5, height)))
        }
    }

    /**
     * @notice Helper function which returns the bulk token set offer approval typehash for a provided height.
     *
     * @param height  The height of the typehash.
     */
    function _getBulkTokenSetOfferApprovalTypehash(uint256 height) internal pure returns(bytes32 hash) {
        if(height > 10 || height == 0) {
            revert PaymentProcessor__InvalidBulkOrderHeight();
        }
        bytes memory memTypehashes = BULK_TOKEN_SET_OFFER_APPROVAL_TYPEHASHES_LOOKUP;
        assembly {
            hash := mload(add(memTypehashes, shl(5, height)))
        }
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./PaymentProcessorModule.sol";

import {
    IPermitC 
} from "@limitbreak/permit-c/interfaces/IPermitC.sol";

/*
                                                     @@@@@@@@@@@@@@             
                                                    @@@@@@@@@@@@@@@@@@(         
                                                   @@@@@@@@@@@@@@@@@@@@@        
                                                  @@@@@@@@@@@@@@@@@@@@@@@@      
                                                           #@@@@@@@@@@@@@@      
                                                               @@@@@@@@@@@@     
                            @@@@@@@@@@@@@@*                    @@@@@@@@@@@@     
                           @@@@@@@@@@@@@@@     @               @@@@@@@@@@@@     
                          @@@@@@@@@@@@@@@     @                @@@@@@@@@@@      
                         @@@@@@@@@@@@@@@     @@               @@@@@@@@@@@@      
                        @@@@@@@@@@@@@@@     #@@             @@@@@@@@@@@@/       
                        @@@@@@@@@@@@@@.     @@@@@@@@@@@@@@@@@@@@@@@@@@@         
                       @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@            
                      @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@             
                     @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@           
                    @@@@@@@@@@@@@@@     @@@@@&%%%%%%%%&&@@@@@@@@@@@@@@          
                    @@@@@@@@@@@@@@      @@@@@               @@@@@@@@@@@         
                   @@@@@@@@@@@@@@@     @@@@@                 @@@@@@@@@@@        
                  @@@@@@@@@@@@@@@     @@@@@@                 @@@@@@@@@@@        
                 @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@        
                @@@@@@@@@@@@@@@     @@@@@@@                 @@@@@@@@@@@&        
                @@@@@@@@@@@@@@     *@@@@@@@               (@@@@@@@@@@@@         
               @@@@@@@@@@@@@@@     @@@@@@@@             @@@@@@@@@@@@@@          
              @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           
             @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@            
            @@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@              
           .@@@@@@@@@@@@@@     @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                 
           @@@@@@@@@@@@@@%     @@@@@@@@@@@@@@@@@@@@@@@@(                        
          @@@@@@@@@@@@@@@                                                       
         @@@@@@@@@@@@@@@                                                        
        @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                         
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                          
       @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&                                          
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@                                           
 
* @title PaymentProcessorModulePermits
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 

abstract contract PaymentProcessorModulePermits is PaymentProcessorModule {
    using EnumerableSet for EnumerableSet.AddressSet;

    address private immutable permitProcessor1;
    address private immutable permitProcessor2;

    constructor(address configurationContract) PaymentProcessorModule(configurationContract) {
        (,,,TrustedPermitProcessors memory trustedPermitProcessors,,
        ) = IPaymentProcessorConfiguration(configurationContract).getPaymentProcessorModuleDeploymentParams();
        
        permitProcessor1 = trustedPermitProcessors.permitProcessor1;
        permitProcessor2 = trustedPermitProcessors.permitProcessor2;
    }

    /*************************************************************************/
    /*                        Default Permit Processors                      */
    /*************************************************************************/

    /**
     * @notice Returns true if `permitProcessor` is a trusted permit processor.
     * 
     * @dev    This function will also return true if the permit processor was added after contract deployment.
     */
    function _isTrustedPermitProcessor(address permitProcessor) internal returns (bool) {
        if (permitProcessor == permitProcessor1 || 
            permitProcessor == permitProcessor2) {
            return true;
        }
        if (appStorage().trustedPermitProcessors.contains(permitProcessor)) {
            return true;
        }
        return IPaymentProcessorSettings(address(this)).checkTrustedPermitProcessors(permitProcessor);
    }

    /**
     * @notice Returns an array of the default payment methods defined at contract deployment.
     * 
     * @dev    This array will **NOT** include default payment methods added after contract deployment.
     */
    function _getTrustedPermitProcessors() internal view returns (address[] memory trustedPermitProcessors) {
        trustedPermitProcessors = new address[](2);
        trustedPermitProcessors[0] = permitProcessor1;
        trustedPermitProcessors[1] = permitProcessor2;
    }

    /*************************************************************************/
    /*                           Order Fulfillment                           */
    /*************************************************************************/

    /**
     * @notice Validates and fulfills a single permitted order.
     * 
     * @dev    Throws if the order is buy side, requires a cosignature, and the cosignature is invalid.
     * @dev    Throws if the permit nonce is invalid.
     * @dev    Throws if the permit signature is invalid.
     *
     * @param startingNativeFunds The starting native funds.
     * @param side                The side of the order.
     * @param matchedOrder        The order to fulfill.
     * @param tradeContext        The execution context of the trade.
     * @param feeOnTop            The additional fee to add on top of the order, paid by taker.
     */
    function _validateAndFulfillSinglePermittedOrder(
        uint256 startingNativeFunds,
        Sides side,
        AdvancedOrder memory matchedOrder,
        TradeContext memory tradeContext,
        FeeOnTop calldata feeOnTop
    ) internal returns (uint256 endingNativeFunds) {
        endingNativeFunds = startingNativeFunds;

        Order memory saleDetails = matchedOrder.saleDetails;

        address purchaser = tradeContext.taker;
        address seller = matchedOrder.saleDetails.maker;
        bool isBuySide = side == Sides.Buy;

        if (!isBuySide) {
            purchaser = matchedOrder.saleDetails.maker;
            seller = tradeContext.taker;
        }

        (uint256 quantityToFill, bool failedToDispense) = 
            _verifyPermittedSaleApprovalAndDispense(isBuySide, seller, matchedOrder, tradeContext);
        
        _adjustForUnitPricing(quantityToFill, saleDetails);

        _validateBasicOrderDetails(tradeContext, saleDetails);

        if (failedToDispense) {
            if (tradeContext.disablePartialFill) {
                revert PaymentProcessor__DispensingTokenWasUnsuccessful();
            }
        } else {
            SplitProceeds memory proceeds =
                _computePaymentSplits(
                    tradeContext,
                    saleDetails.itemPrice,
                    saleDetails.tokenAddress,
                    saleDetails.tokenId,
                    saleDetails.marketplace,
                    saleDetails.marketplaceFeeNumerator,
                    saleDetails.maxRoyaltyFeeNumerator,
                    saleDetails.fallbackRoyaltyRecipient
                );

            uint256 feeOnTopAmount;
            if (feeOnTop.recipient != address(0)) {
                feeOnTopAmount = feeOnTop.amount;

                if (feeOnTopAmount > saleDetails.itemPrice) {
                    revert PaymentProcessor__FeeOnTopCannotBeGreaterThanItemPrice();
                }
            }

            if (saleDetails.paymentMethod == address(0)) {
                unchecked {
                    uint256 nativeProceedsToSpend = saleDetails.itemPrice + feeOnTopAmount;
                    if (endingNativeFunds < nativeProceedsToSpend) {
                        revert PaymentProcessor__RanOutOfNativeFunds();
                    }

                    endingNativeFunds -= nativeProceedsToSpend;
                }
            }

            FulfillOrderFunctionPointers memory fnPointers = 
                _getOrderFulfillmentFunctionPointers(side, saleDetails.paymentMethod, saleDetails.protocol);

            if (feeOnTopAmount > 0) {
                unchecked {
                    uint256 feeOnTopAmountInfrastructure = feeOnTopAmount * tradeContext.protocolFees.feeOnTopProtocolTaxBps / FEE_DENOMINATOR;
                    fnPointers.funcPayout(feeOnTop.recipient, tradeContext.taker, saleDetails.paymentMethod, feeOnTopAmount - feeOnTopAmountInfrastructure);

                    if (purchaser == tradeContext.taker) {
                        proceeds.infrastructureProceeds += feeOnTopAmountInfrastructure;
                    } else {
                        if (feeOnTopAmountInfrastructure > 0) {
                            fnPointers.funcPayout(tradeContext.protocolFees.protocolFeeReceiver, tradeContext.taker, saleDetails.paymentMethod, feeOnTopAmountInfrastructure);
                        }
                    }
                }
            }

            if (proceeds.infrastructureProceeds > 0) {
                fnPointers.funcPayout(tradeContext.protocolFees.protocolFeeReceiver, purchaser, saleDetails.paymentMethod, proceeds.infrastructureProceeds);
            }

            if (proceeds.royaltyProceeds > 0) {
                fnPointers.funcPayout(proceeds.royaltyRecipient, purchaser, saleDetails.paymentMethod, proceeds.royaltyProceeds);
            }

            if (proceeds.marketplaceProceeds > 0) {
                fnPointers.funcPayout(saleDetails.marketplace, purchaser, saleDetails.paymentMethod, proceeds.marketplaceProceeds);
            }

            if (proceeds.sellerProceeds > 0) {
                fnPointers.funcPayout(seller, purchaser, saleDetails.paymentMethod, proceeds.sellerProceeds);
            }

            fnPointers.funcEmitOrderExecutionEvent(tradeContext, saleDetails);
        }
    }

    /*************************************************************************/
    /*                        Signature Verification                         */
    /*************************************************************************/

    /**
     * @notice Performs initial verification of a permit signature and processes it via the permit processor.
     *
     * @dev    Throws if the order is buy side, requires a cosignature, and the cosignature is invalid.
     * @dev    Throws if the permit nonce is invalid.
     * @dev    Throws if the permit signature is invalid.
     *
     * @param isBuySide       True if the order is buy side, false otherwise.
     * @param seller          The seller of the order.
     * @param matchedOrder    The order to verify.
     * @param tradeContext    The trade context.
     */
    function _verifyPermittedSaleApprovalAndDispense(
        bool isBuySide,
        address seller,
        AdvancedOrder memory matchedOrder,
        TradeContext memory tradeContext
    ) internal returns (uint256 quantityToFill, bool isError) {
        Order memory saleDetails = matchedOrder.saleDetails;

        bytes32 orderDigest = _generateListingOrderHash(
            matchedOrder.saleDetails,
            seller,
            isBuySide ? matchedOrder.cosignature.signer : address(0),
            appStorage().masterNonces[seller]
        );

        if (isBuySide) {
            if (matchedOrder.cosignature.signer != address(0)) {
                _verifyCosignature(
                    tradeContext, 
                    matchedOrder.signature, 
                    matchedOrder.cosignature);
            }
        }

        if (saleDetails.protocol == ORDER_PROTOCOLS_ERC1155_FILL_PARTIAL) {
            if (isBuySide) {
                (quantityToFill, isError) = _fillPermittedOrderERC1155(seller, orderDigest, matchedOrder);
            } else {
                isError = _permitTransferFromWithAdditionalDataERC1155(seller, orderDigest, matchedOrder);
                quantityToFill = saleDetails.amount;
            }
        } else {
            quantityToFill = saleDetails.amount;

            if (saleDetails.protocol == ORDER_PROTOCOLS_ERC721_FILL_OR_KILL) {
                isError = _permitTransferFromWithAdditionalDataERC721(seller, orderDigest, matchedOrder);
            } else {
                isError = _permitTransferFromWithAdditionalDataERC1155(seller, orderDigest, matchedOrder);
            }
        }
    }

    /**
     * @notice Processes a permit signature for an ERC1155 order.
     *
     * @dev    Throws if the permit nonce is invalid.
     * @dev    Throws if the permit signature is invalid.
     * @dev    Throws if the amount remaining is less than the minimum fill amount.
     * @dev    Throws if the quantity to fill exceeds uint248 max.
     *
     * @param seller          The seller of the order.
     * @param orderDigest     The order digest.
     * @param matchedOrder    The order to verify.
     */
    function _fillPermittedOrderERC1155(
        address seller,
        bytes32 orderDigest,
        AdvancedOrder memory matchedOrder
    ) internal returns (uint256 quantityToFill, bool isError) {
        Order memory saleDetails = matchedOrder.saleDetails;

        (
            quantityToFill, 
            isError
        ) = IPermitC(matchedOrder.permitContext.permitProcessor).fillPermittedOrderERC1155(
            _repackSignature(matchedOrder.signature),
            OrderFillAmounts({
                orderStartAmount: saleDetails.amount,
                requestedFillAmount: saleDetails.requestedFillAmount,
                minimumFillAmount: saleDetails.minimumFillAmount
            }),
            saleDetails.tokenAddress,
            saleDetails.tokenId,
            seller,
            saleDetails.beneficiary,
            matchedOrder.permitContext.permitNonce,
            uint48(saleDetails.expiration),
            orderDigest,
            PERMITTED_ORDER_SALE_APPROVAL
        );

        if(quantityToFill > type(uint248).max) {
            revert PaymentProcessor__AmountExceedsMaximum();
        }
    }

    /**
     * @notice Processes a permit signature with additional data for an ERC1155 order.
     * 
     * @dev    Throws if the permit nonce is invalid.
     * @dev    Throws if the permit signature is invalid.
     *
     * @param seller          The seller of the order.
     * @param orderDigest     The order digest.
     * @param matchedOrder    The order to verify.
     */
    function _permitTransferFromWithAdditionalDataERC1155(
        address seller,
        bytes32 orderDigest,
        AdvancedOrder memory matchedOrder
    ) internal returns (bool isError) {
        Order memory saleDetails = matchedOrder.saleDetails;

        isError = IPermitC(matchedOrder.permitContext.permitProcessor).permitTransferFromWithAdditionalDataERC1155(
            saleDetails.tokenAddress,
            saleDetails.tokenId,
            matchedOrder.permitContext.permitNonce,
            saleDetails.amount,
            saleDetails.expiration,
            seller,
            saleDetails.beneficiary,
            saleDetails.amount,
            orderDigest,
            PERMITTED_TRANSFER_SALE_APPROVAL,
            _repackSignature(matchedOrder.signature)
        );

        if (!isError) {
            emit PermittedOrderNonceInvalidated(
                matchedOrder.permitContext.permitNonce,
                saleDetails.nonce,
                seller,
                false
            );
        }
    }

    /**
     * @notice Processes a permit signature with additional data for an ERC721 order.
     * 
     * @dev    Throws if the permit nonce is invalid.
     * @dev    Throws if the permit signature is invalid.
     * 
     * @param seller          The seller of the order.
     * @param orderDigest     The order digest.
     * @param matchedOrder    The order to verify.
     */
    function _permitTransferFromWithAdditionalDataERC721(
        address seller,
        bytes32 orderDigest,
        AdvancedOrder memory matchedOrder
    ) internal returns (bool isError) {
        Order memory saleDetails = matchedOrder.saleDetails;

        isError = IPermitC(matchedOrder.permitContext.permitProcessor).permitTransferFromWithAdditionalDataERC721(
            saleDetails.tokenAddress,
            saleDetails.tokenId,
            matchedOrder.permitContext.permitNonce,
            saleDetails.expiration,
            seller,
            saleDetails.beneficiary,
            orderDigest,
            PERMITTED_TRANSFER_SALE_APPROVAL,
            _repackSignature(matchedOrder.signature)
        );

        if (!isError) {
            emit PermittedOrderNonceInvalidated(
                matchedOrder.permitContext.permitNonce,
                saleDetails.nonce,
                seller,
                false
            );
        }
    }

    /**
     * @notice Concatenates the r, s, and v values of a signature into a single bytes array.
     *
     * @param signature The signature to repack.
     */
    function _repackSignature(SignatureECDSA memory signature) internal pure returns (bytes memory) {
        if(signature.v > type(uint8).max) {
            revert PaymentProcessor__InvalidSignatureV();
        }
        return abi.encodePacked(signature.r, signature.s, uint8(signature.v));
    }
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./IPaymentProcessorSettings.sol";
import "./SettingsDataTypes.sol";

/** 
* @title ICollectionSettingsRegistry
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 
interface ICollectionSettingsRegistry {

    /// @notice Emitted when a new payment method whitelist is created.
    event CreatedPaymentMethodWhitelist(
        uint32 indexed paymentMethodWhitelistId, 
        address indexed whitelistOwner,
        string whitelistName);

    /// @notice Emitted when a coin is added to the approved coins mapping for a whitelist.
    event PaymentMethodAddedToWhitelist(
        uint32 indexed paymentMethodWhitelistId, 
        address indexed paymentMethod);

    /// @notice Emitted when a coin is removed from the approved coins mapping for a whitelist.
    event PaymentMethodRemovedFromWhitelist(
        uint32 indexed paymentMethodWhitelistId, 
        address indexed paymentMethod);

    /// @notice Emitted when a permit processor is added to the trusted permit processor list.
    event TrustedPermitProcessorAdded(address indexed permitProcessor);

    /// @notice Emitted when a permit processor is removed from the trusted permit processor list.
    event TrustedPermitProcessorRemoved(address indexed permitProcessor);

    /// @notice Emitted when payment settings are updated for a collection.
    event UpdatedCollectionPaymentSettings(
        address indexed tokenAddress,
        CollectionPaymentSettingsParams params
    );

    /// @notice Emitted whenever pricing bounds change at a token level for price-constrained collections.
    event UpdatedTokenLevelPricingBoundaries(
        address indexed tokenAddress, 
        uint256 indexed tokenId, 
        uint256 floorPrice, 
        uint256 ceilingPrice);

    /// @notice Emitted when a trusted channel is removed for a collection.
    event TrustedChannelRemovedForCollection(
        address indexed tokenAddress, 
        address indexed channel);

    /// @notice Emitted when a trusted channel is added for a collection.
    event TrustedChannelAddedForCollection(
        address indexed tokenAddress, 
        address indexed channel);

    /// @notice Emitted when a payment method whitelist is reassigned to a new owner.
    event ReassignedPaymentMethodWhitelistOwnership(uint32 indexed id, address indexed newOwner);

    function isCollectionSettingsInitialized(address tokenAddress) external view returns (bool);

    function getCollectionSettings(
        address tokenAddress,
        bytes32[] calldata dataExtensions,
        bytes32[] calldata wordExtensions
    ) external view returns (
        CollectionRegistryPaymentSettings memory collectionCoreSettings,
        RegistryPricingBounds memory collectionPricingBounds,
        address constrainedPricingPaymentMethod,
        address exclusiveBountyReceiver,
        bytes[] memory data, 
        bytes32[] memory words
    );

    function getCollectionSettingsExtendedData(
        address tokenAddress, 
        bytes32[] calldata extensions
    ) external view returns (bytes[] memory data);

    function getCollectionSettingsExtendedWords(
        address tokenAddress, 
        bytes32[] calldata extensions
    ) external view returns (bytes32[] memory words);

    function isWhitelistedPaymentMethod(
        uint32 whitelistId,
        address paymentMethod
    ) external view returns (bool paymentMethodWhitelisted);

    function isTrustedChannelForCollection(
        address tokenAddress,
        address channel
    ) external view returns (bool channelIsTrusted);

    function isTrustedPermitProcessor(
        address permitProcessor
    ) external view returns (bool isTrusted);

    function getTokenBoundPricing(
        address tokenAddress,
        uint256 tokenId
    ) external view returns (RegistryPricingBounds memory);
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "./SettingsDataTypes.sol";

/** 
* @title IPaymentProcessorSettings
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 
interface IPaymentProcessorSettings {
    function registrySyncSettings(address tokenAddress) external;

    function registryUpdateWhitelistPaymentMethods(
        uint32 paymentMethodWhitelistId,
        address[] calldata paymentMethods,
        bool paymentMethodsAdded
    ) external;

    function registryUpdateTrustedChannels(
        address tokenAddress,
        address[] calldata channelsToUpdate,
        bool channelsAdded
    ) external;

    function registryUpdateTokenPricingBounds(
        address tokenAddress, 
        uint256[] calldata tokenIds, 
        RegistryPricingBounds[] calldata pricingBounds
    ) external;

    function registryUpdateTrustedPermitProcessors(
        address[] calldata permitProcessors,
        bool permitProcessorsAdded
    ) external;

    function checkSyncCollectionSettings(
        address tokenAddress
    ) external;

    function checkSyncTokenPricingBounds(
        address tokenAddress,
        uint256 tokenId
    ) external returns (uint256, uint256);
    
    function checkWhitelistedPaymentMethod(
        uint32 whitelistId,
        address paymentMethod
    ) external returns (bool isWhitelisted);
    
    function checkCollectionTrustedChannels(
        address tokenAddress,
        address channel
    ) external returns (bool isAllowed);
    
    function checkTrustedPermitProcessors(
        address permitProcessor
    ) external returns (bool isTrusted);
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;


/**
 * @dev This struct defines the payment settings for a collection.
 * 
 * @dev **initialized**: True if the payment settings have been initialized, false otherwise.
 * @dev **paymentSettingsType**: The type of payment settings for the collection.
 * @dev **paymentMethodWhitelistId**: The ID of the payment method whitelist to use for the collection.
 * @dev **royaltyBackfillReceiver**: The address to use as the royalty backfill receiver.
 * @dev **royaltyBackfillNumerator**: The numerator to use for the royalty backfill.
 * @dev **royaltyBountyNumerator**: The numerator to use for the royalty bounty.
 * @dev **extraData**: Extra data for the payment settings.
 */
struct CollectionRegistryPaymentSettings {
    bool initialized;
    uint8 paymentSettingsType;
    uint32 paymentMethodWhitelistId;
    address royaltyBackfillReceiver;
    uint16 royaltyBackfillNumerator;
    uint16 royaltyBountyNumerator;
    uint16 extraData;
}

/**
 * @dev This struct defines the parameters for collection payment settings.
 *
 * @dev **paymentSettings**: The payment settings for the collection.
 * @dev **paymentMethodWhitelistId**: The ID of the payment method whitelist to use for the collection.
 * @dev **constrainedPricingPaymentMethod**: The payment method to use for constrained pricing.
 * @dev **royaltyBackfillNumerator**: The numerator to use for the royalty backfill.
 * @dev **royaltyBackfillReceiver**: The address to use as the royalty backfill receiver.
 * @dev **royaltyBountyNumerator**: The numerator to use for the royalty bounty.
 * @dev **exclusiveBountyReceiver**: The address to use as the exclusive bounty receiver.
 * @dev **extraData**: Extra data for the payment settings.
 * @dev **collectionMinimumFloorPrice**: The minimum floor price for the collection.
 * @dev **collectionMaximumCeilingPrice**: The maximum ceiling price for the collection.
 * @dev **flags**: The flags for the payment settings.
 */
struct CollectionPaymentSettingsParams {
    uint8 paymentSettings;
    uint32 paymentMethodWhitelistId;
    address constrainedPricingPaymentMethod;
    uint16 royaltyBackfillNumerator;
    address royaltyBackfillReceiver;
    uint16 royaltyBountyNumerator;
    address exclusiveBountyReceiver;
    uint16 extraData;
    uint120 collectionMinimumFloorPrice;
    uint120 collectionMaximumCeilingPrice;
}

/**
 * @dev This struct defines the pricing bounds for a registry.
 *
 * @dev **isSet**: True if the pricing bounds are set, false otherwise.
 * @dev **floorPrice**: The floor price for the registry.
 * @dev **ceilingPrice**: The ceiling price for the registry.
 */
struct RegistryPricingBounds {
    bool isSet;
    uint120 floorPrice;
    uint120 ceilingPrice;
}

//SPDX-License-Identifier: LicenseRef-PolyForm-Strict-1.0.0
pragma solidity 0.8.24;

import "../DataTypes.sol";

/** 
* @title PaymentProcessorStorageAccess
* @custom:version 3.0.0
* @author Limit Break, Inc.
*/ 
contract PaymentProcessorStorageAccess {

    /// @dev The base storage slot for Payment Processor contract storage items.
    bytes32 constant DIAMOND_STORAGE_PAYMENT_PROCESSOR = 
        0x0000000000000000000000000000000000000000000000000000000000009A1D;

    /**
     * @dev Returns a storage object that follows the Diamond standard storage pattern for
     * @dev contract storage across multiple module contracts.
     */
    function appStorage() internal pure returns (PaymentProcessorStorage storage diamondStorage) {
        bytes32 slot = DIAMOND_STORAGE_PAYMENT_PROCESSOR;
        assembly {
            diamondStorage.slot := slot
        }
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):