// File: @openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

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 caller.
     *
     * 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);
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * 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[EIP 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);
}

// File: contracts/antegen/AnteNFTBalanceTest.sol



// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

import {AnteTest} from "../AnteTest.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";

/// @title Checks that [HOLDER] holds at least [THRESHOLD] [NFT]s
/// @notice Ante Test to check
contract AnteNFTBalanceTest is
    AnteTest("[HOLDER] [NFT] balance remains >= [THRESHOLD]")
{
    address public factory;
    address public nftHolder;
    IERC721 public nft;
    uint256 public thresholdBalance;

    constructor(
        address _nftAddress,
        address _holderAddress,
        uint256 _thresholdBalance,
        address _testAuthor
    ) {
        factory = msg.sender;
        nft = IERC721(_nftAddress);
        nftHolder = _holderAddress;
        thresholdBalance = _thresholdBalance;

        try IERC721Metadata(_nftAddress).name() returns (string memory name) {
            protocolName = name;
        } catch {}

        bytes memory bProtocolName = bytes(protocolName);
        if (bProtocolName.length == 0) {
            try IERC721Metadata(_nftAddress).symbol() returns (
                string memory symbol
            ) {
                protocolName = symbol;
            } catch {
                protocolName = "NFT";
            }
        }

        testedContracts = [_nftAddress, _holderAddress];
        testAuthor = _testAuthor;
    }

    /// @notice test to check if [HOLDER] owns >= [THRESHOLD] [NFT]s
    /// @return true if [NFT] balance of [HOLDER] is >= [THRESHOLD]
    function checkTestPasses() public view override returns (bool) {
        return nft.balanceOf(nftHolder) >= thresholdBalance;
    }
}

// File: contracts/antegen/AnteNFTBalanceTestFactory.sol



// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

import {AnteNFTBalanceTest} from "./AnteNFTBalanceTest.sol";
import {IAnteNFTBalanceTestFactory} from "./interfaces/IAnteNFTBalanceTestFactory.sol";

error InvalidNFTAddress();
error InvalidHolderAddress();
error InvalidBalance();
error AlreadyExists();
error FailingTest();

contract AnteNFTBalanceTestFactory is IAnteNFTBalanceTestFactory {
    /// @inheritdoc IAnteNFTBalanceTestFactory
    address[] public override allNFTBalanceTests;

    /// @inheritdoc IAnteNFTBalanceTestFactory
    mapping(bytes32 => address) public override testByConfig;

    /// @inheritdoc IAnteNFTBalanceTestFactory
    function createNFTBalanceTest(
        address nftAddress,
        address holderAddress,
        uint256 thresholdBalance
    ) external override returns (address anteNFTBalanceTestAddress) {
        if (nftAddress == address(0)) {
            revert InvalidNFTAddress();
        }
        if (holderAddress == address(0)) {
            revert InvalidHolderAddress();
        }
        if (thresholdBalance == 0) {
            revert InvalidBalance();
        }

        bytes32 configHash = keccak256(
            abi.encodePacked(nftAddress, holderAddress, thresholdBalance)
        );
        address testAddr = testByConfig[configHash];
        if(testAddr != address(0)) {
            revert AlreadyExists();
        }

        AnteNFTBalanceTest anteNFTBalanceTest = new AnteNFTBalanceTest{salt: configHash}(
            nftAddress,
            holderAddress,
            thresholdBalance,
            msg.sender
        );

        if(anteNFTBalanceTest.checkTestPasses() == false) {
          revert FailingTest();
        }

        anteNFTBalanceTestAddress = address(anteNFTBalanceTest);
        allNFTBalanceTests.push(anteNFTBalanceTestAddress);
        testByConfig[configHash] = anteNFTBalanceTestAddress;

        emit AnteNFTBalanceTestCreated(
            nftAddress,
            holderAddress,
            thresholdBalance,
            anteNFTBalanceTestAddress,
            msg.sender
        );
    }
}

// File: contracts/antegen/interfaces/IAnteNFTBalanceTestFactory.sol



// SPDX-License-Identifier: GPL-3.0-only

pragma solidity ^0.8.0;

interface IAnteNFTBalanceTestFactory {
    /// @notice Emitted when an AnteTest
    /// @param nftAddress The address of the test NFT
    /// @param holderAddress The address of the owner of the NFT to test
    /// @param thresholdBalance The NFT threshold balance of the holder
    /// @param anteNftBalanceTestAddress The deployed AnteTest address
    /// @param testCreator The test author address
    event AnteNFTBalanceTestCreated(
        address nftAddress,
        address holderAddress,
        uint256 thresholdBalance,
        address anteNftBalanceTestAddress,
        address testCreator
    );

    /// @notice Deploys the AnteTest with given parameters
    /// @param nftAddress The address of the test NFT
    /// @param holderAddress The address of the owner of the NFT to test
    /// @param thresholdBalance The NFT threshold balance of the holder
    function createNFTBalanceTest(
        address nftAddress,
        address holderAddress,
        uint256 thresholdBalance
    ) external returns (address anteTestAddress);

    /// @notice Returns a single address in the allNFTBalanceTests array
    /// @param i The array index of the address to return
    /// @return The address of the i-th NFTBalanceTest created by this factory
    function allNFTBalanceTests(uint256 i) external view returns (address);

    /// @notice Returns the address of the NFTBalanceTest corresponding to a given config hash
    /// @param configHash config hash of the NFTBalanceTest to look up
    /// @return The address of the corresponding NFTBalanceTest
    function testByConfig(bytes32 configHash) external view returns (address);
}

// File: contracts/AnteTest.sol



// SPDX-License-Identifier: GPL-3.0-only

// ┏━━━┓━━━━━┏┓━━━━━━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━━━━━
// ┃┏━┓┃━━━━┏┛┗┓━━━━━━━━┃┏━━┛━━━━━━━━━━━━━━━━━━━━━━━
// ┃┗━┛┃┏━┓━┗┓┏┛┏━━┓━━━━┃┗━━┓┏┓┏━┓━┏━━┓━┏━┓━┏━━┓┏━━┓
// ┃┏━┓┃┃┏┓┓━┃┃━┃┏┓┃━━━━┃┏━━┛┣┫┃┏┓┓┗━┓┃━┃┏┓┓┃┏━┛┃┏┓┃
// ┃┃ ┃┃┃┃┃┃━┃┗┓┃┃━┫━┏┓━┃┃━━━┃┃┃┃┃┃┃┗┛┗┓┃┃┃┃┃┗━┓┃┃━┫
// ┗┛ ┗┛┗┛┗┛━┗━┛┗━━┛━┗┛━┗┛━━━┗┛┗┛┗┛┗━━━┛┗┛┗┛┗━━┛┗━━┛
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

pragma solidity ^0.8.0;

import "./interfaces/IAnteTest.sol";

/// @title Ante V0.6 Ante Test smart contract
/// @notice Abstract inheritable contract that supplies syntactic sugar for writing Ante Tests
/// @dev Usage: contract YourAnteTest is AnteTest("String descriptor of test") { ... }
abstract contract AnteTest is IAnteTest {
    /// @inheritdoc IAnteTest
    address public override testAuthor;
    /// @inheritdoc IAnteTest
    string public override testName;
    /// @inheritdoc IAnteTest
    string public override protocolName;
    /// @inheritdoc IAnteTest
    address[] public override testedContracts;

    /// @dev testedContracts and protocolName are optional parameters which should
    /// be set in the constructor of your AnteTest
    /// @param _testName The name of the Ante Test
    constructor(string memory _testName) {
        testAuthor = msg.sender;
        testName = _testName;
    }

    /// @inheritdoc IAnteTest
    function setStateAndCheckTestPasses(bytes memory _state) external override returns (bool) {
        if (_state.length > 0) {
            _setState(_state);
        }
        return checkTestPasses();
    }

    /// @notice Returns the testedContracts array of addresses
    /// @return The list of tested contracts as an array of addresses
    function getTestedContracts() external view returns (address[] memory) {
        return testedContracts;
    }

    /// @notice Sets the author of the Ante Test
    /// @dev This can only be called by the current author, which is the deployer initially
    /// @param _testAuthor The address of the test author
    function setTestAuthor(address _testAuthor) external {
        require(msg.sender == testAuthor, "Only the current testAuthor can set a new test author");
        require(_testAuthor != address(0), "ANTE: Test author cannot be the zero address");
        address previousAuthor = testAuthor;
        testAuthor = _testAuthor;

        emit TestAuthorChanged(previousAuthor, _testAuthor);
    }

    /// @notice Returns a string of comma delimited types used for setting the AnteTest state
    /// @return The types of the state variables
    function getStateTypes() external pure virtual override returns (string memory) {
        return "";
    }

    /// @inheritdoc IAnteTest
    function checkTestPasses() public virtual override returns (bool passes) {}

    /// @notice Function containing the logic to set the AnteTest state
    function _setState(bytes memory) internal virtual {}
}

// File: contracts/interfaces/IAnteTest.sol



// SPDX-License-Identifier: GPL-3.0-only

// ┏━━━┓━━━━━┏┓━━━━━━━━━┏━━━┓━━━━━━━━━━━━━━━━━━━━━━━
// ┃┏━┓┃━━━━┏┛┗┓━━━━━━━━┃┏━━┛━━━━━━━━━━━━━━━━━━━━━━━
// ┃┗━┛┃┏━┓━┗┓┏┛┏━━┓━━━━┃┗━━┓┏┓┏━┓━┏━━┓━┏━┓━┏━━┓┏━━┓
// ┃┏━┓┃┃┏┓┓━┃┃━┃┏┓┃━━━━┃┏━━┛┣┫┃┏┓┓┗━┓┃━┃┏┓┓┃┏━┛┃┏┓┃
// ┃┃ ┃┃┃┃┃┃━┃┗┓┃┃━┫━┏┓━┃┃━━━┃┃┃┃┃┃┃┗┛┗┓┃┃┃┃┃┗━┓┃┃━┫
// ┗┛ ┗┛┗┛┗┛━┗━┛┗━━┛━┗┛━┗┛━━━┗┛┗┛┗┛┗━━━┛┗┛┗┛┗━━┛┗━━┛
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

pragma solidity ^0.8.0;

/// @title The interface for the Ante V0.6 Ante Test
/// @notice The Ante V0.6 Ante Test wraps test logic for verifying fundamental invariants of a protocol
interface IAnteTest {
    /// @notice Emitted when the test author is changed
    /// @param previousAuthor The address of the previous author
    /// @param newAuthor The address of the new author
    event TestAuthorChanged(address indexed previousAuthor, address indexed newAuthor);

    /// @notice Function containing the logic to set the AnteTest state and call checkTestPasses
    /// @param _state The encoded data required to set the test state
    /// @return A single bool indicating if the Ante Test passes/fails
    function setStateAndCheckTestPasses(bytes memory _state) external returns (bool);

    /// @notice Function containing test logic to inspect the protocol invariant
    /// @dev This should usually return True
    /// @return A single bool indicating if the Ante Test passes/fails
    function checkTestPasses() external returns (bool);

    /// @notice Returns the author of the Ante Test
    /// @dev This overrides the auto-generated getter for testAuthor as a public var
    /// @return The address of the test author
    function testAuthor() external view returns (address);

    /// @notice Sets the author of the Ante Test
    /// @dev This can only be called by the current author, which is the deployer initially
    /// @param _testAuthor The address of the test author
    function setTestAuthor(address _testAuthor) external;

    /// @notice Returns the name of the protocol the Ante Test is testing
    /// @dev This overrides the auto-generated getter for protocolName as a public var
    /// @return The name of the protocol in string format
    function protocolName() external view returns (string memory);

    /// @notice Returns a single address in the testedContracts array
    /// @dev This overrides the auto-generated getter for testedContracts [] as a public var
    /// @param i The array index of the address to return
    /// @return The address of the i-th element in the list of tested contracts
    function testedContracts(uint256 i) external view returns (address);

    /// @notice Returns the name of the Ante Test
    /// @dev This overrides the auto-generated getter for testName as a public var
    /// @return The name of the Ante Test in string format
    function testName() external view returns (string memory);

    /// @notice Returns a string of comma delimited types used for setting the AnteTest state
    /// @return The types of the state variables
    function getStateTypes() external pure returns (string memory);
}