// File: contracts/bonklet/BonkletBidder.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import "../access/PermitControl.sol";
import "../interfaces/IBonklerAuction.sol";
import "../interfaces/IBonklerNFT.sol";
import "../interfaces/ITiny721.sol";

/**
	Thrown when attempting to stage a bid that does not meet minimum requirements.

	@param bidAmount The bid amount that the caller attempted to stage.
*/
error InvalidBidAmount (
	uint256 bidAmount
);

/**
	Thrown when including another staged bid would overfractionalize a Bonkler.

	@param bonklerId The ID of the Bonkler which would overfractionalize.
*/
error CannotOverfractionalize (
	uint256 bonklerId
);

/**
	Thrown when attempting to stage a bid on an old Bonkler.

	@param bonklerId The ID of the Bonkler which the bid was attempted on.
*/
error InvalidBonklerBid (
	uint256 bonklerId
);

/**
	Thrown when attempting to stage a bid while already winning an auction.

	@param bonklerId The ID of the Bonkler which the bid was attempted on.
*/
error AlreadyWinningAuction (
	uint256 bonklerId
);

/**
	Thrown when attempting to withdraw a bid from a settled auction.

	@param bonklerId The ID of the Bonkler which the withdraw was attempted on.
*/
error CannotWithdrawSettledItem (
	uint256 bonklerId
);

/**
	Thrown when attempting to withdraw a bid from a live auction.

	@param bonklerId The ID of the Bonkler which the withdraw was attempted on.
*/
error CannotWithdrawActiveBid (
	uint256 bonklerId
);

/**
	Thrown when unable to withdraw Ether from the contract.
*/
error EtherTransferWasUnsuccessful ();

/**
	Thrown when attempting to settle a won bid on an invalid Bonkler.

	@param bonklerId The ID of the Bonkler which the settle was attempted on.
*/
error CannotSettle (
	uint256 bonklerId
);

/// Thrown when attempting to vote without a valid Bonklet.
error Unauthorized ();

/**
	Thrown when attempting to redeem or transfer an unsettled Bonkler.

	@param bonklerId The ID of the Bonkler which the operation was attempted on.
*/
error BonklerMustBeSettled (
	uint256 bonklerId
);

/**
	Thrown when attempting to redeem a Bonklet for an unredeemed Bonkler.

	@param bonklerId The ID of the unredeemed Bonkler.
*/
error CannotClaimUnredeemedBonkler (
	uint256 bonklerId
);

/**
	@custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM
	@title A fractionalized bidding system for Bonkler.
	@author Tim Clancy <tim-clancy.eth>

	Bonkler is a religious artifact bestowed to us by the Remilia Corporation. 
	This contract allows callers to pool their Ether together to bid for shared 
	Bonklers. Fractional Bonkler shares are represented as ERC-721 tokens called 
	Bonklets. Fractional holders may vote to redeem or transfer their shared 
	Bonkler.

	@custom:date April 20th, 2023.
*/
contract BonkletBidder is PermitControl, ReentrancyGuard {

	/// The address of the Bonkler auction contract.
	address immutable public BONKLER_AUCTION;

	/// The address of the Bonkler NFT contract.
	address immutable public BONKLER;

	/// The address of the Bonklet NFT contract.
	address immutable public BONKLET;

	/// The minimum bid accepted for joining a Bonkler fractionalization.
	uint256 immutable public BID_MINIMUM;

	/// The maximum number of bidders on a Bonkler.
	uint256 immutable public BIDDER_LIMIT;

	/// The quorum required for conducting a redemption.
	uint256 immutable public REDEMPTION_QUORUM;

	/// A mapping of specific Bonkler IDs to the total Ether staged to bid on it.
	mapping ( uint256 => uint256 ) public stagedTotal;

	/// A mapping of specific Bonkler IDs to a mapping of per-caller staged bids.
	mapping ( uint256 => mapping ( address => uint256 )) public callerStagedBids;

	/**
		This struct defines a set of specific bidders who participated in a Bonkler 
		round.

		@param bidders An array of unique bidder addresses.
		@param didBid A mapping to track whether a particular address has yet bid.
	*/
	struct BidderSet {
		address[] bidders;
		mapping ( address => bool ) didBid;
	}

	/// A mapping of specific Bonkler IDs to bidder sets.
	mapping ( uint256 => BidderSet ) private _bidderSets;

	/// A mapping of Bonkler IDs to whether their receipt has been settled.
	mapping ( uint256 => bool ) public settled;

	/**
		This struct defines relevant data required for tracking the voting powers 
		of a specific Bonklet. This information also impacts the display of 
		metadata.

		@param bonklerId The ID of the corresponding Bonkler that this Bonklet is 
			fractionalizing.
		@param stagedEther The amount of Ether represented by this Bonklet fraction.
	*/
	struct BonkletData {
		uint128 bonklerId;
		uint128 stagedEther;
	}

	/// A mapping of Bonklet token ID to relevant voting-based metadata.
	mapping ( uint256 => BonkletData ) public bonkletData;

	/**
		A mapping of a Bonklet token ID to whether it has voted in favor of Bonkler 
		redemption.
	*/
	mapping ( uint256 => bool ) public redemptionVoted;

	/**
		A mapping from a Bonkler token ID to the number of shares that have voted 
		in favor of redemption.
	*/
	mapping ( uint256 => uint256 ) public redemptionShares;

	/// A mapping of Bonkler IDs to their redeemed shares.
	mapping ( uint256 => uint256 ) public redeemed;

	/**
		A mapping of a Bonklet token ID to whether it has voted in favor of Bonkler 
		transfer to a particular destination address.
	*/
	mapping ( uint256 => mapping ( address => bool )) public transferVoted;

	/**
		A mapping from a Bonkler token ID to the number of shares that have voted 
		in favor of transfer to a particular destination address.
	*/
	mapping ( uint256 => mapping ( address => uint256 )) public transferShares;

	/**
		This event is emitted when a bid is staged.

		@param bidder The address of the caller who created the bid.
		@param bonklerId The Bonkler ID being bid on.
		@param amount The amount of Ether just staged for bidding.
		@param totalAmount The total amount of Ether ready to bid.
	*/
	event BidStaged (
		address indexed bidder,
		uint256 bonklerId,
		uint256 amount,
		uint256 totalAmount
	);

	/**
		This event is emitted when a bid is executed against the Bonkler auction 
		contract.

		@param bidder The address of the caller who created the bid.
		@param bonklerId The Bonkler ID being bid on.
		@param totalAmount The total amount of Ether ready to bid.
	*/
	event BidExecuted (
		address indexed bidder,
		uint256 bonklerId,
		uint256 totalAmount
	);

	/**
		This event is emitted when a failed bid is withdrawn.

		@param bidder The address of the caller who created the bid.
		@param bonklerId The Bonkler ID being bid on.
		@param totalAmount The total amount of Ether withdrawn.
	*/
	event BidWithdrawn (
		address indexed bidder,
		uint256 bonklerId,
		uint256 totalAmount
	);

	/**
		This event is emitted when a won Bonkler is settled.

		@param settler The address of the caller who performs the settling.
		@param bonklerId The Bonkler ID being settled.
	*/
	event Settled (
		address indexed settler,
		uint256 bonklerId
	);

	/**
		This event records when a vote has been placed for redeeming a Bonkler.

		@param voter The address of the voter.
		@param bonklerId The ID of the Bonkler being voted on.
		@param bonkletId The ID of the Bonklet being voted with.
		@param signal Whether or not the voter is in favor.
		@param shares The number of shares this voter has.
	*/
	event RedemptionVote (
		address indexed voter,
		uint256 bonklerId,
		uint256 bonkletId,
		bool signal,
		uint256 shares
	);

	/**
		This event records when a vote for redeeming a Bonkler has been executed.

		@param voter The final voter who executed the redemption.
		@param bonklerId The ID of the Bonkler being redeemed.
		@param bonkletId The ID of the Bonklet which triggered the redemption.
	*/
	event RedemptionExecuted (
		address indexed voter,
		uint256 bonklerId,
		uint256 bonkletId
	);

	/**
		This event records when a caller claims a redeemed portion of a Bonkler 
		using a Bonklet.

		@param claimant The address of the caller who claimed.
		@param bonkletId The ID of the Bonklet which claimed.
		@param reward The Ether rewarded for this claim.
	*/
	event BonkletClaimed (
		address indexed claimant,
		uint256 bonkletId,
		uint256 reward
	);

	/**
		This event records when a vote has been placed for transferring a Bonkler 
		to a particular address.

		@param voter The address of the voter.
		@param bonklerId The ID of the Bonkler being voted on.
		@param bonkletId The ID of the Bonklet being voted with.
		@param destination The destination address of the Bonkler being voted for.
		@param signal Whether or not the voter is in favor.
		@param shares The number of shares this voter has.
	*/
	event TransferVote (
		address indexed voter,
		uint256 bonklerId,
		uint256 bonkletId,
		address destination,
		bool signal,
		uint256 shares
	);

	/**
		This event records when a vote for redeeming a Bonkler has been executed.

		@param voter The final voter who executed the redemption.
		@param bonklerId The ID of the Bonkler being redeemed.
		@param bonkletId The ID of the Bonklet which triggered the redemption.
		@param destination The destination address where the Bonkler was sent.
	*/
	event TransferExecuted (
		address indexed voter,
		uint256 bonklerId,
		uint256 bonkletId,
		address destination
	);

	/**
		Construct a new instance of the Bonkle bid fractionalizer configured with 
		the given immutable contract addresses.

		@param _bonklerAuction The address of the Bonkler auction contract.
		@param _bonkler The address of the Bonkler NFT contract.
		@param _bonklet The address of the fractional Bonklet NFT contract.
		@param _bidMinimum The minimum bid accepted for fractionalization.
		@param _bidderLimit The maximum number of bidders who may share 
			fractionalization on a first-come, first-serve basis.
		@param _redemptionQuorum The threshold required to reach a quorum for a 
			redemption vote.
	*/
	constructor (
		address _bonklerAuction,
		address _bonkler,
		address _bonklet,
		uint256 _bidMinimum,
		uint256 _bidderLimit,
		uint256 _redemptionQuorum
	) {
		BONKLER_AUCTION = _bonklerAuction;
		BONKLER = _bonkler;
		BONKLET = _bonklet;
		BID_MINIMUM = _bidMinimum;
		BIDDER_LIMIT = _bidderLimit;
		REDEMPTION_QUORUM = _redemptionQuorum;
	}

	/**
		Stage a bid for submission to the Bonkler auction contract. If the 
		resources for a bid are obtained, one is automatically created.

		@param _bonklerId The ID of the Bonkler to withdraw failed bid Ether for.
		@param _generationHash The generation hash to submit for the Bonkler.
	*/
	function stageBid (
		uint256 _bonklerId,
		uint256 _generationHash
	) external payable nonReentrant {

		// Revert if the provided bid is too small.
		if (msg.value < BID_MINIMUM) {
			revert InvalidBidAmount(msg.value);
		}

		// Check for, and record, whether the current bidder is new.
		BidderSet storage bidderSet = _bidderSets[_bonklerId];
		if (!bidderSet.didBid[msg.sender]) {
			bidderSet.bidders.push(msg.sender);
			bidderSet.didBid[msg.sender] = true;

			// Prevent exceeding the limit on the number of bidders on a Bonkler.
			if (bidderSet.bidders.length > BIDDER_LIMIT) {
				revert CannotOverfractionalize(_bonklerId);
			}
		}

		// Retrieve the current auction details.
		AuctionData memory auction = IBonklerAuction(BONKLER_AUCTION).auctionData();

		// Revert if we are bidding on a not-current Bonkler.
		if (auction.bonklerId != _bonklerId) {
			revert InvalidBonklerBid(_bonklerId);
		}

		// Revert if we are already winning the auction.
		if (auction.bidder == address(this)) {
			revert AlreadyWinningAuction(_bonklerId);
		}

		// Store the current message value against this auction.
		uint256 newTotal;
		unchecked {
			newTotal = stagedTotal[_bonklerId] + msg.value;
			callerStagedBids[_bonklerId][msg.sender] += msg.value;
		}
		stagedTotal[_bonklerId] = newTotal;

		// Emit a bid-staging event.
		emit BidStaged(msg.sender, _bonklerId, msg.value, newTotal);

		// If we have obtained sufficient Ether, emit an event then create a bid.
		if (auction.amount < newTotal) {
			emit BidExecuted(msg.sender, _bonklerId, newTotal);

			// Create the bid by calling out to the Bonkler auction contract.
			IBonklerAuction(BONKLER_AUCTION).createBid{ value: newTotal }(
				_bonklerId,
				_generationHash
			);
		}
	}

	/**
		Permit callers to withdraw embedded Ether for an intended bid if the bid is 
		not active in the current auction and the bid failed.

		@param _bonklerId The ID of the Bonkler to withdraw failed bid Ether for.
	*/
	function withdrawBid (
		uint256 _bonklerId
	) external nonReentrant {

		/*
			Prevent withdrawals if the Bonkler is owned by this contract, or if 
			receipt of the Bonkler has already been settled.
		*/
		if (
			IERC721(BONKLER).ownerOf(_bonklerId) == address(this) || 
			settled[_bonklerId]
		) {
			revert CannotWithdrawSettledItem(_bonklerId);
		}

		// Retrieve the current auction details.
		AuctionData memory auction = IBonklerAuction(BONKLER_AUCTION).auctionData();

		// Revert if attempting to withdraw from the active, unsettled bid.
		if (auction.bonklerId == _bonklerId && !auction.settled) {
			revert CannotWithdrawActiveBid(auction.bonklerId);
		}

		// Track the caller withdrawal.
		uint256 withdrawal = callerStagedBids[_bonklerId][msg.sender];
		if (withdrawal > 0) {
			unchecked {
				stagedTotal[_bonklerId] -= withdrawal;
			}
			callerStagedBids[_bonklerId][msg.sender] = 0;

			// Return the caller their Ether.
			(bool success, ) = (msg.sender).call{ value: withdrawal }("");
			if (!success) {
				revert EtherTransferWasUnsuccessful();
			}

			// Emit an event.
			emit BidWithdrawn(msg.sender, _bonklerId, withdrawal);
		}
	}

	/**
		Allow for Bonklets to be created from victorious auctions.

		@param _bonklerId The ID of the Bonkler to settle.
	*/
	function settle (
		uint256 _bonklerId
	) external nonReentrant {
		
		// Prevent settlement of unowned or already-settled Bonklers.
		if (
			settled[_bonklerId] ||
			IERC721(BONKLER).ownerOf(_bonklerId) != address(this) 
		) {
			revert CannotSettle(_bonklerId);
		}

		// Mint a Bonklet to every participating bidder given their score.
		uint256 tokenId = ITiny721(BONKLET).totalSupply() + 1;
		address[] memory bidders = _bidderSets[_bonklerId].bidders;
		for (uint256 i; i < bidders.length; ) {
			address bidder = bidders[i];
			ITiny721(BONKLET).mint_Qgo(bidder, 1);

			// Store relevant voting information for each Bonklet.
			unchecked {
				bonkletData[tokenId + i] = BonkletData({
					bonklerId: uint128(_bonklerId),
					stagedEther: uint128(callerStagedBids[_bonklerId][bidder])
				});
				++i;
			}
		}
		settled[_bonklerId] = true;

		// Emit an event.
		emit Settled(msg.sender, _bonklerId);
	}

	/**
		Toggle support for a quorum vote to redeem a Bonkler.

		@param _bonkletId The ID of the Bonklet to vote with.
	*/
	function redeem (
		uint256 _bonkletId
	) external nonReentrant {

		// Only allow Bonklet holders to vote with their own Bonklets.
		if (IERC721(BONKLET).ownerOf(_bonkletId) != msg.sender) {
			revert Unauthorized();
		}

		// If the Bonklet had previously voted, remove its support.
		BonkletData memory bonklet = bonkletData[_bonkletId];
		uint256 bonklerId = bonklet.bonklerId;
		if (redemptionVoted[_bonkletId]) {
			redemptionVoted[_bonkletId] = false;
			unchecked {
				redemptionShares[bonklerId] -= bonklet.stagedEther;
			}

			// Emit an event recording the vote.
			emit RedemptionVote(
				msg.sender,
				bonklerId,
				_bonkletId,
				false,
				bonklet.stagedEther
			);

		// Otherwise, record the Bonklet vote.
		} else {
			redemptionVoted[_bonkletId] = true;
			unchecked {
				redemptionShares[bonklerId] += bonklet.stagedEther;
			}

			// Emit an event recording the vote.
			emit RedemptionVote(
				msg.sender,
				bonklerId,
				_bonkletId,
				true,
				bonklet.stagedEther
			);

			// If redemption quorum is achieved, redeem.
			uint256 power;
			unchecked {
				power = redemptionShares[bonklerId] * 100 / stagedTotal[bonklerId];
			}
			if (power > REDEMPTION_QUORUM) {
				redeemed[bonklerId] = IBonklerNFT(BONKLER).getBonklerShares(bonklerId);
				IBonklerNFT(BONKLER).redeemBonkler(bonklerId);

				// Emit an event recording execution of the redemption vote.
				emit RedemptionExecuted(
					msg.sender,
					bonklerId,
					_bonkletId
				);
			}
		}
	}

	/**
		This function allows Bonklet holders to claim their share of rewards from 
		redeemed Bonklers. Calling this function requires approving this contract 
		to transfer Bonklets on behalf of the caller. After claiming, the Bonklet 
		is transferred to be held in eternal escrow by this contract.

		@param _bonkletId The ID of the Bonklet to vote with.
	*/
	function claim (
		uint256 _bonkletId
	) external nonReentrant {

		// Only allow Bonklet holders to claim with their own Bonklets.
		if (IERC721(BONKLET).ownerOf(_bonkletId) != msg.sender) {
			revert Unauthorized();
		}

		// Prevent claiming unredeeemed Bonklers.
		BonkletData memory bonklet = bonkletData[_bonkletId];
		uint256 bonklerId = bonklet.bonklerId;
		if (redeemed[bonklerId] == 0) {
			revert CannotClaimUnredeemedBonkler(bonklerId);
		}

		// Transfer the caller Bonklet to be held in eternal escrow.
		IERC721(BONKLET).transferFrom(
			msg.sender,
			address(this),
			_bonkletId
		);

		// Return the claimant's portion of the redeemed reward.
		uint256 reward;
		unchecked {
			reward = bonklet.stagedEther * redeemed[bonklerId]
				/ stagedTotal[bonklerId];
		}
		(bool success, ) = (msg.sender).call{ value: reward }("");
		if (!success) {
			revert EtherTransferWasUnsuccessful();
		}

		// Emit an event recording this claim.
		emit BonkletClaimed(msg.sender, _bonkletId, reward);
	}

	/**
		Toggle support for a unanimous vote to transfer a Bonkler.

		@param _bonkletId The ID of the Bonklet to vote with.
		@param _destination The destination being voted on for the Bonkler transfer.
	*/
	function transfer (
		uint256 _bonkletId,
		address _destination
	) external nonReentrant {

		// Only allow Bonklet holders to vote with their own Bonklets.
		if (IERC721(BONKLET).ownerOf(_bonkletId) != msg.sender) {
			revert Unauthorized();
		}

		// If the Bonklet had previously voted, remove its support.
		BonkletData memory bonklet = bonkletData[_bonkletId];
		uint256 bonklerId = bonklet.bonklerId;
		if (transferVoted[_bonkletId][_destination]) {
			transferVoted[_bonkletId][_destination] = false;
			unchecked {
				transferShares[bonklerId][_destination] -= bonklet.stagedEther;
			}

			// Emit an event recording the vote.
			emit TransferVote(
				msg.sender,
				bonklerId,
				_bonkletId,
				_destination,
				false,
				bonklet.stagedEther
			);

		// Otherwise, record the Bonklet vote.
		} else {
			transferVoted[_bonkletId][_destination] = true;
			unchecked {
				transferShares[bonklerId][_destination] += bonklet.stagedEther;
			}

			// Emit an event recording the vote.
			emit TransferVote(
				msg.sender,
				bonklerId,
				_bonkletId,
				_destination,
				true,
				bonklet.stagedEther
			);

			// If transfer unanimity is achieved, transfer.
			if (transferShares[bonklerId][_destination] == stagedTotal[bonklerId]) {
				IERC721(BONKLER).transferFrom(
					address(this),
					_destination,
					bonklerId
				);
				
				// Emit an event recording execution of the transfer vote.
				emit TransferExecuted(
					msg.sender,
					bonklerId,
					_bonkletId,
					_destination
				);
			}
		}
	}
}

// File: @openzeppelin/contracts/security/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.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 be 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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * 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: contracts/access/PermitControl.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
	@custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM
	@title An advanced permission-management contract.
	@author Tim Clancy <@_Enoch>

	This contract allows for a contract owner to delegate specific rights to
	external addresses. Additionally, these rights can be gated behind certain
	sets of circumstances and granted expiration times. This is useful for some
	more finely-grained access control in contracts.

	The owner of this contract is always a fully-permissioned super-administrator.

	@custom:date August 23rd, 2021.
*/
abstract contract PermitControl is Ownable {
	using Address for address;

	/// A special reserved constant for representing no rights.
	bytes32 public constant ZERO_RIGHT = hex"00000000000000000000000000000000";

	/// A special constant specifying the unique, universal-rights circumstance.
	bytes32 public constant UNIVERSAL = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";

	/**
		A special constant specifying the unique manager right. This right allows an
		address to freely-manipulate the `managedRight` mapping.
	*/
	bytes32 public constant MANAGER = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";

	/**
		A mapping of per-address permissions to the circumstances, represented as
		an additional layer of generic bytes32 data, under which the addresses have
		various permits. A permit in this sense is represented by a per-circumstance
		mapping which couples some right, represented as a generic bytes32, to an
		expiration time wherein the right may no longer be exercised. An expiration
		time of 0 indicates that there is in fact no permit for the specified
		address to exercise the specified right under the specified circumstance.

		@dev Universal rights MUST be stored under the 0xFFFFFFFFFFFFFFFFFFFFFFFF...
		max-integer circumstance. Perpetual rights may be given an expiry time of
		max-integer.
	*/
	mapping ( address => mapping( bytes32 => mapping( bytes32 => uint256 ))) 
		public permissions;

	/**
		An additional mapping of managed rights to manager rights. This mapping
		represents the administrator relationship that various rights have with one
		another. An address with a manager right may freely set permits for that
		manager right's managed rights. Each right may be managed by only one other
		right.
	*/
	mapping ( bytes32 => bytes32 ) public managerRight;

	/**
		An event emitted when an address has a permit updated. This event captures,
		through its various parameter combinations, the cases of granting a permit,
		updating the expiration time of a permit, or revoking a permit.

		@param updater The address which has updated the permit.
		@param updatee The address whose permit was updated.
		@param circumstance The circumstance wherein the permit was updated.
		@param role The role which was updated.
		@param expirationTime The time when the permit expires.
	*/
	event PermitUpdated (
		address indexed updater,
		address indexed updatee,
		bytes32 circumstance,
		bytes32 indexed role,
		uint256 expirationTime
	);

	/**
		An event emitted when a management relationship in `managerRight` is
		updated. This event captures adding and revoking management permissions via
		observing the update history of the `managerRight` value.

		@param manager The address of the manager performing this update.
		@param managedRight The right which had its manager updated.
		@param managerRight The new manager right which was updated to.
	*/
	event ManagementUpdated (
		address indexed manager,
		bytes32 indexed managedRight,
		bytes32 indexed managerRight
	);

	/**
		A modifier which allows only the super-administrative owner or addresses
		with a specified valid right to perform a call.

		@param _circumstance The circumstance under which to check for the validity
			of the specified `right`.
		@param _right The right to validate for the calling address. It must be
			non-expired and exist within the specified `_circumstance`.
	*/
	modifier hasValidPermit (
		bytes32 _circumstance,
		bytes32 _right
	) {
		require(
			_msgSender() == owner() || hasRight(_msgSender(), _circumstance, _right),
			"P1"
		);
		_;
	}

	/**
		Set the `_managerRight` whose `UNIVERSAL` holders may freely manage the
		specified `_managedRight`.

		@param _managedRight The right which is to have its manager set to
			`_managerRight`.
		@param _managerRight The right whose `UNIVERSAL` holders may manage
			`_managedRight`.
	*/
	function setManagerRight (
		bytes32 _managedRight,
		bytes32 _managerRight
	) external virtual hasValidPermit(UNIVERSAL, MANAGER) {
		require(_managedRight != ZERO_RIGHT, "P3");
		managerRight[_managedRight] = _managerRight;
		emit ManagementUpdated(_msgSender(), _managedRight, _managerRight);
	}

	/**
		Set the permit to a specific address under some circumstances. A permit may
		only be set by the super-administrative contract owner or an address holding
		some delegated management permit.

		@param _address The address to assign the specified `_right` to.
		@param _circumstance The circumstance in which the `_right` is valid.
		@param _right The specific right to assign.
		@param _expirationTime The time when the `_right` expires for the provided
			`_circumstance`.
	*/
	function setPermit (
		address _address,
		bytes32 _circumstance,
		bytes32 _right,
		uint256 _expirationTime
	) public virtual hasValidPermit(UNIVERSAL, managerRight[_right]) {
		require(_right != ZERO_RIGHT, "P2");
		permissions[_address][_circumstance][_right] = _expirationTime;
		emit PermitUpdated(
			_msgSender(),
			_address,
			_circumstance,
			_right,
			_expirationTime
		);
	}

	/**
		Determine whether or not an address has some rights under the given
		circumstance, and if they do have the right, until when.

		@param _address The address to check for the specified `_right`.
		@param _circumstance The circumstance to check the specified `_right` for.
		@param _right The right to check for validity.

		@return The timestamp in seconds when the `_right` expires. If the timestamp
			is zero, we can assume that the user never had the right.
	*/
	function hasRightUntil (
		address _address,
		bytes32 _circumstance,
		bytes32 _right
	) public view returns (uint256) {
		return permissions[_address][_circumstance][_right];
	}

	/**
		Determine whether or not an address has some rights under the given
		circumstance,

		@param _address The address to check for the specified `_right`.
		@param _circumstance The circumstance to check the specified `_right` for.
		@param _right The right to check for validity.

		@return true or false, whether user has rights and time is valid.
	*/
	function hasRight (
		address _address,
		bytes32 _circumstance,
		bytes32 _right
	) public view returns (bool) {
		return permissions[_address][_circumstance][_right] > block.timestamp;
	}
}

// File: contracts/interfaces/IBonklerAuction.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

/**
	This struct contains the data and configuration of the Bonkler auction 
	contract.

	@param bidder The address of the current highest bidder.
	@param amount Tha current highest bid amount.
	@param withdrawable The amount of Ether that may be withdrawn by the 
		auction operator.
	@param startTime The start time of the auction.
	@param endTime The end time of the auction.
	@param bonklerId The ID of the next Bonkler generated, starting from one.
	@param generationHashesLength The number of Bonkler generation hashes 
		loaded into the contract.
	@param settled Whether or not the contract's auction has been settled.
	@param reservePercentage The percent of the bid to store in the Bonkler.
	@param bonklers The address of the Bonkler NFT contract.
	@param reservePrice The minimum price accepted in a bid.
	@param bidIncrement The minimum increment of the bid.
	@param duration The duration of a single auction.
	@param timeBuffer The duration within which to extend the auction back to 
		`timeBuffer` duration.
	@param bonklersBalance The amount of ETH in the Bonklers NFT contract. The 
		"treasury" balance.
	@param bonklersTotalRedeemed The total number of Bonklers redeemed for 
		treasury shares.
*/
struct AuctionData {
	address bidder;
	uint96 amount;
	uint96 withdrawable;
	uint40 startTime;
	uint40 endTime;
	uint24 bonklerId;
	uint24 generationHashesLength;
	bool settled;
	uint8 reservePercentage;
	address bonklers;
	uint96 reservePrice;
	uint96 bidIncrement;
	uint32 duration;
	uint32 timeBuffer;
	uint256 bonklersBalance;
	uint256 bonklersTotalRedeemed;
}

/**
	@custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM
	@title An interface for the BonklerAuction contract.
	@author Tim Clancy <tim-clancy.eth>

	The BonklerAuction contract manages the daily sale of Bonklers.

	@custom:date April 20th, 2023.
*/
interface IBonklerAuction {

	/**
		Return all public data on the current state of the auction, including 
		useful information regarding the Bonklers NFT itself.

		@return data The current state of the auction.
	*/
	function auctionData () external view returns (AuctionData memory data);

	/**
		Create a bid in Ether for a particular Bonkler.

		@param _bonklerId The ID of the Bonkler to bid on.
		@param _generationHash The generation hash to submit for the Bonkler.
	*/
	function createBid (
		uint256 _bonklerId,
		uint256 _generationHash
	) external payable;
}

// File: contracts/interfaces/IBonklerNFT.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

/**
	@custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM
	@title An interface for the BonklerNFT contract.
	@author Tim Clancy <tim-clancy.eth>

	The BonklerNFT contract manages the redemption of Bonklers.

	@custom:date April 20th, 2023.
*/
interface IBonklerNFT {

	/**
		Return the amount of Ether stored in a particular Bonkler.

		@param _bonklerId The ID of the Bonkler to find stored Ether for.

		@return _ The amount of Ether stored in the Bonkler.
	*/
	function getBonklerShares (
		uint256 _bonklerId
	) external view returns (uint256);

	/**
		Allow a Bonkler holder to burn a Bonkler and redeem the ETH inside it.

		@param _bonklerId The ID of the Bonkler to burn and redeem.
	*/
	function redeemBonkler (
		uint256 _bonklerId
	) external;
}

// File: contracts/interfaces/ITiny721.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

/**
	@custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM
	@title A minimalistic, gas-efficient ERC-721 implementation forked from the
		`Super721` ERC-721 implementation used by SuperFarm.
	@author Tim Clancy <tim-clancy.eth>
	@author 0xthrpw
	@author Qazawat Zirak
	@author Rostislav Khlebnikov

	Compared to the original `Super721` implementation that this contract forked
	from, this is a very pared-down contract.

	@custom:date February 8th, 2022.
*/
interface ITiny721 {

	/**
		Return the total number of this token that have ever been minted.

		@return The total supply of minted tokens.
	*/
	function totalSupply () external view returns (uint256);

	/**
		Provided with an address parameter, this function returns the number of all
		tokens in this collection that are owned by the specified address.

		@param _owner The address of the account for which we are checking balances
	*/
	function balanceOf (
		address _owner
	) external returns (uint256);

	/**
		Return the address that holds a particular token ID.

		@param _id The token ID to check for the holding address of.

		@return The address that holds the token with ID of `_id`.
	*/
	function ownerOf (
		uint256 _id
	) external returns (address);

	/**
		This function performs an unsafe transfer of token ID `_id` from address
		`_from` to address `_to`. The transfer is considered unsafe because it does
		not validate that the receiver can actually take proper receipt of an
		ERC-721 token.

		@param _from The address to transfer the token from.
		@param _to The address to transfer the token to.
		@param _id The ID of the token being transferred.
	*/
	function transferFrom (
		address _from,
		address _to,
		uint256 _id
	) external;

	/**
		This function allows permissioned minters of this contract to mint one or
		more tokens dictated by the `_amount` parameter. Any minted tokens are sent
		to the `_recipient` address.

		Note that tokens are always minted sequentially starting at one. That is,
		the list of token IDs is always increasing and looks like [ 1, 2, 3... ].
		Also note that per our use cases the intended recipient of these minted
		items will always be externally-owned accounts and not other contracts. As a
		result there is no safety check on whether or not the mint destination can
		actually correctly handle an ERC-721 token.

		@param _recipient The recipient of the tokens being minted.
		@param _amount The amount of tokens to mint.
	*/
	function mint_Qgo (
		address _recipient,
		uint256 _amount
	) external;
}

// 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: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/utils/Address.sol



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

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCall(target, data, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        require(isContract(target), "Address: call to non-contract");

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(isContract(target), "Address: delegate call to non-contract");

        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (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;
    }
}