// File: src/BillyTheBull.sol



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

import { NFTOutlet } from "./NFTOutlet.sol";
import { IPuzzle } from "./interfaces/IPuzzle.sol";
import { IERC721 } from "./interfaces/IERC721.sol";

/**
You are Billy the Bull, the most infamous of NFT influencers.
Your trades move markets,
    your lambos fill Instagram timelines,
        your Twitter threads are the stuff of legends.

There's just one problem: you're broke.

With just one more trade, you'd certainly earn it all back.
But your mom is done lending you money, and there's nowhere left to turn.

A new mint has just opened up on The NFT Outlet. You _know_ it'll be a hit.
Your strategy is clear: You need 2 NFTs. Sell one. Keep the other for your collection.
If only there were a way to get them without paying...
*/
contract BillyTheBull is IPuzzle {
    address public owner;
    NFTOutlet public nftOutlet;
    uint public nftPrice;
    uint cachedSolution;

    event OwnsBoth(address indexed wallet, uint tokenId1, uint tokenId2);

    constructor() {
        owner = address(tx.origin);
    }

    function initialize(address _nftOutlet, uint _startingNftPrice) external {
        require(address(nftOutlet) == address(0), "already initialized");
        nftOutlet = NFTOutlet(_nftOutlet);
        nftPrice = _startingNftPrice;
    }

    function name() public pure returns (string memory) {
        return "Billy the Bull";
    }

    function generate(address _seed) public pure returns (uint256 start) {
        start = uint256(keccak256(abi.encode(_seed)));
    }

    function verify(uint _start, uint _solution) public noTampering(_solution) returns (bool) {
        // decode & cache input arguments
        uint tokenId1 = _start >> 128;
        uint tokenId2 = uint(uint128(_start));
        address wallet = address(uint160(_solution));
        IERC721 nftToBuy = nftOutlet.nftDealOfTheDay();

        // use external logic with local storage to determine the ~~magic flag~~
        bytes32 pre = keccak256(abi.encode(owner, nftOutlet, nftPrice, cachedSolution, nftToBuy.totalSupply()));
        (, bytes memory d0) = wallet.delegatecall(abi.encodeWithSignature("getMagicFlag()"));
        bytes32 post = keccak256(abi.encode(owner, nftOutlet, nftPrice, cachedSolution, nftToBuy.totalSupply()));
        require(pre == post, "bad boy");

        // ensure we have a unique magic flag
        bytes memory magicFlag = abi.decode(d0, (bytes));
        require(nftOutlet.magicFlagsUsed(keccak256(magicFlag)) == false, "no reusing flags");
        nftOutlet.setMagicFlagUsed(keccak256(magicFlag));

        // alright houdini, pay without paying
        uint balanceBefore = nftOutlet.paymentToken().balanceOf(wallet);
        (bool s1, bytes memory d1) = address(nftOutlet).call(
            abi.encodeWithSignature("pay(address,uint256)", wallet, _incrementNFTPrice(1e18))
        );
        require(!_returnedFalse(s1, d1), "transfer must succeed");
        require(balanceBefore == nftOutlet.paymentToken().balanceOf(wallet), "sneaky sneaky");

        // mint an nft to your wallet
        (bool s2, bytes memory d2) = address(nftOutlet).call(
            abi.encodeWithSignature("mint(address,uint256)", wallet, tokenId1)
        );
        require(!_returnedFalse(s2, d2), "mint must succeed");

        // did you end up with both nfts?
        require(nftToBuy.ownerOf(tokenId1) == wallet, "must own token id 1");
        require(nftToBuy.ownerOf(tokenId2) == wallet, "must own token id 2");
        emit OwnsBoth(wallet, tokenId1, tokenId2);

        // you win ... if you got the magic flag right
        return uint(keccak256(magicFlag)) == _solution;
    }

    function _returnedFalse(bool success, bytes memory data) internal pure returns (bool) {
        return success && !abi.decode(data, (bool));
    }

    function _incrementNFTPrice(uint _incrementBy) public returns (uint oldPrice) {
        require(_incrementBy < 10e18, "lets keep this affordable");
        oldPrice = nftPrice;
        nftPrice = nftPrice + _incrementBy;
    }

    modifier noTampering(uint _solution) {
        if (cachedSolution == 0) {
            cachedSolution = _solution;
            _;
            cachedSolution = 0;
        } else {
            require(cachedSolution == _solution, "max one solution");
            _;
        }
    }
}

// File: src/NFTOutlet.sol



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

import { BillyTheBull } from "./BillyTheBull.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { IERC721 } from "./interfaces/IERC721.sol";

contract NFTOutlet {
    address immutable puzzle;

    ERC20 public paymentToken;
    IERC721 public nftDealOfTheDay;
    address treasury;

    mapping(address => bool) public validAssets;
    mapping(address => bool) public mintsClaimed;
    mapping(bytes32 => bool) public magicFlagsUsed;

    constructor(
        address _puzzle,
        address[] memory _paymentTokens,
        address[] memory _nfts
    ) {
        puzzle = _puzzle;
        paymentToken = ERC20(_paymentTokens[0]);
        nftDealOfTheDay = IERC721(_nfts[0]);

        for (uint256 i = 0; i < _paymentTokens.length; i++) {
            validAssets[_paymentTokens[i]] = true;
        }

        for (uint256 i = 0; i < _nfts.length; i++) {
            validAssets[_nfts[i]] = true;
        }
    }

    /////////////////////////
    /////// MODIFIERS ///////
    /////////////////////////

    modifier onlyPuzzle() {
        require(msg.sender == puzzle, "only puzzle");
        _;
    }

    modifier onlyPuzzleOwner() {
        require(msg.sender == BillyTheBull(puzzle).owner(), "only puzzle owner");
        _;
    }

    /////////////////////////
    //// PAYMENT ACTIONS ////
    /////////////////////////

    function pay(address _from, uint256 _amount) public onlyPuzzle returns (bool) {
        require(_from != address(0), "no zero address");
        try paymentToken.transferFrom(_from, address(this), _amount) returns (bool) {
            require(
                keccak256(abi.encode(_amount)) !=
                0x420badbabe420badbabe420badbabe420badbabe420badbabe420badbabe6969,
                "too immature"
            );
            return true;
        } catch {
            require(uint(uint32(_amount)) <= 4294967295, "invalid amount");
            return false;
        }
    }

    /////////////////////////
    //// MINTING ACTIONS ////
    /////////////////////////

    function mint(address _to, uint256 _tokenId) public onlyPuzzle returns (bool) {
        require(!mintsClaimed[_to], "already claimed");
        try nftDealOfTheDay.safeMint(_to, _tokenId) {
            mintsClaimed[_to] = true;
            return true;
        } catch {
            return false;
        }
    }

    /////////////////////////
    ///// ADMIN ACTIONS /////
    /////////////////////////

    function setMagicFlagUsed(bytes32 _magicFlag) onlyPuzzle public {
        magicFlagsUsed[_magicFlag] = true;
    }

    function changePaymentToken(address _newStablecoin) public onlyPuzzleOwner {
        require(validAssets[_newStablecoin], "no sneaky assets");
        paymentToken = ERC20(_newStablecoin);
    }

    function rescueERC20(address _token) public {
        ERC20(_token).transfer(treasury, ERC20(_token).balanceOf(address(this)));
    }
}

// File: src/interfaces/IPuzzle.sol



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

interface IPuzzle {
    /// @notice Returns the puzzle's name.
    /// @return The puzzle's name.
    function name() external pure returns (string memory);

    /// @notice Generates the puzzle's starting position based on a seed.
    /// @dev The seed is intended to be `msg.sender` of some wrapper function or
    /// call.
    /// @param _seed The seed to use to generate the puzzle.
    /// @return The puzzle's starting position.
    function generate(address _seed) external returns (uint256);

    /// @notice Verifies that a solution is valid for the puzzle.
    /// @dev `_start` is intended to be an output from {IPuzzle-generate}.
    /// @param _start The puzzle's starting position.
    /// @param _solution The solution to the puzzle.
    /// @return Whether the solution is valid.
    function verify(uint256 _start, uint256 _solution) external returns (bool);
}

// File: src/interfaces/IERC721.sol



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

import { ERC721 } from "@solmate/tokens/ERC721.sol";

abstract contract IERC721 is ERC721 {
    function initialize(address _puzzle) external virtual;
    function safeMint(address to, uint256 tokenId) external virtual;
    function totalSupply() external view virtual returns (uint256);
}

// File: lib/solmate/src/tokens/ERC20.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// File: lib/solmate/src/tokens/ERC721.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) public view virtual returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(from == _ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(_ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = _ownerOf[id];

        require(owner != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            _balanceOf[owner]--;
        }

        delete _ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC721TokenReceiver.onERC721Received.selector;
    }
}