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

/// @notice Library for efficient querying of the delegate registries.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ext/delegate/DelegateCheckerLib.sol)
library DelegateCheckerLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The canonical delegate registry V1.
    /// See: https://etherscan.io/address/0x00000000000076a84fef008cdabe6409d2fe638b
    address internal constant DELEGATE_REGISTRY_V1 = 0x00000000000076A84feF008CDAbe6409d2FE638B;

    /// @dev The canonical delegate registry V2.
    /// See: https://etherscan.io/address/0x00000000000000447e69651d841bD8D104Bed493
    address internal constant DELEGATE_REGISTRY_V2 = 0x00000000000000447e69651d841bD8D104Bed493;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                DELEGATE CHECKING OPERATIONS                */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // Note:
    // - `to` is the delegate. Typically called the "hot wallet".
    // - `from` is the grantor of the delegate rights. Typically called the "cold vault".

    /// @dev Returns if `to` is a delegate of `from`.
    /// ```
    ///     v2.checkDelegateForAll(to, from, "") ||
    ///     v1.checkDelegateForAll(to, from)
    /// ```
    function checkDelegateForAll(address to, address from) internal view returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            // `0x60` is already 0.
            mstore(0x40, from)
            mstore(0x2c, shl(96, to))
            mstore(0x0c, 0xe839bd53000000000000000000000000) // `checkDelegateForAll(address,address,bytes32)`.
            isValid := eq(mload(staticcall(gas(), DELEGATE_REGISTRY_V2, 0x1c, 0x64, 0x01, 0x20)), 1)
            if iszero(isValid) {
                mstore(0x01, 0x9c395bc200) // `checkDelegateForAll(address,address)`.
                isValid :=
                    eq(mload(staticcall(gas(), DELEGATE_REGISTRY_V1, 0x1c, 0x44, 0x01, 0x20)), 1)
            }
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Returns if `to` is a delegate of `from`.
    /// ```
    ///     v2.checkDelegateForAll(to, from, rights) ||
    ///     (rights == "" && v1.checkDelegateForAll(to, from))
    /// ```
    function checkDelegateForAll(address to, address from, bytes32 rights)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(0x60, rights)
            mstore(0x40, from)
            mstore(0x2c, shl(96, to))
            mstore(0x0c, 0xe839bd53000000000000000000000000) // `checkDelegateForAll(address,address,bytes32)`.
            isValid := eq(mload(staticcall(gas(), DELEGATE_REGISTRY_V2, 0x1c, 0x64, 0x01, 0x20)), 1)
            if iszero(or(rights, isValid)) {
                mstore(0x01, 0x9c395bc200) // `checkDelegateForAll(address,address)`.
                isValid :=
                    eq(mload(staticcall(gas(), DELEGATE_REGISTRY_V1, 0x1c, 0x44, 0x01, 0x20)), 1)
            }
            mstore(0x40, m) // Restore the free memory pointer.
            mstore(0x60, 0) // Restore the zero pointer.
        }
    }

    /// @dev Returns if `to` is a delegate of `from` for the specified `contract_`.
    /// ```
    ///     v2.checkDelegateForContract(to, from, contract_, "") ||
    ///     v1.checkDelegateForContract(to, from, contract_)
    /// ```
    /// Returns true if `checkDelegateForAll(to, from)` returns true.
    function checkDelegateForContract(address to, address from, address contract_)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(0x80, m), 0)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForContract(address,address,address,bytes32)`.
            mstore(add(0x0c, m), 0x8988eea9000000000000000000000000)
            isValid := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0x84, m, 0x20)
            isValid := and(eq(mload(m), 1), isValid)
            if iszero(isValid) {
                mstore(m, 0x90c9a2d0) // `checkDelegateForContract(address,address,address)`.
                isValid := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x64, m, 0x20)
                isValid := and(eq(mload(m), 1), isValid)
            }
        }
    }

    /// @dev Returns if `to` is a delegate of `from` for the specified `contract_`.
    /// ```
    ///     v2.checkDelegateForContract(to, from, contract_, rights) ||
    ///     (rights == "" && v1.checkDelegateForContract(to, from, contract_))
    /// ```
    /// Returns true if `checkDelegateForAll(to, from, rights)` returns true.
    function checkDelegateForContract(address to, address from, address contract_, bytes32 rights)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(0x80, m), rights)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForContract(address,address,address,bytes32)`.
            mstore(add(0x0c, m), 0x8988eea9000000000000000000000000)
            isValid := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0x84, m, 0x20)
            isValid := and(eq(mload(m), 1), isValid)
            if iszero(or(rights, isValid)) {
                mstore(m, 0x90c9a2d0) // `checkDelegateForContract(address,address,address)`.
                isValid := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x64, m, 0x20)
                isValid := and(eq(mload(m), 1), isValid)
            }
        }
    }

    /// @dev Returns if `to` is a delegate of `from` for the specified `contract_` and token `id`.
    /// ```
    ///     v2.checkDelegateForERC721(to, from, contract_, id, "") ||
    ///     v1.checkDelegateForToken(to, from, contract_, id)
    /// ```
    /// Returns true if `checkDelegateForContract(to, from, contract_)` returns true.
    function checkDelegateForERC721(address to, address from, address contract_, uint256 id)
        internal
        view
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(0xa0, m), 0)
            mstore(add(0x80, m), id)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForERC721(address,address,address,uint256,bytes32)`.
            mstore(add(0x0c, m), 0xb9f36874000000000000000000000000)
            isValid := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0xa4, m, 0x20)
            isValid := and(eq(mload(m), 1), isValid)
            if iszero(isValid) {
                mstore(m, 0xaba69cf8) // `checkDelegateForToken(address,address,address,uint256)`.
                isValid := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x84, m, 0x20)
                isValid := and(eq(mload(m), 1), isValid)
            }
        }
    }

    /// @dev Returns if `to` is a delegate of `from` for the specified `contract_` and token `id`.
    /// ```
    ///     v2.checkDelegateForERC721(to, from, contract_, id, rights) ||
    ///     (rights == "" && v1.checkDelegateForToken(to, from, contract_, id))
    /// ```
    /// Returns true if `checkDelegateForContract(to, from, contract_, rights)` returns true.
    function checkDelegateForERC721(
        address to,
        address from,
        address contract_,
        uint256 id,
        bytes32 rights
    ) internal view returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(add(0xa0, m), rights)
            mstore(add(0x80, m), id)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForERC721(address,address,address,uint256,bytes32)`.
            mstore(add(0x0c, m), 0xb9f36874000000000000000000000000)
            isValid := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0xa4, m, 0x20)
            isValid := and(eq(mload(m), 1), isValid)
            if iszero(or(rights, isValid)) {
                mstore(m, 0xaba69cf8) // `checkDelegateForToken(address,address,address,uint256)`.
                isValid := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x84, m, 0x20)
                isValid := and(eq(mload(m), 1), isValid)
            }
        }
    }

    /// @dev Returns the amount of an ERC20 token for `contract_`
    /// that `to` is granted rights to act on the behalf of `from`.
    /// ```
    ///     max(
    ///         v2.checkDelegateForERC20(to, from, contract_, ""),
    ///         v1.checkDelegateForContract(to, from, contract_) ? type(uint256).max : 0
    ///     )
    /// ```
    /// Returns `type(uint256).max` if `checkDelegateForContract(to, from, contract_)` returns true.
    function checkDelegateForERC20(address to, address from, address contract_)
        internal
        view
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let o := add(0x80, m)
            mstore(o, 0)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForERC20(address,address,address,bytes32)`.
            mstore(add(0x0c, m), 0xba63c817000000000000000000000000)
            amount := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0x84, o, 0x20)
            amount := mul(mload(o), amount)
            if not(amount) {
                mstore(m, 0x90c9a2d0) // `checkDelegateForContract(address,address,address)`.
                let t := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x64, m, 0x20)
                amount := or(sub(0, and(eq(mload(m), 1), t)), amount)
            }
        }
    }

    /// @dev Returns the amount of an ERC20 token for `contract_`
    /// that `to` is granted rights to act on the behalf of `from`.
    /// ```
    ///     max(
    ///         v2.checkDelegateForERC20(to, from, contract_, rights),
    ///         (rights == "" && v1.checkDelegateForContract(to, from, contract_)) ? type(uint256).max : 0
    ///     )
    /// ```
    /// Returns `type(uint256).max` if `checkDelegateForContract(to, from, contract_, rights)` returns true.
    function checkDelegateForERC20(address to, address from, address contract_, bytes32 rights)
        internal
        view
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(0x00, 0)
            mstore(add(0x80, m), rights)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForERC20(address,address,address,bytes32)`.
            mstore(add(0x0c, m), 0xba63c817000000000000000000000000)
            amount := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0x84, 0x00, 0x20)
            amount := mul(mload(0x00), amount)
            if iszero(or(rights, iszero(not(amount)))) {
                mstore(m, 0x90c9a2d0) // `checkDelegateForContract(address,address,address)`.
                let t := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x64, m, 0x20)
                amount := or(sub(0, and(eq(mload(m), 1), t)), amount)
            }
        }
    }

    /// @dev Returns the amount of an ERC1155 token `id` for `contract_`
    /// that `to` is granted rights to act on the behalf of `from`.
    /// ```
    ///     max(
    ///         v2.checkDelegateForERC1155(to, from, contract_, id, rights),
    ///         v1.checkDelegateForContract(to, from, contract_, id) ? type(uint256).max : 0
    ///     )
    /// ```
    /// Returns `type(uint256).max` if `checkDelegateForContract(to, from, contract_)` returns true.
    function checkDelegateForERC1155(address to, address from, address contract_, uint256 id)
        internal
        view
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            let o := add(0xa0, m)
            mstore(o, 0)
            mstore(add(0x80, m), id)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForERC1155(address,address,address,uint256,bytes32)`.
            mstore(add(0x0c, m), 0xb8705875000000000000000000000000)
            amount := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0xa4, o, 0x20)
            amount := mul(mload(o), amount)
            if not(amount) {
                mstore(m, 0x90c9a2d0) // `checkDelegateForContract(address,address,address)`.
                let t := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x64, m, 0x20)
                amount := or(sub(0, and(eq(mload(m), 1), t)), amount)
            }
        }
    }

    /// @dev Returns the amount of an ERC1155 token `id` for `contract_`
    /// that `to` is granted rights to act on the behalf of `from`.
    /// ```
    ///     max(
    ///         v2.checkDelegateForERC1155(to, from, contract_, id, rights),
    ///         (rights == "" && v1.checkDelegateForContract(to, from, contract_, id)) ? type(uint256).max : 0
    ///     )
    /// ```
    /// Returns `type(uint256).max` if `checkDelegateForContract(to, from, contract_, rights)` returns true.
    function checkDelegateForERC1155(
        address to,
        address from,
        address contract_,
        uint256 id,
        bytes32 rights
    ) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40)
            mstore(0x00, 0)
            mstore(add(0xa0, m), rights)
            mstore(add(0x80, m), id)
            mstore(add(0x60, m), contract_)
            mstore(add(0x4c, m), shl(96, from))
            mstore(add(0x2c, m), shl(96, to))
            // `checkDelegateForERC1155(address,address,address,uint256,bytes32)`.
            mstore(add(0x0c, m), 0xb8705875000000000000000000000000)
            amount := staticcall(gas(), DELEGATE_REGISTRY_V2, add(m, 0x1c), 0xa4, 0x00, 0x20)
            amount := mul(mload(0x00), amount)
            if iszero(or(rights, iszero(not(amount)))) {
                mstore(m, 0x90c9a2d0) // `checkDelegateForContract(address,address,address)`.
                let t := staticcall(gas(), DELEGATE_REGISTRY_V1, add(m, 0x1c), 0x64, m, 0x20)
                amount := or(sub(0, and(eq(mload(m), 1), t)), amount)
            }
        }
    }
}
