ETH Price: $2,331.31 (+0.85%)

Contract

0x4e533e8A49021858d295DDC3aFDa5042D8400e32
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Accept Ownership245486872026-02-27 13:52:5918 days ago1772200379IN
0x4e533e8A...2D8400e32
0 ETH0.000004560.16037182
Set Collection W...244756352026-02-17 9:24:2328 days ago1771320263IN
0x4e533e8A...2D8400e32
0 ETH0.000001050.04204809
Transfer Ownersh...244713062026-02-16 18:54:4729 days ago1771268087IN
0x4e533e8A...2D8400e32
0 ETH0.00009892.0417581
Settle244406932026-02-12 12:27:3533 days ago1770899255IN
0x4e533e8A...2D8400e32
0 ETH0.001688785.59080623
Set Collection W...244349262026-02-11 17:08:5934 days ago1770829739IN
0x4e533e8A...2D8400e32
0 ETH0.000013470.28605943
Set Ops Wallet244348862026-02-11 17:00:5934 days ago1770829259IN
0x4e533e8A...2D8400e32
0 ETH0.000007180.23555058
Set Bonding Curv...243846852026-02-04 16:28:4741 days ago1770222527IN
0x4e533e8A...2D8400e32
0 ETH0.000037220.8064515
Set Dutch Vault ...243846852026-02-04 16:28:4741 days ago1770222527IN
0x4e533e8A...2D8400e32
0 ETH0.000022280.8064515

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading
Loading...
Loading
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
DutchAuctionMarketplace

Compiler Version
v0.8.26+commit.8a97fa7a

Optimization Enabled:
Yes with 200 runs

Other Settings:
cancun EvmVersion
File 1 of 50 : DutchAuctionMarketplace.sol
// SPDX-License-Identifier: BSL-1.1
pragma solidity 0.8.26;

// External Dependencies
import {Ownable2Step} from
    "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from
    "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from
    "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

// Internal Dependencies
import {IDUTCHToken} from "./interfaces/IDUTCHToken.sol";
import {IDutchVault} from "./interfaces/IDutchVault.sol";
import {BondingCurve} from "./BondingCurve.sol";
import {IDutchAuctionMarketplace} from "./interfaces/IDutchAuctionMarketplace.sol";

/// @title DutchAuctionMarketplace
/// @notice NFT marketplace for Dutch (descending-price) auctions.
contract DutchAuctionMarketplace is Ownable2Step, Pausable, ReentrancyGuard, IDutchAuctionMarketplace {
    using SafeERC20 for IERC20;

    // -------------------------------------------------------------------------
    // Constants
    // -------------------------------------------------------------------------

    /// @notice Basis points denominator (100% = 10000 bps).
    uint256 private constant BPS_DENOMINATOR = 10000;

    /// @notice Maximum number of listings that can be settled in one batch.
    uint256 private constant MAX_BATCH_SIZE = 50;

    /// @notice Burn address for deflationary proceeds.
    address private constant BURN_ADDRESS = address(0xdead);

    // -------------------------------------------------------------------------
    // Immutable State
    // -------------------------------------------------------------------------

    /// @notice The DUTCH token used for settlement.
    IDUTCHToken private immutable _dutchToken;

    // -------------------------------------------------------------------------
    // Mutable State
    // -------------------------------------------------------------------------

    /// @notice Dutch Vault address (NFT Purchase Account).
    address private _dutchVaultAddress;

    /// @notice Operations wallet address.
    address private _opsWallet;

    /// @notice The BondingCurve for ETH purchases.
    BondingCurve private _bondingCurve;

    /// @notice Default auction duration in seconds.
    uint256 private _auctionDuration;

    /// @notice Settlement fee in basis points.
    uint256 private _sellerFeeOnSettled;

    /// @notice Listing fee in basis points.
    uint256 private _sellerListingFee;

    /// @notice Max listing fee in ETH.
    uint256 private _maxListingFee;

    /// @notice Listing fee split: Vault share in basis points.
    uint256 private _listingVaultSplitBps;

    /// @notice Listing fee split: Ops share in basis points.
    uint256 private _listingOpsSplitBps;

    /// @notice Seller fee on settled split: Vault share in basis points.
    uint256 private _sellerFeeOnSettledVaultSplitBps;

    /// @notice Seller fee on settled split: Ops share in basis points.
    uint256 private _sellerFeeOnSettledOpsSplitBps;

    /// @notice Seller fee on settled split: Burn share in basis points.
    uint256 private _sellerFeeOnSettledBurnSplitBps;

    /// @notice Whether collection whitelist is enabled.
    bool private _collectionWhitelistEnabled;

    /// @notice Mapping of collection address to whitelist status.
    mapping(address collection => bool allowed)
        private _isCollectionAllowed;

    /// @notice Array of allowed collection addresses for enumeration.
    address[] private _allowedCollections;

    /// @notice Total number of listings created.
    uint256 private _totalListings;

    /// @notice Listing ID counter.
    uint256 private _listingIdCounter;

    /// @notice Mapping of listing ID to listing details.
    mapping(uint256 listingId => Listing listing) private _listings;

    /// @notice Mapping of NFT (nftContract, tokenId) to listing ID.
    /// @dev Used to prevent duplicate active listings for the same NFT.
    ///      Gets overwritten if an NFT is relisted after settlement or cancellation.
    mapping(address nftContract => mapping(uint256 tokenId => uint256 listingId))
        private _nftToListingId;

    // -------------------------------------------------------------------------
    // Data Structures
    // -------------------------------------------------------------------------

    /// @notice Listing structure.
    /// @param seller Address of the NFT seller.
    /// @param nftContract Address of the NFT contract.
    /// @param tokenId Token ID of the NFT.
    /// @param maxPrice Maximum price in ETH (auction start price).
    /// @param minPrice Minimum price in ETH (auction floor price).
    /// @param startTime Auction start timestamp.
    /// @param duration Auction duration in seconds.
    /// @param settled Whether the listing has been settled.
    /// @param cancelled Whether the listing has been cancelled.
    /// @param proceedsRecipient Address to receive settlement proceeds.
    struct Listing {
        address seller;
        address nftContract;
        uint256 tokenId;
        uint256 maxPrice;
        uint256 minPrice;
        uint256 startTime;
        uint256 duration;
        bool settled;
        bool cancelled;
        address proceedsRecipient;
    }

    /// @notice Price structure for current listing price.
    /// @param priceETH Current price in ETH.
    /// @param priceDUTCH Current price in DUTCH tokens.
    struct CurrentPrice {
        uint256 priceETH;
        uint256 priceDUTCH;
    }

    // -------------------------------------------------------------------------
    // Events
    // -------------------------------------------------------------------------

    /// @notice Emitted when a listing is created.
    /// @param listingId_ The ID of the listing.
    /// @param seller_ The address of the seller.
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @param maxPrice_ The maximum price in ETH.
    /// @param minPrice_ The minimum price in ETH.
    /// @param startTime_ The auction start timestamp.
    /// @param duration_ The auction duration in seconds.
    event ListingCreated(
        uint256 indexed listingId_,
        address indexed seller_,
        address indexed nftContract_,
        uint256 tokenId_,
        uint256 maxPrice_,
        uint256 minPrice_,
        uint256 startTime_,
        uint256 duration_
    );

    /// @notice Emitted when collection whitelist is enabled or disabled.
    /// @param enabled_ Whether the whitelist is enabled.
    event CollectionWhitelistSet(bool enabled_);

    /// @notice Emitted when a collection is whitelisted or de-whitelisted.
    /// @param collection_ The address of the NFT collection.
    /// @param allowed_ Whether the collection is allowed.
    event CollectionAllowedSet(address indexed collection_, bool allowed_);

    /// @notice Emitted when a listing is cancelled.
    /// @param listingId_ The ID of the cancelled listing.
    event ListingCanceled(uint256 indexed listingId_);

    /// @notice Emitted when a listing is settled.
    /// @param listingId_ The ID of the settled listing.
    /// @param buyer_ The address of the buyer.
    /// @param priceETH_ The final settlement price in ETH.
    /// @param priceDUTCH_ The final settlement price in DUTCH tokens.
    event ListingSettled(
        uint256 indexed listingId_, address indexed buyer_, uint256 priceETH_, uint256 priceDUTCH_
    );

    /// @notice Emitted when seller fee on settled is updated.
    /// @param newFeeBps_ The new fee in basis points.
    event SellerFeeOnSettledUpdated(uint256 newFeeBps_);

    /// @notice Emitted when seller listing fee is updated.
    /// @param newFeeBps_ The new fee in basis points.
    event SellerListingFeeUpdated(uint256 newFeeBps_);

    /// @notice Emitted when max listing fee is updated.
    /// @param newFee_ The new fee in ETH.
    event MaxListingFeeUpdated(uint256 newFee_);

    /// @notice Emitted when auction duration is updated.
    /// @param newDuration_ The new auction duration in seconds.
    event AuctionDurationUpdated(uint256 newDuration_);

    /// @notice Emitted when Dutch Vault address is updated.
    /// @param newAddress_ The new Dutch Vault address.
    event DutchVaultAddressUpdated(address newAddress_);

    /// @notice Emitted when ops wallet address is updated.
    /// @param newAddress_ The new ops wallet address.
    event OpsWalletUpdated(address newAddress_);

    /// @notice Emitted when listing fee split configuration is updated.
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops share in basis points.
    event ListingFeeSplitUpdated(uint256 vaultBps_, uint256 opsBps_);

    /// @notice Emitted when seller fee on settled split configuration is updated.
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops share in basis points.
    /// @param burnBps_ Burn share in basis points.
    event SellerFeeOnSettledSplitUpdated(
        uint256 vaultBps_, uint256 opsBps_, uint256 burnBps_
    );

    // -------------------------------------------------------------------------
    // Errors
    // -------------------------------------------------------------------------

    /// @notice Thrown when an invalid address is provided.
    error InvalidAddress();

    /// @notice Thrown when an invalid amount is provided.
    error InvalidAmount();

    /// @notice Thrown when caller does not own the NFT.
    error NotNFTOwner();

    /// @notice Thrown when marketplace is not approved for NFT.
    error NotApproved();

    /// @notice Thrown when ETH transfer fails.
    error ETHTransferFailed();

    /// @notice Thrown when collection is not whitelisted.
    error CollectionNotWhitelisted();

    /// @notice Thrown when listing is invalid or does not exist.
    error InvalidListing();

    /// @notice Thrown when caller is not authorized for the operation.
    error Unauthorized();

    /// @notice Thrown when price slippage exceeds buyer's tolerance.
    error SlippageExceeded(uint256 expected, uint256 actual);

    /// @notice Thrown when array lengths do not match.
    error ArrayLengthMismatch();

    /// @notice Thrown when batch size exceeds maximum allowed.
    error BatchSizeTooLarge();

    /// @notice Thrown when ETH is sent with protocol listing (no fee required).
    error NoFeeRequired();

    /// @notice Thrown when tax split percentages don't sum to 100%.
    error InvalidSplitConfig();

    /// @notice Thrown when bonding hook is not set.
    error BondingHookNotSet();

    /// @notice Thrown when ETH refund fails.
    error ETHRefundFailed();

    /// @notice Thrown when attempting to create a listing for an NFT that already has an active listing.
    error NFTAlreadyListed();

    /// @notice Thrown when NFT transfer fails.
    error NFTTransferFailed();

    // -------------------------------------------------------------------------
    // Constructor
    // -------------------------------------------------------------------------

    /// @notice Constructor.
    /// @param dutchToken_ The DUTCH token contract address.
    /// @param dutchVaultAddress_ The Dutch Vault address.
    /// @param opsWallet_ The operations wallet address.
    constructor(
        address dutchToken_,
        address dutchVaultAddress_,
        address opsWallet_
    ) Ownable(msg.sender) {
        if (dutchToken_ == address(0)) revert InvalidAddress();
        if (dutchVaultAddress_ == address(0)) revert InvalidAddress();
        if (opsWallet_ == address(0)) revert InvalidAddress();

        _dutchToken = IDUTCHToken(dutchToken_);
        _dutchVaultAddress = dutchVaultAddress_;
        _opsWallet = opsWallet_;

        // Initialize default configuration.
        _collectionWhitelistEnabled = false;
        _sellerFeeOnSettled = 200; // 2.0%
        _sellerListingFee = 50; // 0.5%
        _maxListingFee = 0.1 ether;
        _auctionDuration = 12 hours;
        _totalListings = 0;

        // Initialize default fee splits.
        // Listing fee: 75% Vault, 25% Ops.
        _listingVaultSplitBps = 7500;
        _listingOpsSplitBps = 2500;

        // Seller fee on settled split: 0% Vault, 0% Ops, 100% Burn.
        _sellerFeeOnSettledVaultSplitBps = 0;
        _sellerFeeOnSettledOpsSplitBps = 0;
        _sellerFeeOnSettledBurnSplitBps = 10000;
    }

    // -------------------------------------------------------------------------
    // View Functions
    // -------------------------------------------------------------------------

    /// @notice Get collection whitelist status.
    /// @return enabled_ Whether collection whitelist is enabled.
    function getCollectionWhitelistEnabled()
        external
        view
        returns (bool enabled_)
    {
        return _collectionWhitelistEnabled;
    }

    /// @notice Get seller fee on settled.
    /// @return feeBps_ The fee in basis points.
    function getSellerFeeOnSettled() external view returns (uint256 feeBps_) {
        return _sellerFeeOnSettled;
    }

    /// @notice Get seller listing fee.
    /// @return feeBps_ The fee in basis points.
    function getSellerListingFee() external view returns (uint256 feeBps_) {
        return _sellerListingFee;
    }

    /// @notice Get max listing fee.
    /// @return fee_ The fee in ETH.
    function getMaxListingFee() external view returns (uint256 fee_) {
        return _maxListingFee;
    }

    /// @notice Get listing fee split configuration.
    /// @return vaultBps_ Vault share in basis points.
    /// @return opsBps_ Ops share in basis points.
    function getListingFeeSplitConfig()
        external
        view
        returns (uint256 vaultBps_, uint256 opsBps_)
    {
        return (_listingVaultSplitBps, _listingOpsSplitBps);
    }

    /// @notice Get seller fee on settled split configuration.
    /// @return vaultBps_ Vault share in basis points.
    /// @return opsBps_ Ops share in basis points.
    /// @return burnBps_ Burn share in basis points.
    function getSellerFeeOnSettledSplitConfig()
        external
        view
        returns (uint256 vaultBps_, uint256 opsBps_, uint256 burnBps_)
    {
        return (
            _sellerFeeOnSettledVaultSplitBps,
            _sellerFeeOnSettledOpsSplitBps,
            _sellerFeeOnSettledBurnSplitBps
        );
    }

    /// @notice Get auction duration.
    /// @return duration_ The duration in seconds.
    function getAuctionDuration() external view returns (uint256 duration_) {
        return _auctionDuration;
    }

    /// @notice Get Dutch Token address.
    /// @return dutchToken_ The Dutch Token address.
    function getDutchToken() external view returns (address dutchToken_) {
        return address(_dutchToken);
    }

    /// @notice Get Dutch Vault address.
    /// @return vaultAddress_ The Dutch Vault address.
    function getDutchVaultAddress()
        external
        view
        returns (address vaultAddress_)
    {
        return _dutchVaultAddress;
    }

    /// @notice Get Bonding Curve address.
    /// @return bondingCurve_ The Bonding Curve address.
    function getBondingCurve() external view returns (address bondingCurve_) {
        return address(_bondingCurve);
    }

    /// @notice Get ops wallet address.
    /// @return wallet_ The ops wallet address.
    function getOpsWallet() external view returns (address wallet_) {
        return _opsWallet;
    }

    /// @notice Get total listings.
    /// @return total_ The total number of listings created.
    function getTotalListings() external view returns (uint256 total_) {
        return _totalListings;
    }

    /// @notice Get listing details.
    /// @param listingId_ The ID of the listing.
    /// @return listing_ The listing details.
    function getListing(uint256 listingId_)
        external
        view
        returns (Listing memory listing_)
    {
        return _listings[listingId_];
    }

    /// @notice Get listing for a specific NFT.
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @return listingId_ The listing ID for the NFT.
    /// @return listing_ The listing for the NFT.
    /// @dev Returns the most recent listing for the NFT. Reverts if the NFT has never been listed.
    function getListingByNFT(address nftContract_, uint256 tokenId_)
        external
        view
        returns (uint256 listingId_, Listing memory listing_)
    {
        listingId_ = _nftToListingId[nftContract_][tokenId_];
        if (listingId_ >= _listingIdCounter) {
            revert InvalidListing();
        }
        
        Listing storage storedListing_ = _listings[listingId_];
        // Verify this is actually a listing for this NFT (not just default mapping value 0).
        if (
            storedListing_.nftContract != nftContract_
                || storedListing_.tokenId != tokenId_
        ) {
            revert InvalidListing();
        }
        
        return (listingId_, storedListing_);
    }

    /// @notice Get a paginated list of listings by ID.
    /// @dev Iterates over sequential listing IDs starting from `offset_`.
    /// @param offset_ The starting listing ID (inclusive).
    /// @param limit_ The maximum number of listings to return.
    /// @return listings_ Array of listings for the requested page.
    function getListingsPaginated(uint256 offset_, uint256 limit_)
        external
        view
        returns (Listing[] memory listings_)
    {
        uint256 total_ = _listingIdCounter;

        // If offset is beyond the last listing ID, return empty array.
        if (offset_ >= total_) {
            return new Listing[](0);
        }

        uint256 endExclusive_ = offset_ + limit_;
        if (endExclusive_ > total_) {
            endExclusive_ = total_;
        }

        uint256 length_ = endExclusive_ - offset_;
        listings_ = new Listing[](length_);

        for (uint256 i; i < length_; ++i) {
            listings_[i] = _listings[offset_ + i];
        }
    }

    /// @notice Check if a collection is whitelisted.
    /// @param collection_ The address of the NFT collection.
    /// @return allowed_ Whether the collection is allowed.
    function getIsCollectionAllowed(address collection_)
        external
        view
        returns (bool allowed_)
    {
        return _isCollectionAllowed[collection_];
    }

    /// @notice Get all allowed collection addresses.
    /// @return collections_ Array of allowed collection addresses.
    function getAllowedCollections()
        external
        view
        returns (address[] memory collections_)
    {
        return _allowedCollections;
    }

    /// @notice Get the number of allowed collections.
    /// @return count_ The number of allowed collections.
    function getAllowedCollectionsCount()
        external
        view
        returns (uint256 count_)
    {
        return _allowedCollections.length;
    }

    /// @notice Get current price for a listing (Dutch auction).
    /// @param listingId_ The ID of the listing.
    /// @return price_ The current price in both ETH and DUTCH.
    function getCurrentPrice(uint256 listingId_)
        external
        view
        returns (CurrentPrice memory price_)
    {
        return _getCurrentPrice(listingId_);
    }

    /// @notice Internal helper to get current price in both ETH and DUTCH.
    /// @param listingId_ The ID of the listing.
    /// @return price_ The current price in both ETH and DUTCH.
    function _getCurrentPrice(uint256 listingId_)
        internal
        view
        returns (CurrentPrice memory price_)
    {
        price_.priceETH = _getCurrentPriceETH(listingId_);
        price_.priceDUTCH = _convertETHToDUTCH(price_.priceETH);
    }

    /// @notice Internal helper to get current price in ETH only.
    /// @param listingId_ The ID of the listing.
    /// @return priceETH_ The current price in ETH.
    function _getCurrentPriceETH(uint256 listingId_)
        internal
        view
        returns (uint256 priceETH_)
    {
        // 1. Validate listing exists.
        if (listingId_ >= _listingIdCounter) revert InvalidListing();

        Listing storage listing_ = _listings[listingId_];

        // 2. Check listing is not cancelled.
        if (listing_.cancelled) revert InvalidListing();

        // 3. Check listing is not settled.
        if (listing_.settled) revert InvalidListing();

        // 4. Calculate current price based on elapsed time.
        uint256 elapsed_ = block.timestamp - listing_.startTime;

        // If auction hasn't started or just started, return max price.
        if (elapsed_ == 0) {
            return listing_.maxPrice;
        }

        // If auction has reached or passed its duration, return min price.
        if (elapsed_ >= listing_.duration) {
            return listing_.minPrice;
        }

        // Non-linear (quadratic) decay: faster drop early, slower near the end.
        // price = minPrice + priceRange * (1 - t)^4, where t = elapsed/duration.
        // We implement this in fixed-point with 1e18 precision to keep
        // intermediates bounded:
        //   t = elapsed / duration
        //   oneMinus = 1 - t
        //   factor = oneMinus^4
        uint256 priceRange_ = listing_.maxPrice - listing_.minPrice;

        // t in [0, 1e18]
        uint256 t_ = (elapsed_ * 1e18) / listing_.duration;
        uint256 oneMinus_ = 1e18 - t_;

        // factor = (1 - t)^4 = ((1 - t)^2)^2 in 1e18 fixed point.
        uint256 sq_ = (oneMinus_ * oneMinus_) / 1e18;       // (1 - t)^2
        uint256 factor_ = (sq_ * sq_) / 1e18;               // (1 - t)^4

        // adjustedRange = priceRange * factor
        uint256 adjustedRange_ = (priceRange_ * factor_) / 1e18;

        priceETH_ = listing_.minPrice + adjustedRange_;
    }

    // -------------------------------------------------------------------------
    // Owner Functions
    // -------------------------------------------------------------------------

    /// @notice Pause the marketplace.
    function pause() external onlyOwner {
        _pause();
    }

    /// @notice Unpause the marketplace.
    function unpause() external onlyOwner {
        _unpause();
    }

    /// @notice Enable or disable collection whitelist.
    /// @param enabled_ Whether to enable the whitelist.
    function setCollectionWhitelistEnabled(bool enabled_) external onlyOwner {
        _collectionWhitelistEnabled = enabled_;
        emit CollectionWhitelistSet(enabled_);
    }

    /// @notice Whitelist or de-whitelist a collection.
    /// @param collection_ The address of the NFT collection.
    /// @param allowed_ Whether the collection is allowed.
    function setCollectionAllowed(address collection_, bool allowed_)
        external
        onlyOwner
    {
        if (collection_ == address(0)) revert InvalidAddress();
        
        bool wasAllowed_ = _isCollectionAllowed[collection_];
        _isCollectionAllowed[collection_] = allowed_;
        
        // Maintain enumeration array
        if (allowed_ && !wasAllowed_) {
            // Adding to whitelist: append to array
            _allowedCollections.push(collection_);
        } else if (!allowed_ && wasAllowed_) {
            // Removing from whitelist: find and remove from array
            uint256 length_ = _allowedCollections.length;
            for (uint256 i; i < length_; ++i) {
                if (_allowedCollections[i] == collection_) {
                    // Move last element to current position and pop
                    _allowedCollections[i] = _allowedCollections[length_ - 1];
                    _allowedCollections.pop();
                    break;
                }
            }
        }
        
        emit CollectionAllowedSet(collection_, allowed_);
    }

    /// @notice Update seller fee on settled.
    /// @param feeBps_ The new fee in basis points (max 10000 = 100%).
    function setSellerFeeOnSettled(uint256 feeBps_) external onlyOwner {
        if (feeBps_ > BPS_DENOMINATOR) revert InvalidAmount();
        _sellerFeeOnSettled = feeBps_;
        emit SellerFeeOnSettledUpdated(feeBps_);
    }

    /// @notice Update seller listing fee.
    /// @param feeBps_ The new fee in basis points (max 10000 = 100%).
    function setSellerListingFee(uint256 feeBps_) external onlyOwner {
        if (feeBps_ > BPS_DENOMINATOR) revert InvalidAmount();
        _sellerListingFee = feeBps_;
        emit SellerListingFeeUpdated(feeBps_);
    }

    /// @notice Update max listing fee.
    /// @param fee_ The new fee in ETH.
    function setMaxListingFee(uint256 fee_) external onlyOwner {
        if (fee_ == 0) revert InvalidAmount();
        _maxListingFee = fee_;
        emit MaxListingFeeUpdated(fee_);
    }

    /// @notice Update listing fee split configuration.
    /// @dev Vault and Ops splits must sum to 100% (10000 BPS).
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops share in basis points.
    function setListingFeeSplitConfig(
        uint256 vaultBps_,
        uint256 opsBps_
    ) external onlyOwner {
        if (vaultBps_ + opsBps_ != BPS_DENOMINATOR) {
            revert InvalidSplitConfig();
        }

        _listingVaultSplitBps = vaultBps_;
        _listingOpsSplitBps = opsBps_;

        emit ListingFeeSplitUpdated(vaultBps_, opsBps_);
    }

    /// @notice Update seller fee on settled split configuration.
    /// @dev Vault, Ops, and Burn splits must sum to 100% (10000 BPS).
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops share in basis points.
    /// @param burnBps_ Burn share in basis points.
    function setSellerFeeOnSettledSplitConfig(
        uint256 vaultBps_,
        uint256 opsBps_,
        uint256 burnBps_
    ) external onlyOwner {
        if (vaultBps_ + opsBps_ + burnBps_ != BPS_DENOMINATOR) {
            revert InvalidSplitConfig();
        }

        _sellerFeeOnSettledVaultSplitBps = vaultBps_;
        _sellerFeeOnSettledOpsSplitBps = opsBps_;
        _sellerFeeOnSettledBurnSplitBps = burnBps_;

        emit SellerFeeOnSettledSplitUpdated(vaultBps_, opsBps_, burnBps_);
    }

    /// @notice Update auction duration.
    /// @param duration_ The new auction duration in seconds.
    function setAuctionDuration(uint256 duration_) external onlyOwner {
        if (duration_ == 0) revert InvalidAmount();
        _auctionDuration = duration_;
        emit AuctionDurationUpdated(duration_);
    }

    /// @notice Update Dutch Vault address.
    /// @param newAddress_ The new Dutch Vault address.
    function setDutchVaultAddress(address newAddress_) external onlyOwner {
        if (newAddress_ == address(0)) revert InvalidAddress();
        _dutchVaultAddress = newAddress_;
        emit DutchVaultAddressUpdated(newAddress_);
    }

    /// @notice Update ops wallet address.
    /// @param newAddress_ The new ops wallet address.
    function setOpsWallet(address newAddress_) external onlyOwner {
        if (newAddress_ == address(0)) revert InvalidAddress();
        _opsWallet = newAddress_;
        emit OpsWalletUpdated(newAddress_);
    }

    /// @notice Set bonding curve address.
    /// @dev Only callable by owner. Required for ETH purchases.
    /// @param bondingCurve_ The BondingCurve contract address.
    function setBondingCurve(address payable bondingCurve_) external onlyOwner {
        if (bondingCurve_ == address(0)) revert InvalidAddress();
        _bondingCurve = BondingCurve(bondingCurve_);
    }

    // -------------------------------------------------------------------------
    // Public Functions
    // -------------------------------------------------------------------------

    /// @notice Create a new listing.
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @param maxPrice_ The maximum price in ETH (auction start price).
    /// @param minPrice_ The minimum price in ETH (auction floor price).
    /// @return listingId_ The ID of the created listing.
    function createListing(
        address nftContract_,
        uint256 tokenId_,
        uint256 maxPrice_,
        uint256 minPrice_
    ) external payable nonReentrant whenNotPaused returns (uint256 listingId_) {
        return _createListing(
            nftContract_, tokenId_, maxPrice_, minPrice_, msg.sender
        );
    }

    /// @notice Create a listing with proceeds burned.
    /// @dev Permissionless. Proceeds sent to burn address (0xdead).
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @param maxPrice_ The maximum price in ETH (auction start price).
    /// @param minPrice_ The minimum price in ETH (auction floor price).
    /// @return listingId_ The ID of the created listing.
    function createListingWithBurn(
        address nftContract_,
        uint256 tokenId_,
        uint256 maxPrice_,
        uint256 minPrice_
    ) external payable nonReentrant whenNotPaused returns (uint256 listingId_) {
        return _createListing(
            nftContract_, tokenId_, maxPrice_, minPrice_, BURN_ADDRESS
        );
    }

    /// @notice Cancel a listing.
    /// @dev Can be called by seller or current NFT owner.
    /// @param listingId_ The ID of the listing to cancel.
    function cancelListing(uint256 listingId_) external whenNotPaused {
        // 1. Validate listing exists.
        if (listingId_ >= _listingIdCounter) revert InvalidListing();

        Listing storage listing_ = _listings[listingId_];

        // 2. Check listing is not already settled.
        if (listing_.settled) revert InvalidListing();

        // 3. Check listing is not already cancelled.
        if (listing_.cancelled) revert InvalidListing();

        // 4. Check caller is seller or current NFT owner.
        address currentOwner_ =
            IERC721(listing_.nftContract).ownerOf(listing_.tokenId);
        if (
            msg.sender != listing_.seller && msg.sender != currentOwner_
        ) {
            revert Unauthorized();
        }

        // 5. Mark as cancelled.
        listing_.cancelled = true;

        // 6. Emit event.
        emit ListingCanceled(listingId_);
    }

    /// @notice Settle a listing by purchasing the NFT with DUTCH.
    /// @param listingId_ The ID of the listing to settle.
    /// @param maxPriceDUTCH_ Maximum price willing to pay (slippage protection).
    function settle(uint256 listingId_, uint256 maxPriceDUTCH_)
        public
        whenNotPaused
        nonReentrant
    {
        // 1. Validate bonding curve is set.
        if (address(_bondingCurve) == address(0)) revert BondingHookNotSet();

        // 2. Validate listing exists.
        if (listingId_ >= _listingIdCounter) revert InvalidListing();

        Listing storage listing_ = _listings[listingId_];

        // 3. Check listing is not already settled.
        if (listing_.settled) revert InvalidListing();

        // 4. Check listing is not cancelled.
        if (listing_.cancelled) revert InvalidListing();

        // 5. Get current NFT price, convert to DUTCH, and check slippage.
        CurrentPrice memory currentPrice_ = _getCurrentPrice(listingId_);
        if (currentPrice_.priceDUTCH > maxPriceDUTCH_) {
            revert SlippageExceeded(maxPriceDUTCH_, currentPrice_.priceDUTCH);
        }

        // 6. Mark as settled (before external calls).
        listing_.settled = true;

        // 7. Transfer DUTCH tokens from buyer to marketplace.
        IERC20(address(_dutchToken)).safeTransferFrom(
            msg.sender, address(this), currentPrice_.priceDUTCH
        );

        // 8. Execute common settlement logic.
        _settleInternal(listingId_, currentPrice_.priceETH, currentPrice_.priceDUTCH, msg.sender);
    }

    /// @notice Settle a listing by purchasing the NFT with ETH. Only callable for protocol-owned NFTs.
    /// @dev Converts ETH to DUTCH via bonding hook without buy tax applied, then settles normally.
    /// @param listingId_ The ID of the listing to settle.
    /// @param maxPriceETH_ Maximum price willing to pay (slippage protection).
    function settleWithETH(uint256 listingId_, uint256 maxPriceETH_)
        external
        payable
        whenNotPaused
        nonReentrant
    {
        // 1. Validate bonding curve is set.
        if (address(_bondingCurve) == address(0)) revert BondingHookNotSet();

        // 2. Validate listing exists.
        if (listingId_ >= _listingIdCounter) revert InvalidListing();

        Listing storage listing_ = _listings[listingId_];

        // 3. Check listing is not already settled.
        if (listing_.settled) revert InvalidListing();

        // 4. Check listing is not cancelled.
        if (listing_.cancelled) revert InvalidListing();

        // 5. Check listing was created by DutchVault. Only protocol-owned NFTs can be settled with ETH.
        if (listing_.seller != _dutchVaultAddress) revert InvalidListing();

        // 6. Get current NFT price, convert to DUTCH, and check slippage.
        CurrentPrice memory currentPrice_ = _getCurrentPrice(listingId_);
        if (currentPrice_.priceETH > maxPriceETH_) {
            revert SlippageExceeded(maxPriceETH_, currentPrice_.priceETH);
        }

        // 7. Mark as settled (before external calls).
        listing_.settled = true;

        // 8. Swap ETH for exact DUTCH amount via bonding curve.
        // Curve will refund excess ETH back to this contract.
        (, uint256 ethUsed_,) = _bondingCurve.buyDutchExactOut{value: msg.value}(
            currentPrice_.priceDUTCH,
            address(this)
        );

        // 9. Check slippage.
        if (ethUsed_ > maxPriceETH_) {
            revert SlippageExceeded(maxPriceETH_, ethUsed_);
        }

        // 10. Execute common settlement logic.
        _settleInternal(listingId_, ethUsed_, currentPrice_.priceDUTCH, msg.sender);

        // 11. Refund any remaining ETH to buyer.
        uint256 refund_ = msg.value - ethUsed_;
        if (refund_ > 0) {
            (bool success_, ) = payable(msg.sender).call{value: refund_}("");
            if (!success_) revert ETHRefundFailed();
        }
    }

    /// @notice Batch settle multiple listings in a single transaction.
    /// @param listingIds_ Array of listing IDs to settle.
    /// @param maxPrices_ Array of maximum prices for slippage protection.
    function batchSettle(
        uint256[] calldata listingIds_,
        uint256[] calldata maxPrices_
    ) external {
        // Validate array lengths match.
        if (listingIds_.length != maxPrices_.length) {
            revert ArrayLengthMismatch();
        }

        // Validate batch size does not exceed maximum.
        if (listingIds_.length > MAX_BATCH_SIZE) {
            revert BatchSizeTooLarge();
        }

        // Settle each listing.
        for (uint256 i; i < listingIds_.length; ++i) {
            settle(listingIds_[i], maxPrices_[i]);
        }
    }

    // -------------------------------------------------------------------------
    // Internal Functions
    // -------------------------------------------------------------------------

    /// @notice Internal helper to create a listing.
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @param maxPrice_ The maximum price in ETH (auction start price).
    /// @param minPrice_ The minimum price in ETH (auction floor price).
    /// @param proceedsRecipient_ Address to receive settlement proceeds.
    /// @return listingId_ The ID of the created listing.
    function _createListing(
        address nftContract_,
        uint256 tokenId_,
        uint256 maxPrice_,
        uint256 minPrice_,
        address proceedsRecipient_
    ) internal returns (uint256 listingId_) {
        // 1. Validate inputs (nftContract, maxPrice, minPrice).
        if (nftContract_ == address(0)) revert InvalidAddress();
        if (maxPrice_ == 0 || minPrice_ == 0) revert InvalidAmount();
        if (maxPrice_ < minPrice_) revert InvalidAmount();

        // 1b. Check collection whitelist.
        // DutchVault can list any collection (protocol-owned NFTs).
        if (
            _collectionWhitelistEnabled
                && !_isCollectionAllowed[nftContract_]
                && msg.sender != _dutchVaultAddress
        ) {
            revert CollectionNotWhitelisted();
        }

        // 2. Verify NFT ownership.
        address owner_ = IERC721(nftContract_).ownerOf(tokenId_);
        if (owner_ != msg.sender) revert NotNFTOwner();

        // 3. Verify marketplace approval.
        if (
            !IERC721(nftContract_).isApprovedForAll(msg.sender, address(this))
                && IERC721(nftContract_).getApproved(tokenId_) != address(this)
        ) {
            revert NotApproved();
        }

        // 4. Check if NFT already has an active listing.
        uint256 existingListingId_ = _nftToListingId[nftContract_][tokenId_];
        if (existingListingId_ < _listingIdCounter) {
            Listing storage existingListing_ = _listings[existingListingId_];
            // Verify this is actually a listing for this NFT (not just default mapping value 0).
            if (
                existingListing_.nftContract == nftContract_
                    && existingListing_.tokenId == tokenId_
            ) {
                if (!existingListing_.settled && !existingListing_.cancelled) {
                    revert NFTAlreadyListed();
                }
                // If existing listing is settled or cancelled, allow new listing (mapping will be overwritten).
            }
        }

        // 5. Calculate and collect listing fee (skip if protocol or vault).
        if (proceedsRecipient_ != BURN_ADDRESS && msg.sender != _dutchVaultAddress) {
            // Normal user listing: charge fee.
            uint256 listingFee_ = _calculateListingFee(minPrice_);
            if (msg.value != listingFee_) revert InvalidAmount();
            _distributeListingFee(listingFee_);
        } else {
            // Protocol or DutchVault listings: no fee required.
            if (msg.value != 0) revert NoFeeRequired();
        }

        // 6. Create listing.
        listingId_ = _listingIdCounter;
        _listings[listingId_] = Listing({
            seller: msg.sender,
            nftContract: nftContract_,
            tokenId: tokenId_,
            maxPrice: maxPrice_,
            minPrice: minPrice_,
            startTime: block.timestamp,
            duration: _auctionDuration,
            settled: false,
            cancelled: false,
            proceedsRecipient: proceedsRecipient_
        });

        // 7. Update counters.
        _listingIdCounter++;
        _totalListings++;

        // 8. Store NFT to listing ID mapping.
        _nftToListingId[nftContract_][tokenId_] = listingId_;

        // 9. Emit event.
        emit ListingCreated(
            listingId_,
            msg.sender,
            nftContract_,
            tokenId_,
            maxPrice_,
            minPrice_,
            block.timestamp,
            _auctionDuration
        );
    }

    /// @notice Calculate listing fee.
    /// @param minPrice_ The minimum price in ETH.
    /// @return fee_ The listing fee in ETH.
    function _calculateListingFee(uint256 minPrice_)
        internal
        view
        returns (uint256 fee_)
    {
        // Fee = Math.min(minPrice * listingFeeBps, maxListingFee).
        uint256 percentageFee_ = (minPrice_ * _sellerListingFee)
            / BPS_DENOMINATOR;
        fee_ = percentageFee_ > _maxListingFee
            ? _maxListingFee
            : percentageFee_;
    }

    /// @notice Distribute listing fee between Dutch Vault and Ops Wallet.
    /// @param feeAmount_ The fee amount in ETH.
    function _distributeListingFee(uint256 feeAmount_) internal {
        // Split between Dutch Vault and Ops Wallet according to configured
        // basis point shares.
        uint256 vaultAmount_ =
            (feeAmount_ * _listingVaultSplitBps) / BPS_DENOMINATOR;
        uint256 opsAmount_ = feeAmount_ - vaultAmount_;

        // Transfer to Dutch Vault.
        (bool success1_,) =
            payable(_dutchVaultAddress).call{value: vaultAmount_}("");
        if (!success1_) revert ETHTransferFailed();

        // Transfer to Ops Wallet.
        (bool success2_,) =
            payable(_opsWallet).call{value: opsAmount_}("");
        if (!success2_) revert ETHTransferFailed();
    }

    /// @notice Internal settlement logic shared by settle() and settleWithETH().
    /// @dev Assumes listing is valid and DUTCH tokens are in marketplace.
    /// @param listingId_ The ID of the listing to settle.
    /// @param currentPriceInDUTCH_ The current price in DUTCH tokens.
    /// @param buyer_ The address of the buyer.
    function _settleInternal(
        uint256 listingId_,
        uint256 currentPriceInETH_,
        uint256 currentPriceInDUTCH_,
        address buyer_
    ) internal {
        Listing storage listing_ = _listings[listingId_];

        // Handle proceeds based on listing type.
        if (listing_.proceedsRecipient == BURN_ADDRESS) {
            // Protocol listing: burn all proceeds (no fees).
            _dutchToken.userBurn(currentPriceInDUTCH_);
        } else if (listing_.seller == _dutchVaultAddress) {
            // Protocol listing, just transfer proceeds to recipient (no fees).
            IERC20(address(_dutchToken)).safeTransfer(
                listing_.proceedsRecipient, currentPriceInDUTCH_
            );
        } else {
            // Third-party listing: charge seller fee and pay seller.
            uint256 fee_ =
                (currentPriceInDUTCH_ * _sellerFeeOnSettled) / BPS_DENOMINATOR;

            // Distribute seller fee according to configurable splits.
            uint256 vaultFee_ =
                (fee_ * _sellerFeeOnSettledVaultSplitBps) / BPS_DENOMINATOR;
            uint256 opsFee_ =
                (fee_ * _sellerFeeOnSettledOpsSplitBps) / BPS_DENOMINATOR;
            uint256 burnFee_ = fee_ - vaultFee_ - opsFee_;

            if (vaultFee_ != 0) {
                IERC20(address(_dutchToken)).safeTransfer(
                    _dutchVaultAddress, vaultFee_
                );
            }

            if (opsFee_ != 0) {
                IERC20(address(_dutchToken)).safeTransfer(
                    _opsWallet, opsFee_
                );
            }

            if (burnFee_ != 0) {
                _dutchToken.userBurn(burnFee_);
            }

            // Transfer proceeds to recipient (typically seller).
            uint256 proceeds_ = currentPriceInDUTCH_ - fee_;
            IERC20(address(_dutchToken)).safeTransfer(
                listing_.proceedsRecipient, proceeds_
            );
        }

        // Transfer NFT from seller to buyer.
        IERC721(listing_.nftContract).transferFrom(
            listing_.seller, buyer_, listing_.tokenId
        );

        if (IERC721(listing_.nftContract).ownerOf(listing_.tokenId) != buyer_) {
            revert NFTTransferFailed();
        }

        // Notify DutchVault if it's the seller (automatic settlement).
        if (listing_.seller == _dutchVaultAddress) {
            IDutchVault(_dutchVaultAddress).onListingSettled(
                listingId_, buyer_, currentPriceInETH_, currentPriceInDUTCH_
            );
        }

        // Emit event.
        emit ListingSettled(listingId_, buyer_, currentPriceInETH_, currentPriceInDUTCH_);
    }

    /// @notice Convert an ETH‑denominated price into a DUTCH token amount
    ///         using the current bonding curve spot price.
    /// @param ethAmount_ The amount of ETH (18 decimals).
    /// @return dutchAmount_ The corresponding DUTCH amount (18 decimals).
    function _convertETHToDUTCH(uint256 ethAmount_)
        internal
        view
        returns (uint256 dutchAmount_)
    {
        if (ethAmount_ == 0) {
            return 0;
        }

        // If bonding curve is not set, return 0 for DUTCH price.
        if (address(_bondingCurve) == address(0)) {
            return 0;
        }

        // Spot price from bonding curve: WETH per 1 DUTCH (18 decimals).
        uint256 dutchPrice_ = _bondingCurve.getCurrentPrice();
        if (dutchPrice_ == 0) {
            revert InvalidAmount();
        }

        // dutchAmount = ethAmount / price, both 18‑decimals.
        dutchAmount_ = (ethAmount_ * 1e18) / dutchPrice_;
    }

    // -------------------------------------------------------------------------
    // Receive Function
    // -------------------------------------------------------------------------

    /// @notice Allow contract to receive ETH refunds from bonding hook.
    /// @dev Required for settleWithETH() when bonding hook refunds excess ETH.
    receive() external payable {}
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {Ownable} from "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

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

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

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

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling 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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    bool private _paused;

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}

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

pragma solidity ^0.8.20;

import {StorageSlot} from "./StorageSlot.sol";

/**
 * @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * 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].
 *
 * IMPORTANT: Deprecated. This storage-based reentrancy guard will be removed and replaced
 * by the {ReentrancyGuardTransient} variant in v6.0.
 *
 * @custom:stateless
 */
abstract contract ReentrancyGuard {
    using StorageSlot for bytes32;

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant REENTRANCY_GUARD_STORAGE =
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    // 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;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _reentrancyGuardStorageSlot().getUint256Slot().value = 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() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    /**
     * @dev A `view` only version of {nonReentrant}. Use to block view functions
     * from being called, preventing reading from inconsistent contract state.
     *
     * CAUTION: This is a "view" modifier and does not change the reentrancy
     * status. Use it only on view functions. For payable or non-payable functions,
     * use the standard {nonReentrant} modifier instead.
     */
    modifier nonReentrantView() {
        _nonReentrantBeforeView();
        _;
    }

    function _nonReentrantBeforeView() private view {
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        _nonReentrantBeforeView();

        // Any calls to nonReentrant after this point will fail
        _reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _reentrancyGuardStorageSlot().getUint256Slot().value == ENTERED;
    }

    function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
        return REENTRANCY_GUARD_STORAGE;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)

pragma solidity >=0.4.16;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        if (!_safeTransfer(token, to, value, true)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        if (!_safeTransferFrom(token, from, to, value, true)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
        return _safeTransfer(token, to, value, false);
    }

    /**
     * @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
     */
    function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
        return _safeTransferFrom(token, from, to, value, false);
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        if (!_safeApprove(token, spender, value, false)) {
            if (!_safeApprove(token, spender, 0, true)) revert SafeERC20FailedOperation(address(token));
            if (!_safeApprove(token, spender, value, true)) revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that relies on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Oppositely, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity `token.transfer(to, value)` call, relaxing the requirement on the return value: the
     * return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param to The recipient of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeTransfer(IERC20 token, address to, uint256 value, bool bubble) private returns (bool success) {
        bytes4 selector = IERC20.transfer.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(to, shr(96, not(0))))
            mstore(0x24, value)
            success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
        }
    }

    /**
     * @dev Imitates a Solidity `token.transferFrom(from, to, value)` call, relaxing the requirement on the return
     * value: the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param from The sender of the tokens
     * @param to The recipient of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value,
        bool bubble
    ) private returns (bool success) {
        bytes4 selector = IERC20.transferFrom.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(from, shr(96, not(0))))
            mstore(0x24, and(to, shr(96, not(0))))
            mstore(0x44, value)
            success := call(gas(), token, 0, 0x00, 0x64, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
            mstore(0x60, 0)
        }
    }

    /**
     * @dev Imitates a Solidity `token.approve(spender, value)` call, relaxing the requirement on the return value:
     * the return value is optional (but if data is returned, it must not be false).
     *
     * @param token The token targeted by the call.
     * @param spender The spender of the tokens
     * @param value The amount of token to transfer
     * @param bubble Behavior switch if the transfer call reverts: bubble the revert reason or return a false boolean.
     */
    function _safeApprove(IERC20 token, address spender, uint256 value, bool bubble) private returns (bool success) {
        bytes4 selector = IERC20.approve.selector;

        assembly ("memory-safe") {
            let fmp := mload(0x40)
            mstore(0x00, selector)
            mstore(0x04, and(spender, shr(96, not(0))))
            mstore(0x24, value)
            success := call(gas(), token, 0, 0x00, 0x44, 0x00, 0x20)
            // if call success and return is true, all is good.
            // otherwise (not success or return is not true), we need to perform further checks
            if iszero(and(success, eq(mload(0x00), 1))) {
                // if the call was a failure and bubble is enabled, bubble the error
                if and(iszero(success), bubble) {
                    returndatacopy(fmp, 0x00, returndatasize())
                    revert(fmp, returndatasize())
                }
                // if the return value is not true, then the call is only successful if:
                // - the token address has code
                // - the returndata is empty
                success := and(success, and(iszero(returndatasize()), gt(extcodesize(token), 0)))
            }
            mstore(0x40, fmp)
        }
    }
}

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

pragma solidity >=0.6.2;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 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 ERC-721 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 ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

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

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

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

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

// SPDX-License-Identifier: BSL-1.1
pragma solidity 0.8.26;

/// @title IDUTCHToken
/// @notice Interface for DUTCH token bonding curve interactions.
interface IDUTCHToken {
    /// @notice Mints tokens to a specified address.
    /// @dev Only callable by the bonding curve contract.
    /// @param to_ The address to mint tokens to.
    /// @param amount_ The amount of tokens to mint.
    function mint(address to_, uint256 amount_) external;

    /// @notice Burns tokens from a specified address.
    /// @dev Only callable by the bonding curve contract.
    /// @param from_ The address to burn tokens from.
    /// @param amount_ The amount of tokens to burn.
    function burn(address from_, uint256 amount_) external;

    /// @notice Returns the current bonding curve supply.
    /// @return The bonding curve supply.
    function bondingCurveSupply() external view returns (uint256);

    /// @notice Returns the total amount of tokens burned by users.
    /// @return The total user burned tokens.
    function getTotalUserBurned() external view returns (uint256);

    /// @notice Allows users to voluntarily burn their tokens.
    /// @dev Transfers tokens to burn address without decreasing supply.
    /// @param amount_ The amount of tokens to burn.
    function userBurn(uint256 amount_) external;
}

// SPDX-License-Identifier: BSL-1.1
pragma solidity 0.8.26;

/// @title IDutchVault
/// @notice Interface for DutchVault contract.
interface IDutchVault {
    /// @notice Returns the base price for a collection.
    /// @param collection_ The collection address.
    /// @return price_ The base price in wei.
    function getBasePrice(address collection_)
        external
        view
        returns (uint256 price_);

    /// @notice Returns the allocation for a collection.
    /// @param collection_ The collection address.
    /// @return allocationBPS_ The allocation in basis points.
    function getCollectionAllocation(address collection_)
        external
        view
        returns (uint16 allocationBPS_);

    /// @notice Receives a contribution from AuctionAssist.
    /// @param collection_ The collection to contribute to.
    /// @param contributor_ The contributor address.
    function receiveContribution(address collection_, address contributor_)
        external
        payable;

    /// @notice Returns contribution to AuctionAssist for user withdrawal.
    /// @param collection_ The collection to return contribution from.
    /// @param amount_ The amount to return.
    function returnContribution(address collection_, uint256 amount_)
        external;

    /// @notice Called by marketplace when a vault listing is settled.
    /// @param listingId_ The marketplace listing ID.
    /// @param buyer_ The buyer address.
    /// @param salePriceETH_ The final sale price in ETH.
    /// @param salePriceDUTCH_ The final sale price in DUTCH.
    function onListingSettled(
        uint256 listingId_,
        address buyer_,
        uint256 salePriceETH_,
        uint256 salePriceDUTCH_
    ) external;
}

File 11 of 50 : BondingCurve.sol
// SPDX-License-Identifier: BSL-1.1
pragma solidity 0.8.26;

import {UD60x18, ud} from "@prb/math/src/UD60x18.sol";

import {IWETH9} from "@uniswap/v4-periphery/src/interfaces/external/IWETH9.sol";

import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

import {IDUTCHToken} from "./interfaces/IDUTCHToken.sol";

/**
 * @title BondingCurve
 * @notice Polynomial bonding curve for DUTCH token with configurable taxes
 * @dev Implements buy and sell operations with tax distribution
 *      Price formula: P(supply) = C × supply^1.5
 *      Where C = 500_000_000 (5.0E-10 ETH)
 */
contract BondingCurve is Ownable2Step, ReentrancyGuard, Pausable {

    // -------------------------------------------------------------------------
    // Errors
    // -------------------------------------------------------------------------

    /// @notice Thrown when caller is not authorized for liquidity operations.
    error Unauthorized();

    /// @notice Thrown when swap amount is zero.
    error InvalidAmount();

    /// @notice Thrown when hook's WETH reserves are insufficient for sell.
    error InsufficientReserves();

    /// @notice Thrown when calculated output is less than minimum expected.
    error SlippageExceeded(uint256 expected, uint256 actual);

    /// @notice Thrown when invalid address provided (zero address).
    error InvalidAddress();

    /// @notice Thrown when invalid BPS value (exceeds 10000).
    error InvalidBPS();

    /// @notice Thrown when tax split percentages don't sum to 100%.
    error InvalidSplitConfig();

    /// @notice Thrown when insufficient excess reserve is available.
    error InsufficientExcessReserve();

    /// @notice Thrown when attempting to initialize a second pool.
    error PoolAlreadyInitialized();

    /// @notice Thrown when pool has incorrect token pair.
    error InvalidTokenPair();

    /// @notice Thrown when attempting operations before pool initialization.
    error PoolNotInitialized();

    /// @notice Thrown when attempting to buy before presale ends.
    error PresaleNotEnded();

    // -------------------------------------------------------------------------
    // Events
    // -------------------------------------------------------------------------

    /// @notice Emitted when DUTCH tokens are minted via buy swap.
    /// @param wethAmount_ Amount of WETH spent (including tax).
    /// @param tokenAmount_ Amount of DUTCH tokens minted.
    /// @param taxAmount_ Amount of tax collected.
    /// @param currentPrice_ Spot price after mint.
    event Minted(
        uint256 wethAmount_,
        uint256 tokenAmount_,
        uint256 taxAmount_,
        uint256 currentPrice_
    );

    /// @notice Emitted when DUTCH tokens are burned via sell swap.
    /// @param tokenAmount_ Amount of DUTCH tokens burned.
    /// @param wethAmount_ Amount of WETH refunded (net after tax).
    /// @param taxAmount_ Amount of tax collected.
    /// @param currentPrice_ Spot price after burn.
    event Burned(
        uint256 tokenAmount_,
        uint256 wethAmount_,
        uint256 taxAmount_,
        uint256 currentPrice_
    );

    /// @notice Emitted when buy tax rate is updated.
    /// @param oldBps_ Previous buy tax in basis points.
    /// @param newBps_ New buy tax in basis points.
    event BuyTaxUpdated(uint256 oldBps_, uint256 newBps_);

    /// @notice Emitted when sell tax rate is updated.
    /// @param oldBps_ Previous sell tax in basis points.
    /// @param newBps_ New sell tax in basis points.
    event SellTaxUpdated(uint256 oldBps_, uint256 newBps_);

    /// @notice Emitted when buy tax split configuration is updated.
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops wallet share in basis points.
    event BuySplitUpdated(uint256 vaultBps_, uint256 opsBps_);

    /// @notice Emitted when sell tax split configuration is updated.
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops wallet share in basis points.
    event SellSplitUpdated(uint256 vaultBps_, uint256 opsBps_);

    /// @notice Emitted when DutchVault address is updated.
    /// @param oldVault_ Previous vault address.
    /// @param newVault_ New vault address.
    event DutchVaultUpdated(address oldVault_, address newVault_);

    /// @notice Emitted when OpsWallet address is updated.
    /// @param oldWallet_ Previous wallet address.
    /// @param newWallet_ New wallet address.
    event OpsWalletUpdated(address oldWallet_, address newWallet_);

    /// @notice Emitted when excess reserve is withdrawn.
    /// @param amount_ Amount of WETH withdrawn.
    /// @param dutchVault_ DutchVault that received the WETH.
    event ExcessReserveWithdrawn(uint256 amount_, address dutchVault_);

    /// @notice Emitted when tax exemption status changes.
    /// @param account_ Address whose exemption status changed.
    /// @param exempt_ New exemption status.
    event TaxExemptionSet(address indexed account_, bool exempt_);

    // -------------------------------------------------------------------------
    // Immutables
    // -------------------------------------------------------------------------

    /// @notice DUTCH token contract.
    IDUTCHToken public immutable dutchToken;

    /// @notice WETH currency for pool interactions.
    IWETH9 private immutable _weth;

    // -------------------------------------------------------------------------
    // Constants - Bonding Curve
    // -------------------------------------------------------------------------

    /// @notice Curve constant: 5.0E-10 ETH (500,000,000 wei).
    UD60x18 private constant CURVE_CONSTANT = UD60x18.wrap(500_000_000);

    /// @notice Price exponent: 1.5.
    UD60x18 private constant EXPONENT = UD60x18.wrap(1_500000000000000000);

    /// @notice Integral exponent: 2.5 (EXPONENT + 1).
    UD60x18 private constant EXPONENT_PLUS_ONE = UD60x18.wrap(2_500000000000000000);

    // -------------------------------------------------------------------------
    // Constants - Tax Configuration
    // -------------------------------------------------------------------------

    /// @notice Basis points denominator (100%).
    uint256 private constant BPS_DENOMINATOR = 10000;

    // -------------------------------------------------------------------------
    // State - Addresses
    // -------------------------------------------------------------------------

    /// @notice DutchVault address receiving vault share of taxes.
    address private _dutchVault;

    /// @notice Operations wallet receiving ops share of taxes.
    address private _opsWallet;

    /// @notice DutchAuctionMarketplace address (tax-exempt, set at deploy).
    address private _marketplace;

    /// @notice Presale contract address; first call to buyDutchExactIn/Out must be from this. Tax-exempt.
    address private _presale;

    // -------------------------------------------------------------------------
    // State - Tax Configuration
    // -------------------------------------------------------------------------

    /// @notice Buy tax rate in basis points (default: 1000 = 10%).
    uint256 private _buyTaxBps;

    /// @notice Sell tax rate in basis points (default: 1000 = 10%).
    uint256 private _sellTaxBps;

    /// @notice Buy tax: Vault share in basis points (default: 7500 = 75%).
    uint256 private _buyVaultSplitBps;

    /// @notice Buy tax: Ops share in basis points (default: 2500 = 25%).
    uint256 private _buyOpsSplitBps;

    /// @notice Sell tax: Vault share in basis points (default: 7500 = 75%).
    uint256 private _sellVaultSplitBps;

    /// @notice Sell tax: Ops share in basis points (default: 2500 = 25%).
    uint256 private _sellOpsSplitBps;

    /// @notice Mapping of tax-exempt addresses (e.g., marketplace).
    mapping(address account => bool isExempt) private _taxExempt;

    // -------------------------------------------------------------------------
    // State - Accounting
    // -------------------------------------------------------------------------

    /// @notice WETH reserve balance backing bonding curve.
    uint256 private _wethReserve;

    /// @notice Cumulative DUTCH tokens minted through bonding curve.
    uint256 private _totalMinted;

    /// @notice Cumulative DUTCH tokens burned through bonding curve.
    uint256 private _totalBurned;

    /// @notice Cumulative WETH added to reserve (after buy tax).
    uint256 private _wethInflow;

    /// @notice Cumulative WETH removed from reserve (before sell tax).
    uint256 private _wethOutflow;

    /// @notice Total number of mint operations (buy swaps).
    uint256 private _totalMintOperations;

    /// @notice Total number of burn operations (sell swaps).
    uint256 private _totalBurnOperations;

    /// @notice Total excess WETH withdrawn from burned token range.
    uint256 private _totalExcessWithdrawn;

    /// @notice Flag indicating if the presale has ended.
    bool private _isPresaleEnded;

    constructor(
        address wethAddress_,
        address dutchToken_,
        address dutchVault_,
        address opsWallet_,
        address marketplace_,
        address presale_,
        address owner_
    ) Ownable(owner_) {
        // Validate addresses.
        if (wethAddress_ == address(0)) revert InvalidAddress();
        if (dutchToken_ == address(0)) revert InvalidAddress();
        if (dutchVault_ == address(0)) revert InvalidAddress();
        if (opsWallet_ == address(0)) revert InvalidAddress();
        if (marketplace_ == address(0)) revert InvalidAddress();
        if (presale_ == address(0)) revert InvalidAddress();
        
        dutchToken = IDUTCHToken(dutchToken_);
        _dutchVault = dutchVault_;
        _opsWallet = opsWallet_;
        _marketplace = marketplace_;
        _presale = presale_;

        // Initialize WETH
        _weth = IWETH9(wethAddress_);

        // Initialize tax configuration with defaults (configurable)
        _buyTaxBps = 1000;          // 10%.
        _sellTaxBps = 1000;         // 10%.
        _buyVaultSplitBps = 7500;   // 75%.
        _buyOpsSplitBps = 2500;     // 25%.
        _sellVaultSplitBps = 7500;  // 75%.
        _sellOpsSplitBps = 2500;    // 25%.
        
        // Mark marketplace as tax-exempt.
        _taxExempt[marketplace_] = true;
    }

    // -------------------------------------------------------------------------
    // Admin Functions
    // -------------------------------------------------------------------------

    /// @notice Updates the buy tax rate.
    /// @dev Only callable by owner.
    /// @param newBps_ New buy tax in basis points (max 10000).
    function setBuyTaxBps(uint256 newBps_) external onlyOwner {
        if (newBps_ >= BPS_DENOMINATOR) revert InvalidBPS();
        
        uint256 oldBps_ = _buyTaxBps;
        _buyTaxBps = newBps_;
        
        emit BuyTaxUpdated(oldBps_, newBps_);
    }

    /// @notice Updates the sell tax rate.
    /// @dev Only callable by owner.
    /// @param newBps_ New sell tax in basis points (max 10000).
    function setSellTaxBps(uint256 newBps_) external onlyOwner {
        if (newBps_ >= BPS_DENOMINATOR) revert InvalidBPS();
        
        uint256 oldBps_ = _sellTaxBps;
        _sellTaxBps = newBps_;
        
        emit SellTaxUpdated(oldBps_, newBps_);
    }

    /// @notice Updates the buy tax split configuration.
    /// @dev Only callable by owner. Splits must sum to 100% (10000 BPS).
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops wallet share in basis points.
    function setBuySplitConfig(uint256 vaultBps_, uint256 opsBps_)
        external
        onlyOwner
    {
        if (vaultBps_ + opsBps_ != BPS_DENOMINATOR) {
            revert InvalidSplitConfig();
        }
        
        _buyVaultSplitBps = vaultBps_;
        _buyOpsSplitBps = opsBps_;
        
        emit BuySplitUpdated(vaultBps_, opsBps_);
    }

    /// @notice Updates the sell tax split configuration.
    /// @dev Only callable by owner. Splits must sum to 100% (10000 BPS).
    /// @param vaultBps_ Vault share in basis points.
    /// @param opsBps_ Ops wallet share in basis points.
    function setSellSplitConfig(uint256 vaultBps_, uint256 opsBps_)
        external
        onlyOwner
    {
        if (vaultBps_ + opsBps_ != BPS_DENOMINATOR) {
            revert InvalidSplitConfig();
        }
        
        _sellVaultSplitBps = vaultBps_;
        _sellOpsSplitBps = opsBps_;
        
        emit SellSplitUpdated(vaultBps_, opsBps_);
    }

    /// @notice Updates the DutchVault address.
    /// @dev Only callable by owner.
    /// @param newVault_ New vault address.
    function setDutchVault(address newVault_) external onlyOwner {
        if (newVault_ == address(0)) revert InvalidAddress();
        
        address oldVault_ = _dutchVault;
        _dutchVault = newVault_;
        
        emit DutchVaultUpdated(oldVault_, newVault_);
    }

    /// @notice Updates the OpsWallet address.
    /// @dev Only callable by owner.
    /// @param newWallet_ New wallet address.
    function setOpsWallet(address newWallet_) external onlyOwner {
        if (newWallet_ == address(0)) revert InvalidAddress();
        
        emit OpsWalletUpdated(_opsWallet, newWallet_);
        _opsWallet = newWallet_;
    }

    /// @notice Sets tax exemption status for an address.
    /// @dev Only callable by owner.
    /// @param account_ Address to update exemption status.
    /// @param exempt_ True to exempt from taxes, false otherwise.
    function setTaxExemption(
        address account_,
        bool exempt_
    ) external onlyOwner {
        if (account_ == address(0)) revert InvalidAddress();
        _taxExempt[account_] = exempt_;
        emit TaxExemptionSet(account_, exempt_);
    }

    /// @notice Pauses all swap operations.
    /// @dev Only callable by owner. Prevents swaps.
    function pause() external onlyOwner {
        _pause();
    }

    /// @notice Unpauses all swap operations.
    /// @dev Only callable by owner. Resumes normal swap operations.
    function unpause() external onlyOwner {
        _unpause();
    }

    // -------------------------------------------------------------------------
    // View Functions - Configuration
    // -------------------------------------------------------------------------

    /// @notice Checks if an address is tax-exempt.
    /// @param account_ Address to check.
    /// @return True if address is exempt from taxes.
    function isTaxExempt(address account_) external view returns (bool) {
        return _taxExempt[account_];
    }

    /// @notice Returns the current buy tax rate.
    /// @return Buy tax in basis points.
    function getBuyTaxBps() external view returns (uint256) {
        return _buyTaxBps;
    }

    /// @notice Returns the current sell tax rate.
    /// @return Sell tax in basis points.
    function getSellTaxBps() external view returns (uint256) {
        return _sellTaxBps;
    }

    /// @notice Returns the buy tax split configuration.
    /// @return vaultBps_ Vault share in basis points.
    /// @return opsBps_ Ops wallet share in basis points.
    function getBuySplitConfig()
        external
        view
        returns (uint256 vaultBps_, uint256 opsBps_)
    {
        return (_buyVaultSplitBps, _buyOpsSplitBps);
    }

    /// @notice Returns the sell tax split configuration.
    /// @return vaultBps_ Vault share in basis points.
    /// @return opsBps_ Ops wallet share in basis points.
    function getSellSplitConfig()
        external
        view
        returns (uint256 vaultBps_, uint256 opsBps_)
    {
        return (_sellVaultSplitBps, _sellOpsSplitBps);
    }

    /// @notice Returns whether the presale has ended.
    /// @return True if presale has ended, false otherwise.
    function isPresaleEnded() external view returns (bool) {
        return _isPresaleEnded;
    }

    /// @notice Manually sets presale ended flag (owner only).
    /// @dev Used for testing or emergency situations.
    function setPresaleEnded() external onlyOwner {
        _isPresaleEnded = true;
    }

    /// @notice Returns the DutchVault address.
    /// @return DutchVault address.
    function getDutchVault() external view returns (address) {
        return _dutchVault;
    }

    /// @notice Returns the OpsWallet address.
    /// @return OpsWallet address.
    function getOpsWallet() external view returns (address) {
        return _opsWallet;
    }

    // -------------------------------------------------------------------------
    // View Functions - Current State, Quotes
    // -------------------------------------------------------------------------

    /// @notice Calculates available excess reserve from burned tokens.
    /// @dev Excess reserve is the WETH value of burned tokens from bottom of
    ///      curve, minus any prior withdrawals.
    /// @return available_ Amount of excess WETH available to withdraw.
    function getExcessReserve() external view returns (uint256 available_) {
        uint256 burnedTokens_ = dutchToken.getTotalUserBurned();
        uint256 excessWeth_ = _calculateCostFromZero(burnedTokens_);

        available_ = excessWeth_ > _totalExcessWithdrawn
            ? excessWeth_ - _totalExcessWithdrawn
            : 0;
    }

    /// @notice Exact-in buy quote: DUTCH out for ETH in. Includes buy tax when _buyTaxBps>0.
    /// @dev Simulates buy operation with tax. Does not account for slippage or tax exemptions.
    /// @param wethIn_ Amount of WETH to spend (including tax).
    /// @return dutchOut_ Gross amount of DUTCH tokens before distribution.
    /// @return taxAmount_ Amount of WETH taken as tax.
    /// @return netDutch_ Net DUTCH tokens user receives.
    function getQuoteWETHtoDUTCH(uint256 wethIn_)
        external
        view
        returns (uint256 dutchOut_, uint256 taxAmount_, uint256 netDutch_)
    {
        if (wethIn_ == 0) return (0, 0, 0);

        // Calculate net WETH after tax.
        uint256 netWeth_;
        if (_buyTaxBps > 0) {
            netWeth_ = (wethIn_ * (BPS_DENOMINATOR - _buyTaxBps)) /
                BPS_DENOMINATOR;
            taxAmount_ = wethIn_ - netWeth_;
        } else {
            netWeth_ = wethIn_;
            taxAmount_ = 0;
        }

        // Calculate tokens from net WETH.
        dutchOut_ = _calculateTokensFromWETH(netWeth_);
        netDutch_ = dutchOut_;
    }

    /// @notice Exact-out buy quote: ETH required for exact DUTCH. Includes buy tax when _buyTaxBps>0.
    /// @dev Simulates buy operation with tax. Does not account for slippage or tax exemptions.
    /// @param exactDutchAmount_ Exact DUTCH to receive.
    /// @return totalEthRequired_ Total ETH to send (curve + tax).
    /// @return taxAmount_ ETH that would be taken as tax (0 if _buyTaxBps==0).
    function getQuoteWETHForExactDUTCH(uint256 exactDutchAmount_)
        external
        view
        returns (uint256 totalEthRequired_, uint256 taxAmount_)
    {
        if (exactDutchAmount_ == 0) return (0, 0);

        // Calculate curve cost.
        uint256 ethForCurve_ = _calculateMintCost(exactDutchAmount_);

        // Apply buy tax.
        if (_buyTaxBps == 0) {
            totalEthRequired_ = ethForCurve_;
            taxAmount_ = 0;
        } else {
            totalEthRequired_ = (ethForCurve_ * BPS_DENOMINATOR) / (BPS_DENOMINATOR - _buyTaxBps);
            taxAmount_ = totalEthRequired_ - ethForCurve_;
        }
    }

    /// @notice Sell quote: ETH out for DUTCH in. Includes sell tax when _sellTaxBps>0.
    /// @param dutchIn_ Amount of DUTCH tokens to sell.
    /// @return wethOut_ Gross WETH refund before tax.
    /// @return taxAmount_ Amount of WETH taken as tax.
    /// @return netWeth_ Net WETH user receives.
    function getQuoteDUTCHtoWETH(uint256 dutchIn_)
        external
        view
        returns (uint256 wethOut_, uint256 taxAmount_, uint256 netWeth_)
    {
        if (dutchIn_ == 0) return (0, 0, 0);

        // Calculate gross refund.
        wethOut_ = _calculateBurnRefund(dutchIn_);

        // Apply sell tax.
        if (_sellTaxBps > 0) {
            taxAmount_ = (wethOut_ * _sellTaxBps) / BPS_DENOMINATOR;
            netWeth_ = wethOut_ - taxAmount_;
        } else {
            taxAmount_ = 0;
            netWeth_ = wethOut_;
        }
    }

    //-------------------------------------------------------------------------
    // Admin Functions - Excess Reserve
    //-------------------------------------------------------------------------

    /// @notice Withdraws excess reserve from burned tokens to DutchVault.
    /// @dev Only callable by owner. Amount must not exceed available excess.
    /// @param amount_ Amount of excess WETH to withdraw.
    function withdrawExcessReserve(uint256 amount_) external onlyOwner {
        if (amount_ == 0) revert InvalidAmount();

        uint256 burnedTokens_ = dutchToken.getTotalUserBurned();
        uint256 excessWeth_ = _calculateCostFromZero(burnedTokens_);
        uint256 available_ = excessWeth_ > _totalExcessWithdrawn
            ? excessWeth_ - _totalExcessWithdrawn
            : 0;

        if (amount_ > available_) revert InsufficientExcessReserve();
        if (amount_ > _wethReserve) revert InsufficientReserves();

        _totalExcessWithdrawn += amount_;
        _wethReserve -= amount_;

        // Unwrap WETH to ETH before sending to DutchVault.
        _weth.withdraw(amount_);
        (bool success_,) = payable(_dutchVault).call{value: amount_}("");
        if (!success_) revert InvalidAddress();
        
        emit ExcessReserveWithdrawn(amount_, _dutchVault);
    }

    // -------------------------------------------------------------------------
    // Buy / Sell Functions
    // -------------------------------------------------------------------------

    /// @notice Exact-in buy: send ETH, receive DUTCH. Reverts if DUTCH out < minDutchAmount_.
    /// @dev First buy must be from _presale. Tax: 0 if _taxExempt[caller] or _buyTaxBps==0.
    /// @param minDutchAmount_ Minimum DUTCH to receive (slippage).
    /// @param recipient_ Recipient of DUTCH.
    /// @return dutchAmount_ DUTCH minted.
    /// @return ethUsed_ ETH used (always msg.value).
    /// @return taxAmount_ ETH taken as tax (0 if exempt).
    function buyDutchExactIn(uint256 minDutchAmount_, address recipient_)
        external
        payable
        whenNotPaused
        nonReentrant
        returns (uint256 dutchAmount_, uint256 ethUsed_, uint256 taxAmount_)
    {
        if (!_isPresaleEnded && msg.sender != _presale) revert PresaleNotEnded();
        if (msg.value == 0) revert InvalidAmount();
        if (recipient_ == address(0)) revert InvalidAddress();

        // 1. Tax (exempt or zero rate => no tax).
        uint256 netWeth_;
        if (_taxExempt[msg.sender] || _buyTaxBps == 0) {
            netWeth_ = msg.value;
            taxAmount_ = 0;
        } else {
            taxAmount_ = (msg.value * _buyTaxBps) / BPS_DENOMINATOR;
            netWeth_ = msg.value - taxAmount_;
        }

        // 2. Exact-in: DUTCH from net WETH.
        dutchAmount_ = _calculateTokensFromWETH(netWeth_);
        if (dutchAmount_ == 0) revert InvalidAmount();
        if (dutchAmount_ < minDutchAmount_) revert SlippageExceeded(minDutchAmount_, dutchAmount_);

        // 3. Wrap, update reserve, mint.
        _weth.deposit{value: netWeth_}();
        _wethReserve += netWeth_;
        _wethInflow += netWeth_;
        _totalMinted += dutchAmount_;
        ++_totalMintOperations;

        // 4. Distribute buy tax.
        if (taxAmount_ > 0) {
            (uint256 vaultAmount_, uint256 opsAmount_) = _calculateBuyTax(taxAmount_);
            if (vaultAmount_ > 0) {
                (bool vaultOk,) = payable(_dutchVault).call{value: vaultAmount_}("");
                if (!vaultOk) revert InvalidAddress();
            }
            if (opsAmount_ > 0) {
                (bool opsOk,) = payable(_opsWallet).call{value: opsAmount_}("");
                if (!opsOk) revert InvalidAddress();
            }
        }

        // 5. Mint DUTCH tokens to recipient.
        dutchToken.mint(recipient_, dutchAmount_);

        // 6. Mark presale as ended.
        if (!_isPresaleEnded && msg.sender == _presale) _isPresaleEnded = true;

        // 7. Emit event.
        ethUsed_ = msg.value;
        emit Minted(msg.value, dutchAmount_, taxAmount_, getCurrentPrice());
    }

    /// @notice Exact-out buy: receive exact DUTCH for ETH. Refunds msg.value - totalRequired. Reverts if msg.value < totalRequired.
    /// @dev First buy must be from _presale. Tax: 0 if _taxExempt[caller] or _buyTaxBps==0.
    /// @param exactDutchAmount_ DUTCH to receive.
    /// @param recipient_ Recipient of DUTCH.
    /// @return dutchAmount_ DUTCH minted (equals exactDutchAmount_).
    /// @return ethUsed_ ETH used (curve + tax); excess refunded to caller.
    /// @return taxAmount_ ETH taken as tax (0 if exempt).
    function buyDutchExactOut(uint256 exactDutchAmount_, address recipient_)
        external
        payable
        whenNotPaused
        nonReentrant
        returns (uint256 dutchAmount_, uint256 ethUsed_, uint256 taxAmount_)
    {
        if (!_isPresaleEnded && msg.sender != _presale) revert PresaleNotEnded();
        if (exactDutchAmount_ == 0) revert InvalidAmount();
        if (recipient_ == address(0)) revert InvalidAddress();

        // 1. Tax (exempt or zero rate => no tax). Exact-out: ethForCurve + totalRequired.
        uint256 ethForCurve_ = _calculateMintCost(exactDutchAmount_);
        uint256 totalRequired_;
        if (_taxExempt[msg.sender] || _buyTaxBps == 0) {
            totalRequired_ = ethForCurve_;
            taxAmount_ = 0;
        } else {
            totalRequired_ = (ethForCurve_ * BPS_DENOMINATOR) / (BPS_DENOMINATOR - _buyTaxBps);
            taxAmount_ = totalRequired_ - ethForCurve_;
        }

        if (msg.value < totalRequired_) revert SlippageExceeded(totalRequired_, msg.value);

        // 2. Wrap ETH, update reserve.
        _weth.deposit{value: ethForCurve_}();
        _wethReserve += ethForCurve_;
        _wethInflow += ethForCurve_;
        _totalMinted += exactDutchAmount_;
        ++_totalMintOperations;

        // 3. Distribute buy tax.
        if (taxAmount_ > 0) {
            (uint256 vaultAmount_, uint256 opsAmount_) = _calculateBuyTax(taxAmount_);
            if (vaultAmount_ > 0) {
                (bool vaultOk,) = payable(_dutchVault).call{value: vaultAmount_}("");
                if (!vaultOk) revert InvalidAddress();
            }
            if (opsAmount_ > 0) {
                (bool opsOk,) = payable(_opsWallet).call{value: opsAmount_}("");
                if (!opsOk) revert InvalidAddress();
            }
        }

        // 4. Mint DUTCH tokens to recipient.
        dutchToken.mint(recipient_, exactDutchAmount_);

        // 5. Mark presale as ended.
        if (!_isPresaleEnded && msg.sender == _presale) _isPresaleEnded = true;

        // 6. Refund excess ETH to caller.
        uint256 excess_ = msg.value - totalRequired_;
        if (excess_ > 0) {
            (bool ok,) = payable(msg.sender).call{value: excess_}("");
            if (!ok) revert InvalidAmount();
        }

        // 7. Emit event.
        dutchAmount_ = exactDutchAmount_;
        ethUsed_ = totalRequired_;
        emit Minted(totalRequired_, exactDutchAmount_, taxAmount_, getCurrentPrice());
    }

    /// @notice Sell DUTCH for ETH. Burns caller's DUTCH; sends net ETH after sell tax. Reverts if net ETH < minEthAmount_.
    /// @dev Tax: 0 if _taxExempt[caller] or _sellTaxBps==0.
    /// @param tokenAmount_ Amount of DUTCH tokens to sell.
    /// @param minEthAmount_ Minimum ETH to receive (slippage protection).
    /// @return netEth_ Net ETH returned to user after tax.
    function redeemDUTCH(
        uint256 tokenAmount_,
        uint256 minEthAmount_
    )
        external
        whenNotPaused
        nonReentrant
        returns (uint256 netEth_)
    {
        // 1. Validate input.
        if (tokenAmount_ == 0) revert InvalidAmount();

        // 2. Calculate gross refund from curve.
        uint256 grossRefund_ = _calculateBurnRefund(tokenAmount_);

        // 3. Check reserve sufficiency.
        if (_wethReserve < grossRefund_) revert InsufficientReserves();

        // 4. Calculate tax (unless caller is exempt).
        uint256 taxAmount_;
        bool isExempt_ = _taxExempt[msg.sender];
        if (isExempt_ || _sellTaxBps == 0) {
            netEth_ = grossRefund_;
            taxAmount_ = 0;
        } else {
            taxAmount_ = (grossRefund_ * _sellTaxBps) / BPS_DENOMINATOR;
            netEth_ = grossRefund_ - taxAmount_;
        }

        // 5. Slippage protection.
        if (netEth_ < minEthAmount_) {
            revert SlippageExceeded(minEthAmount_, netEth_);
        }

        // 6. Burn tokens from caller.
        dutchToken.burn(msg.sender, tokenAmount_);

        // 7. Update state.
        _wethReserve -= grossRefund_;
        _wethOutflow += grossRefund_;
        _totalBurned += tokenAmount_;
        ++_totalBurnOperations;

        // 8. Distribute sell tax if applicable.
        if (taxAmount_ > 0) {
            (uint256 vaultAmount_, uint256 opsAmount_) =
                _calculateSellTax(taxAmount_);

            // Unwrap tax amount to ETH.
            _weth.withdraw(vaultAmount_ + opsAmount_);

            // Send tax to vault and ops wallet.
            if (vaultAmount_ > 0) {
                (bool vaultSuccess_,) = payable(_dutchVault).call{value: vaultAmount_}("");
                if (!vaultSuccess_) revert InvalidAddress();
            }
            
            if (opsAmount_ > 0) {
                (bool opsSuccess_,) = payable(_opsWallet).call{value: opsAmount_}("");
                if (!opsSuccess_) revert InvalidAddress();
            }
        }

        // 9. Unwrap and send net refund to caller.
        _weth.withdraw(netEth_);
        (bool success_, ) = payable(msg.sender).call{value: netEth_}("");
        if (!success_) revert InvalidAddress();

        // 10. Emit event.
        emit Burned(tokenAmount_, netEth_, taxAmount_, getCurrentPrice());
    }

    /// @notice Allow receiving ETH
    receive() external payable {}

    // -------------------------------------------------------------------------
    // View Functions - Bonding Curve
    // -------------------------------------------------------------------------

    /// @notice Returns the current spot price for 1 token at current supply.
    /// @dev Price = C × supply^1.5.
    /// @return price_ The current price in WETH (18 decimals).
    function getCurrentPrice() public view returns (uint256 price_) {
        uint256 currentSupply_ = _totalMinted - _totalBurned;
        
        if (currentSupply_ == 0) {
            return 0;
        }
        
        UD60x18 supply_ = ud(currentSupply_);
        UD60x18 price60x18_ = CURVE_CONSTANT.mul(supply_.pow(EXPONENT));
        price_ = price60x18_.unwrap();
    }

    /// @notice Returns current bonding curve supply.
    /// @return supply_ Current supply (totalMinted - totalBurned).
    function getBondingCurveSupply() external view returns (uint256 supply_) {
        supply_ = _totalMinted - _totalBurned;
    }

    /// @notice Returns WETH reserve balance.
    /// @return reserve_ Current WETH reserve backing the curve.
    function getReserveBalance() external view returns (uint256 reserve_) {
        reserve_ = _wethReserve;
    }

    /// @notice Returns total minted tokens.
    /// @return total_ Cumulative DUTCH minted.
    function getTotalMinted() external view returns (uint256 total_) {
        total_ = _totalMinted;
    }

    /// @notice Returns total burned tokens.
    /// @return total_ Cumulative DUTCH burned.
    function getTotalBurned() external view returns (uint256 total_) {
        total_ = _totalBurned;
    }

    // -------------------------------------------------------------------------
    // Internal Functions - Bonding Curve Math
    // -------------------------------------------------------------------------

    /// @notice WETH cost to mint tokens at current supply. Integral: C×((s₁^2.5-s₀^2.5)/2.5).
    /// @param tokenAmount_ Tokens to mint.
    /// @return cost_ WETH cost (18 decimals).
    function _calculateMintCost(uint256 tokenAmount_)
        internal
        view
        returns (uint256 cost_)
    {
        uint256 currentSupply_ = _totalMinted - _totalBurned;

        // Convert to UD60x18 for PRBMath.
        UD60x18 s0_ = ud(currentSupply_);
        UD60x18 s1_ = ud(currentSupply_ + tokenAmount_);

        // Calculate s1^2.5 and s0^2.5.
        UD60x18 s1Pow_ = s1_.pow(EXPONENT_PLUS_ONE);
        UD60x18 s0Pow_ = s0_.pow(EXPONENT_PLUS_ONE);

        // Calculate integral: (s1^2.5 - s0^2.5) / 2.5.
        UD60x18 integral_ = (s1Pow_.sub(s0Pow_)).div(EXPONENT_PLUS_ONE);

        // Multiply by curve constant: C × integral.
        UD60x18 cost60x18_ = CURVE_CONSTANT.mul(integral_);

        // WETH uses 18 decimals, same as UD60x18, no conversion needed.
        cost_ = cost60x18_.unwrap();
    }

    /// @notice Calculates WETH refund for burning tokens.
    /// @dev Integral: C × ((s₀^2.5 - s₁^2.5) / 2.5).
    /// @param tokenAmount_ Tokens to burn.
    /// @return refund_ WETH refund (18 decimals).
    function _calculateBurnRefund(uint256 tokenAmount_)
        internal
        view
        returns (uint256 refund_)
    {
        uint256 currentSupply_ = _totalMinted - _totalBurned;

        // Convert to UD60x18 for PRBMath.
        UD60x18 s0_ = ud(currentSupply_);
        UD60x18 s1_ = ud(currentSupply_ - tokenAmount_);

        // Calculate s0^2.5 and s1^2.5.
        UD60x18 s0Pow_ = s0_.pow(EXPONENT_PLUS_ONE);
        UD60x18 s1Pow_ = s1_.pow(EXPONENT_PLUS_ONE);

        // Calculate integral: (s0^2.5 - s1^2.5) / 2.5.
        UD60x18 integral_ = (s0Pow_.sub(s1Pow_)).div(EXPONENT_PLUS_ONE);

        // Multiply by curve constant: C × integral.
        UD60x18 refund60x18_ = CURVE_CONSTANT.mul(integral_);

        // WETH uses 18 decimals, same as UD60x18, no conversion needed.
        refund_ = refund60x18_.unwrap();
    }

    /// @notice Tokens mintable for given WETH. Inverts mint curve: Δs = ((cost×2.5/C)+s0^2.5)^(1/2.5)-s0.
    /// @param wethAmount_ WETH to spend on curve.
    /// @return tokenAmount_ Tokens minted.
    function _calculateTokensFromWETH(uint256 wethAmount_)
        internal
        view
        returns (uint256 tokenAmount_)
    {
        uint256 currentSupply_ = _totalMinted - _totalBurned;

        // Convert to UD60x18 for PRBMath.
        UD60x18 s0_ = ud(currentSupply_);
        UD60x18 wethAmount60x18_ = ud(wethAmount_);

        // Calculate (cost × 2.5 / C).
        UD60x18 scaledCost_ =
            wethAmount60x18_.mul(EXPONENT_PLUS_ONE).div(CURVE_CONSTANT);

        // Calculate s0^2.5.
        UD60x18 s0Pow_ = s0_.pow(EXPONENT_PLUS_ONE);

        // Calculate (cost × 2.5 / C + s0^2.5).
        UD60x18 sum_ = scaledCost_.add(s0Pow_);

        // Calculate s1 = sum^(1/2.5).
        UD60x18 s1_ = sum_.pow(EXPONENT_PLUS_ONE.inv());

        // Token amount = s₁ - s₀.
        tokenAmount_ = s1_.unwrap() - currentSupply_;
    }

    /// @notice Tokens to burn for desired WETH. Inverts burn curve: Δs = s0-(s0^2.5-refund×2.5/C)^(1/2.5).
    /// @param wethAmount_ WETH desired.
    /// @return tokenAmount_ Tokens to burn.
    function _calculateTokensNeededForWETH(uint256 wethAmount_)
        internal
        view
        returns (uint256 tokenAmount_)
    {
        uint256 currentSupply_ = _totalMinted - _totalBurned;

        // Convert to UD60x18 for PRBMath.
        UD60x18 s0_ = ud(currentSupply_);
        UD60x18 wethAmount60x18_ = ud(wethAmount_);

        // Calculate (refund × 2.5 / C).
        UD60x18 scaledRefund_ =
            wethAmount60x18_.mul(EXPONENT_PLUS_ONE).div(CURVE_CONSTANT);

        // Calculate s0^2.5.
        UD60x18 s0Pow_ = s0_.pow(EXPONENT_PLUS_ONE);

        // Calculate (s0^2.5 - refund × 2.5 / C).
        UD60x18 diff_ = s0Pow_.sub(scaledRefund_);

        // Calculate s1 = diff^(1/2.5).
        UD60x18 s1_ = diff_.pow(EXPONENT_PLUS_ONE.inv());

        // Token amount = s₀ - s₁.
        tokenAmount_ = currentSupply_ - s1_.unwrap();
    }

    /// @notice WETH cost for tokens from curve bottom (0). Used for excess-reserve accounting.
    /// @param tokenAmount_ Tokens from bottom of curve.
    /// @return cost_ WETH (18 decimals).
    function _calculateCostFromZero(uint256 tokenAmount_)
        internal
        pure
        returns (uint256 cost_)
    {
        UD60x18 s1_ = ud(tokenAmount_);
        UD60x18 s1Pow_ = s1_.pow(EXPONENT_PLUS_ONE);
        UD60x18 integral_ = s1Pow_.div(EXPONENT_PLUS_ONE);
        UD60x18 cost60x18_ = CURVE_CONSTANT.mul(integral_);
        cost_ = cost60x18_.unwrap();
    }

    /// @notice Buy tax split: Vault and Ops. Ops gets rounding remainder.
    /// @param taxAmount_ Total tax.
    /// @return vaultAmount_ For DutchVault.
    /// @return opsAmount_ For OpsWallet.
    function _calculateBuyTax(uint256 taxAmount_)
        internal
        view
        returns (uint256 vaultAmount_, uint256 opsAmount_)
    {
        vaultAmount_ = (taxAmount_ * _buyVaultSplitBps) / BPS_DENOMINATOR;
        opsAmount_ = taxAmount_ - vaultAmount_;
    }

    /// @notice Sell tax split: Vault and Ops. Ops gets rounding remainder.
    /// @param taxAmount_ Total tax.
    /// @return vaultAmount_ For DutchVault.
    /// @return opsAmount_ For OpsWallet.
    function _calculateSellTax(uint256 taxAmount_)
        internal
        view
        returns (uint256 vaultAmount_, uint256 opsAmount_)
    {
        vaultAmount_ = (taxAmount_ * _sellVaultSplitBps) / BPS_DENOMINATOR;
        opsAmount_ = taxAmount_ - vaultAmount_;
    }
}

// SPDX-License-Identifier: BSL-1.1
pragma solidity 0.8.26;

/// @title IDutchAuctionMarketplace
/// @notice Interface for the Dutch Auction Marketplace contract.
interface IDutchAuctionMarketplace {
    /// @notice Create a listing with proceeds to seller.
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @param maxPrice_ The maximum price (auction start price).
    /// @param minPrice_ The minimum price (auction floor price).
    /// @return listingId_ The ID of the created listing.
    function createListing(
        address nftContract_,
        uint256 tokenId_,
        uint256 maxPrice_,
        uint256 minPrice_
    ) external payable returns (uint256 listingId_);

    /// @notice Create a listing with proceeds burned.
    /// @param nftContract_ The address of the NFT contract.
    /// @param tokenId_ The token ID of the NFT.
    /// @param maxPrice_ The maximum price (auction start price).
    /// @param minPrice_ The minimum price (auction floor price).
    /// @return listingId_ The ID of the created listing.
    function createListingWithBurn(
        address nftContract_,
        uint256 tokenId_,
        uint256 maxPrice_,
        uint256 minPrice_
    ) external payable returns (uint256 listingId_);

    /// @notice Cancel a listing.
    /// @param listingId_ The ID of the listing to cancel.
    function cancelListing(uint256 listingId_) external;

    /// @notice Get seller fee on settled.
    /// @return feeBps_ The fee in basis points.
    function getSellerFeeOnSettled() external view returns (uint256 feeBps_);
}

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

pragma solidity ^0.8.20;

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.

pragma solidity ^0.8.20;

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 * Example usage to set ERC-1967 implementation slot:
 * ```solidity
 * contract ERC1967 {
 *     // Define the slot. Alternatively, use the SlotDerivation library to derive the slot.
 *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
 *
 *     function _getImplementation() internal view returns (address) {
 *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
 *     }
 *
 *     function _setImplementation(address newImplementation) internal {
 *         require(newImplementation.code.length > 0);
 *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
 *     }
 * }
 * ```
 *
 * TIP: Consider using this library along with {SlotDerivation}.
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC1363.sol)

pragma solidity >=0.6.2;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (utils/introspection/IERC165.sol)

pragma solidity >=0.4.16;

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

File 17 of 50 : UD60x18.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

/*

██████╗ ██████╗ ██████╗ ███╗   ███╗ █████╗ ████████╗██╗  ██╗
██╔══██╗██╔══██╗██╔══██╗████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
██████╔╝██████╔╝██████╔╝██╔████╔██║███████║   ██║   ███████║
██╔═══╝ ██╔══██╗██╔══██╗██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
██║     ██║  ██║██████╔╝██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
╚═╝     ╚═╝  ╚═╝╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝

██╗   ██╗██████╗  ██████╗  ██████╗ ██╗  ██╗ ██╗ █████╗
██║   ██║██╔══██╗██╔════╝ ██╔═████╗╚██╗██╔╝███║██╔══██╗
██║   ██║██║  ██║███████╗ ██║██╔██║ ╚███╔╝ ╚██║╚█████╔╝
██║   ██║██║  ██║██╔═══██╗████╔╝██║ ██╔██╗  ██║██╔══██╗
╚██████╔╝██████╔╝╚██████╔╝╚██████╔╝██╔╝ ██╗ ██║╚█████╔╝
 ╚═════╝ ╚═════╝  ╚═════╝  ╚═════╝ ╚═╝  ╚═╝ ╚═╝ ╚════╝

*/

import "./ud60x18/Casting.sol";
import "./ud60x18/Constants.sol";
import "./ud60x18/Conversions.sol";
import "./ud60x18/Errors.sol";
import "./ud60x18/Helpers.sol";
import "./ud60x18/Math.sol";
import "./ud60x18/ValueType.sol";

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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title IWETH9
interface IWETH9 is IERC20 {
    /// @notice Deposit ether to get wrapped ether
    function deposit() external payable;

    /// @notice Withdraw wrapped ether to get ether
    function withdraw(uint256) external;
}

File 19 of 50 : IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC20.sol)

pragma solidity >=0.4.16;

import {IERC20} from "../token/ERC20/IERC20.sol";

File 20 of 50 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (interfaces/IERC165.sol)

pragma solidity >=0.4.16;

import {IERC165} from "../utils/introspection/IERC165.sol";

File 21 of 50 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_SD21x18 } from "../sd21x18/Constants.sol";
import { SD21x18 } from "../sd21x18/ValueType.sol";
import { uMAX_SD59x18 } from "../sd59x18/Constants.sol";
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { uMAX_UD21x18 } from "../ud21x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { UD21x18 } from "../ud21x18/ValueType.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Casts a UD60x18 number into SD1x18.
/// @dev Requirements:
/// - x ≤ uMAX_SD1x18
function intoSD1x18(UD60x18 x) pure returns (SD1x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(int256(uMAX_SD1x18))) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD1x18_Overflow(x);
    }
    result = SD1x18.wrap(int64(uint64(xUint)));
}

/// @notice Casts a UD60x18 number into SD21x18.
/// @dev Requirements:
/// - x ≤ uMAX_SD21x18
function intoSD21x18(UD60x18 x) pure returns (SD21x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(int256(uMAX_SD21x18))) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD21x18_Overflow(x);
    }
    result = SD21x18.wrap(int128(uint128(xUint)));
}

/// @notice Casts a UD60x18 number into UD2x18.
/// @dev Requirements:
/// - x ≤ uMAX_UD2x18
function intoUD2x18(UD60x18 x) pure returns (UD2x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uMAX_UD2x18) {
        revert CastingErrors.PRBMath_UD60x18_IntoUD2x18_Overflow(x);
    }
    result = UD2x18.wrap(uint64(xUint));
}

/// @notice Casts a UD60x18 number into UD21x18.
/// @dev Requirements:
/// - x ≤ uMAX_UD21x18
function intoUD21x18(UD60x18 x) pure returns (UD21x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uMAX_UD21x18) {
        revert CastingErrors.PRBMath_UD60x18_IntoUD21x18_Overflow(x);
    }
    result = UD21x18.wrap(uint128(xUint));
}

/// @notice Casts a UD60x18 number into SD59x18.
/// @dev Requirements:
/// - x ≤ uMAX_SD59x18
function intoSD59x18(UD60x18 x) pure returns (SD59x18 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > uint256(uMAX_SD59x18)) {
        revert CastingErrors.PRBMath_UD60x18_IntoSD59x18_Overflow(x);
    }
    result = SD59x18.wrap(int256(xUint));
}

/// @notice Casts a UD60x18 number into uint128.
/// @dev This is basically an alias for {unwrap}.
function intoUint256(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x);
}

/// @notice Casts a UD60x18 number into uint128.
/// @dev Requirements:
/// - x ≤ MAX_UINT128
function intoUint128(UD60x18 x) pure returns (uint128 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > MAX_UINT128) {
        revert CastingErrors.PRBMath_UD60x18_IntoUint128_Overflow(x);
    }
    result = uint128(xUint);
}

/// @notice Casts a UD60x18 number into uint40.
/// @dev Requirements:
/// - x ≤ MAX_UINT40
function intoUint40(UD60x18 x) pure returns (uint40 result) {
    uint256 xUint = UD60x18.unwrap(x);
    if (xUint > MAX_UINT40) {
        revert CastingErrors.PRBMath_UD60x18_IntoUint40_Overflow(x);
    }
    result = uint40(xUint);
}

/// @notice Alias for {wrap}.
function ud(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

/// @notice Alias for {wrap}.
function ud60x18(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

/// @notice Unwraps a UD60x18 number into uint256.
function unwrap(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x);
}

/// @notice Wraps a uint256 number into the UD60x18 value type.
function wrap(uint256 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(x);
}

File 22 of 50 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD60x18 } from "./ValueType.sol";

// NOTICE: the "u" prefix stands for "unwrapped".

/// @dev Euler's number as a UD60x18 number.
UD60x18 constant E = UD60x18.wrap(2_718281828459045235);

/// @dev The maximum input permitted in {exp}.
uint256 constant uEXP_MAX_INPUT = 133_084258667509499440;
UD60x18 constant EXP_MAX_INPUT = UD60x18.wrap(uEXP_MAX_INPUT);

/// @dev The maximum input permitted in {exp2}.
uint256 constant uEXP2_MAX_INPUT = 192e18 - 1;
UD60x18 constant EXP2_MAX_INPUT = UD60x18.wrap(uEXP2_MAX_INPUT);

/// @dev Half the UNIT number.
uint256 constant uHALF_UNIT = 0.5e18;
UD60x18 constant HALF_UNIT = UD60x18.wrap(uHALF_UNIT);

/// @dev $log_2(10)$ as a UD60x18 number.
uint256 constant uLOG2_10 = 3_321928094887362347;
UD60x18 constant LOG2_10 = UD60x18.wrap(uLOG2_10);

/// @dev $log_2(e)$ as a UD60x18 number.
uint256 constant uLOG2_E = 1_442695040888963407;
UD60x18 constant LOG2_E = UD60x18.wrap(uLOG2_E);

/// @dev The maximum value a UD60x18 number can have.
uint256 constant uMAX_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_584007913129639935;
UD60x18 constant MAX_UD60x18 = UD60x18.wrap(uMAX_UD60x18);

/// @dev The maximum whole value a UD60x18 number can have.
uint256 constant uMAX_WHOLE_UD60x18 = 115792089237316195423570985008687907853269984665640564039457_000000000000000000;
UD60x18 constant MAX_WHOLE_UD60x18 = UD60x18.wrap(uMAX_WHOLE_UD60x18);

/// @dev PI as a UD60x18 number.
UD60x18 constant PI = UD60x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of UD60x18.
uint256 constant uUNIT = 1e18;
UD60x18 constant UNIT = UD60x18.wrap(uUNIT);

/// @dev The unit number squared.
uint256 constant uUNIT_SQUARED = 1e36;
UD60x18 constant UNIT_SQUARED = UD60x18.wrap(uUNIT_SQUARED);

/// @dev Zero as a UD60x18 number.
UD60x18 constant ZERO = UD60x18.wrap(0);

File 23 of 50 : Conversions.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { uMAX_UD60x18, uUNIT } from "./Constants.sol";
import { PRBMath_UD60x18_Convert_Overflow } from "./Errors.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Converts a UD60x18 number to a simple integer by dividing it by `UNIT`.
/// @dev The result is rounded toward zero.
/// @param x The UD60x18 number to convert.
/// @return result The same number in basic integer form.
function convert(UD60x18 x) pure returns (uint256 result) {
    result = UD60x18.unwrap(x) / uUNIT;
}

/// @notice Converts a simple integer to UD60x18 by multiplying it by `UNIT`.
///
/// @dev Requirements:
/// - x ≤ MAX_UD60x18 / UNIT
///
/// @param x The basic integer to convert.
/// @return result The same number converted to UD60x18.
function convert(uint256 x) pure returns (UD60x18 result) {
    if (x > uMAX_UD60x18 / uUNIT) {
        revert PRBMath_UD60x18_Convert_Overflow(x);
    }
    unchecked {
        result = UD60x18.wrap(x * uUNIT);
    }
}

File 24 of 50 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD60x18 } from "./ValueType.sol";

/// @notice Thrown when ceiling a number overflows UD60x18.
error PRBMath_UD60x18_Ceil_Overflow(UD60x18 x);

/// @notice Thrown when converting a basic integer to the fixed-point format overflows UD60x18.
error PRBMath_UD60x18_Convert_Overflow(uint256 x);

/// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
error PRBMath_UD60x18_Exp_InputTooBig(UD60x18 x);

/// @notice Thrown when taking the binary exponent of a base greater than 192e18.
error PRBMath_UD60x18_Exp2_InputTooBig(UD60x18 x);

/// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows UD60x18.
error PRBMath_UD60x18_Gm_Overflow(UD60x18 x, UD60x18 y);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD1x18.
error PRBMath_UD60x18_IntoSD1x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD21x18.
error PRBMath_UD60x18_IntoSD21x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in SD59x18.
error PRBMath_UD60x18_IntoSD59x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD2x18.
error PRBMath_UD60x18_IntoUD2x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in UD21x18.
error PRBMath_UD60x18_IntoUD21x18_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint128.
error PRBMath_UD60x18_IntoUint128_Overflow(UD60x18 x);

/// @notice Thrown when trying to cast a UD60x18 number that doesn't fit in uint40.
error PRBMath_UD60x18_IntoUint40_Overflow(UD60x18 x);

/// @notice Thrown when taking the logarithm of a number less than UNIT.
error PRBMath_UD60x18_Log_InputTooSmall(UD60x18 x);

/// @notice Thrown when calculating the square root overflows UD60x18.
error PRBMath_UD60x18_Sqrt_Overflow(UD60x18 x);

File 25 of 50 : Helpers.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { wrap } from "./Casting.sol";
import { UD60x18 } from "./ValueType.sol";

/// @notice Implements the checked addition operation (+) in the UD60x18 type.
function add(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() + y.unwrap());
}

/// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
function and(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() & bits);
}

/// @notice Implements the AND (&) bitwise operation in the UD60x18 type.
function and2(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() & y.unwrap());
}

/// @notice Implements the equal operation (==) in the UD60x18 type.
function eq(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() == y.unwrap();
}

/// @notice Implements the greater than operation (>) in the UD60x18 type.
function gt(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() > y.unwrap();
}

/// @notice Implements the greater than or equal to operation (>=) in the UD60x18 type.
function gte(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() >= y.unwrap();
}

/// @notice Implements a zero comparison check function in the UD60x18 type.
function isZero(UD60x18 x) pure returns (bool result) {
    // This wouldn't work if x could be negative.
    result = x.unwrap() == 0;
}

/// @notice Implements the left shift operation (<<) in the UD60x18 type.
function lshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() << bits);
}

/// @notice Implements the lower than operation (<) in the UD60x18 type.
function lt(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() < y.unwrap();
}

/// @notice Implements the lower than or equal to operation (<=) in the UD60x18 type.
function lte(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() <= y.unwrap();
}

/// @notice Implements the checked modulo operation (%) in the UD60x18 type.
function mod(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() % y.unwrap());
}

/// @notice Implements the not equal operation (!=) in the UD60x18 type.
function neq(UD60x18 x, UD60x18 y) pure returns (bool result) {
    result = x.unwrap() != y.unwrap();
}

/// @notice Implements the NOT (~) bitwise operation in the UD60x18 type.
function not(UD60x18 x) pure returns (UD60x18 result) {
    result = wrap(~x.unwrap());
}

/// @notice Implements the OR (|) bitwise operation in the UD60x18 type.
function or(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() | y.unwrap());
}

/// @notice Implements the right shift operation (>>) in the UD60x18 type.
function rshift(UD60x18 x, uint256 bits) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() >> bits);
}

/// @notice Implements the checked subtraction operation (-) in the UD60x18 type.
function sub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() - y.unwrap());
}

/// @notice Implements the unchecked addition operation (+) in the UD60x18 type.
function uncheckedAdd(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(x.unwrap() + y.unwrap());
    }
}

/// @notice Implements the unchecked subtraction operation (-) in the UD60x18 type.
function uncheckedSub(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(x.unwrap() - y.unwrap());
    }
}

/// @notice Implements the XOR (^) bitwise operation in the UD60x18 type.
function xor(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(x.unwrap() ^ y.unwrap());
}

File 26 of 50 : Math.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { wrap } from "./Casting.sol";
import {
    uEXP_MAX_INPUT,
    uEXP2_MAX_INPUT,
    uHALF_UNIT,
    uLOG2_10,
    uLOG2_E,
    uMAX_UD60x18,
    uMAX_WHOLE_UD60x18,
    UNIT,
    uUNIT,
    uUNIT_SQUARED,
    ZERO
} from "./Constants.sol";
import { UD60x18 } from "./ValueType.sol";

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Calculates the arithmetic average of x and y using the following formula:
///
/// $$
/// avg(x, y) = (x & y) + ((xUint ^ yUint) / 2)
/// $$
///
/// In English, this is what this formula does:
///
/// 1. AND x and y.
/// 2. Calculate half of XOR x and y.
/// 3. Add the two results together.
///
/// This technique is known as SWAR, which stands for "SIMD within a register". You can read more about it here:
/// https://devblogs.microsoft.com/oldnewthing/20220207-00/?p=106223
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The arithmetic average as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();
    unchecked {
        result = wrap((xUint & yUint) + ((xUint ^ yUint) >> 1));
    }
}

/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev This is optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x ≤ MAX_WHOLE_UD60x18
///
/// @param x The UD60x18 number to ceil.
/// @return result The smallest whole number greater than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    if (xUint > uMAX_WHOLE_UD60x18) {
        revert Errors.PRBMath_UD60x18_Ceil_Overflow(x);
    }

    assembly ("memory-safe") {
        // Equivalent to `x % UNIT`.
        let remainder := mod(x, uUNIT)

        // Equivalent to `UNIT - remainder`.
        let delta := sub(uUNIT, remainder)

        // Equivalent to `x + remainder > 0 ? delta : 0`.
        result := add(x, mul(delta, gt(remainder, 0)))
    }
}

/// @notice Divides two UD60x18 numbers, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @param x The numerator as a UD60x18 number.
/// @param y The denominator as a UD60x18 number.
/// @return result The quotient as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(Common.mulDiv(x.unwrap(), uUNIT, y.unwrap()));
}

/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Requirements:
/// - x ≤ 133_084258667509499440
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    // This check prevents values greater than 192e18 from being passed to {exp2}.
    if (xUint > uEXP_MAX_INPUT) {
        revert Errors.PRBMath_UD60x18_Exp_InputTooBig(x);
    }

    unchecked {
        // Inline the fixed-point multiplication to save gas.
        uint256 doubleUnitProduct = xUint * uLOG2_E;
        result = exp2(wrap(doubleUnitProduct / uUNIT));
    }
}

/// @notice Calculates the binary exponent of x using the binary fraction method.
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693
///
/// Requirements:
/// - x < 192e18
/// - The result must fit in UD60x18.
///
/// @param x The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
    if (xUint > uEXP2_MAX_INPUT) {
        revert Errors.PRBMath_UD60x18_Exp2_InputTooBig(x);
    }

    // Convert x to the 192.64-bit fixed-point format.
    uint256 x_192x64 = (xUint << 64) / uUNIT;

    // Pass x to the {Common.exp2} function, which uses the 192.64-bit fixed-point number representation.
    result = wrap(Common.exp2(x_192x64));
}

/// @notice Yields the greatest whole number less than or equal to x.
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
/// @param x The UD60x18 number to floor.
/// @return result The greatest whole number less than or equal to x, as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(UD60x18 x) pure returns (UD60x18 result) {
    assembly ("memory-safe") {
        // Equivalent to `x % UNIT`.
        let remainder := mod(x, uUNIT)

        // Equivalent to `x - remainder > 0 ? remainder : 0)`.
        result := sub(x, mul(remainder, gt(remainder, 0)))
    }
}

/// @notice Yields the excess beyond the floor of x using the odd function definition.
/// @dev See https://en.wikipedia.org/wiki/Fractional_part.
/// @param x The UD60x18 number to get the fractional part of.
/// @return result The fractional part of x as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function frac(UD60x18 x) pure returns (UD60x18 result) {
    assembly ("memory-safe") {
        result := mod(x, uUNIT)
    }
}

/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$, rounding down.
///
/// @dev Requirements:
/// - x * y must fit in UD60x18.
///
/// @param x The first operand as a UD60x18 number.
/// @param y The second operand as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();
    if (xUint == 0 || yUint == 0) {
        return ZERO;
    }

    unchecked {
        // Checking for overflow this way is faster than letting Solidity do it.
        uint256 xyUint = xUint * yUint;
        if (xyUint / xUint != yUint) {
            revert Errors.PRBMath_UD60x18_Gm_Overflow(x, y);
        }

        // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
        // during multiplication. See the comments in {Common.sqrt}.
        result = wrap(Common.sqrt(xyUint));
    }
}

/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The UD60x18 number for which to calculate the inverse.
/// @return result The inverse as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(UD60x18 x) pure returns (UD60x18 result) {
    unchecked {
        result = wrap(uUNIT_SQUARED / x.unwrap());
    }
}

/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(UD60x18 x) pure returns (UD60x18 result) {
    unchecked {
        // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
        // {log2} can return is ~196_205294292027477728.
        result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
    }
}

/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The UD60x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    if (xUint < uUNIT) {
        revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
    }

    // Note that the `mul` in this assembly block is the standard multiplication operation, not {UD60x18.mul}.
    // prettier-ignore
    assembly ("memory-safe") {
        switch x
        case 1 { result := mul(uUNIT, sub(0, 18)) }
        case 10 { result := mul(uUNIT, sub(1, 18)) }
        case 100 { result := mul(uUNIT, sub(2, 18)) }
        case 1000 { result := mul(uUNIT, sub(3, 18)) }
        case 10000 { result := mul(uUNIT, sub(4, 18)) }
        case 100000 { result := mul(uUNIT, sub(5, 18)) }
        case 1000000 { result := mul(uUNIT, sub(6, 18)) }
        case 10000000 { result := mul(uUNIT, sub(7, 18)) }
        case 100000000 { result := mul(uUNIT, sub(8, 18)) }
        case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
        case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
        case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
        case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
        case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
        case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
        case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
        case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
        case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
        case 1000000000000000000 { result := 0 }
        case 10000000000000000000 { result := uUNIT }
        case 100000000000000000000 { result := mul(uUNIT, 2) }
        case 1000000000000000000000 { result := mul(uUNIT, 3) }
        case 10000000000000000000000 { result := mul(uUNIT, 4) }
        case 100000000000000000000000 { result := mul(uUNIT, 5) }
        case 1000000000000000000000000 { result := mul(uUNIT, 6) }
        case 10000000000000000000000000 { result := mul(uUNIT, 7) }
        case 100000000000000000000000000 { result := mul(uUNIT, 8) }
        case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
        case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
        case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
        case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
        case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
        case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
        case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
        case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
        case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
        case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
        case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
        case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
        case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
        case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
        case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
        case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
        case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
        case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
        case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
        case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
        case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
        case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
        case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
        case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
        case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
        case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
        case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
        case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
        case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
        case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
        case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
        case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
        case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
        case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
        case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
        case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
        case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
        case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
        case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
        case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
        case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
        case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 59) }
        default { result := uMAX_UD60x18 }
    }

    if (result.unwrap() == uMAX_UD60x18) {
        unchecked {
            // Inline the fixed-point division to save gas.
            result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
        }
    }
}

/// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
///
/// $$
/// log_2{x} = n + log_2{y}, \text{ where } y = x*2^{-n}, \ y \in [1, 2)
/// $$
///
/// For $0 \leq x \lt 1$, the input is inverted:
///
/// $$
/// log_2{x} = -log_2{\frac{1}{x}}
/// $$
///
/// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation
///
/// Notes:
/// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
///
/// Requirements:
/// - x ≥ UNIT
///
/// @param x The UD60x18 number for which to calculate the binary logarithm.
/// @return result The binary logarithm as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function log2(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    if (xUint < uUNIT) {
        revert Errors.PRBMath_UD60x18_Log_InputTooSmall(x);
    }

    unchecked {
        // Calculate the integer part of the logarithm.
        uint256 n = Common.msb(xUint / uUNIT);

        // This is the integer part of the logarithm as a UD60x18 number. The operation can't overflow because n
        // n is at most 255 and UNIT is 1e18.
        uint256 resultUint = n * uUNIT;

        // Calculate $y = x * 2^{-n}$.
        uint256 y = xUint >> n;

        // If y is the unit number, the fractional part is zero.
        if (y == uUNIT) {
            return wrap(resultUint);
        }

        // Calculate the fractional part via the iterative approximation.
        // The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
        uint256 DOUBLE_UNIT = 2e18;
        for (uint256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
            y = (y * y) / uUNIT;

            // Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
            if (y >= DOUBLE_UNIT) {
                // Add the 2^{-m} factor to the logarithm.
                resultUint += delta;

                // Halve y, which corresponds to z/2 in the Wikipedia article.
                y >>= 1;
            }
        }
        result = wrap(resultUint);
    }
}

/// @notice Multiplies two UD60x18 numbers together, returning a new UD60x18 number.
///
/// @dev Uses {Common.mulDiv} to enable overflow-safe multiplication and division.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
///
/// @dev See the documentation in {Common.mulDiv18}.
/// @param x The multiplicand as a UD60x18 number.
/// @param y The multiplier as a UD60x18 number.
/// @return result The product as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function mul(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    result = wrap(Common.mulDiv18(x.unwrap(), y.unwrap()));
}

/// @notice Raises x to the power of y.
///
/// For $1 \leq x \leq \infty$, the following standard formula is used:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// For $0 \leq x \lt 1$, since the unsigned {log2} is undefined, an equivalent formula is used:
///
/// $$
/// i = \frac{1}{x}
/// w = 2^{log_2{i} * y}
/// x^y = \frac{1}{w}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2} and {mul}.
/// - Returns `UNIT` for 0^0.
/// - It may not perform well with very small values of x. Consider using SD59x18 as an alternative.
///
/// Requirements:
/// - Refer to the requirements in {exp2}, {log2}, and {mul}.
///
/// @param x The base as a UD60x18 number.
/// @param y The exponent as a UD60x18 number.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function pow(UD60x18 x, UD60x18 y) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();
    uint256 yUint = y.unwrap();

    // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
    if (xUint == 0) {
        return yUint == 0 ? UNIT : ZERO;
    }
    // If x is `UNIT`, the result is always `UNIT`.
    else if (xUint == uUNIT) {
        return UNIT;
    }

    // If y is zero, the result is always `UNIT`.
    if (yUint == 0) {
        return UNIT;
    }
    // If y is `UNIT`, the result is always x.
    else if (yUint == uUNIT) {
        return x;
    }

    // If x is > UNIT, use the standard formula.
    if (xUint > uUNIT) {
        result = exp2(mul(log2(x), y));
    }
    // Conversely, if x < UNIT, use the equivalent formula.
    else {
        UD60x18 i = wrap(uUNIT_SQUARED / xUint);
        UD60x18 w = exp2(mul(log2(i), y));
        result = wrap(uUNIT_SQUARED / w.unwrap());
    }
}

/// @notice Raises x (a UD60x18 number) to the power y (an unsigned basic integer) using the well-known
/// algorithm "exponentiation by squaring".
///
/// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv18}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - The result must fit in UD60x18.
///
/// @param x The base as a UD60x18 number.
/// @param y The exponent as a uint256.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function powu(UD60x18 x, uint256 y) pure returns (UD60x18 result) {
    // Calculate the first iteration of the loop in advance.
    uint256 xUint = x.unwrap();
    uint256 resultUint = y & 1 > 0 ? xUint : uUNIT;

    // Equivalent to `for(y /= 2; y > 0; y /= 2)`.
    for (y >>= 1; y > 0; y >>= 1) {
        xUint = Common.mulDiv18(xUint, xUint);

        // Equivalent to `y % 2 == 1`.
        if (y & 1 > 0) {
            resultUint = Common.mulDiv18(resultUint, xUint);
        }
    }
    result = wrap(resultUint);
}

/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x ≤ MAX_UD60x18 / UNIT
///
/// @param x The UD60x18 number for which to calculate the square root.
/// @return result The result as a UD60x18 number.
/// @custom:smtchecker abstract-function-nondet
function sqrt(UD60x18 x) pure returns (UD60x18 result) {
    uint256 xUint = x.unwrap();

    unchecked {
        if (xUint > uMAX_UD60x18 / uUNIT) {
            revert Errors.PRBMath_UD60x18_Sqrt_Overflow(x);
        }
        // Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two UD60x18 numbers.
        // In this case, the two numbers are both the square root.
        result = wrap(Common.sqrt(xUint * uUNIT));
    }
}

File 27 of 50 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;

/// @notice The unsigned 60.18-decimal fixed-point number representation, which can have up to 60 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the Solidity type uint256.
/// @dev The value type is defined here so it can be imported in all other files.
type UD60x18 is uint256;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD1x18,
    Casting.intoSD21x18,
    Casting.intoSD59x18,
    Casting.intoUD2x18,
    Casting.intoUD21x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
    Math.avg,
    Math.ceil,
    Math.div,
    Math.exp,
    Math.exp2,
    Math.floor,
    Math.frac,
    Math.gm,
    Math.inv,
    Math.ln,
    Math.log10,
    Math.log2,
    Math.mul,
    Math.pow,
    Math.powu,
    Math.sqrt
} for UD60x18 global;

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

// The global "using for" directive makes the functions in this library callable on the UD60x18 type.
using {
    Helpers.add,
    Helpers.and,
    Helpers.eq,
    Helpers.gt,
    Helpers.gte,
    Helpers.isZero,
    Helpers.lshift,
    Helpers.lt,
    Helpers.lte,
    Helpers.mod,
    Helpers.neq,
    Helpers.not,
    Helpers.or,
    Helpers.rshift,
    Helpers.sub,
    Helpers.uncheckedAdd,
    Helpers.uncheckedSub,
    Helpers.xor
} for UD60x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                    OPERATORS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes it possible to use these operators on the UD60x18 type.
using {
    Helpers.add as +,
    Helpers.and2 as &,
    Math.div as /,
    Helpers.eq as ==,
    Helpers.gt as >,
    Helpers.gte as >=,
    Helpers.lt as <,
    Helpers.lte as <=,
    Helpers.or as |,
    Helpers.mod as %,
    Math.mul as *,
    Helpers.neq as !=,
    Helpers.not as ~,
    Helpers.sub as -,
    Helpers.xor as ^
} for UD60x18 global;

File 28 of 50 : Common.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

// Common.sol
//
// Common mathematical functions used in both SD59x18 and UD60x18. Note that these global functions do not
// always operate with SD59x18 and UD60x18 numbers.

/*//////////////////////////////////////////////////////////////////////////
                                CUSTOM ERRORS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Thrown when the resultant value in {mulDiv} overflows uint256.
error PRBMath_MulDiv_Overflow(uint256 x, uint256 y, uint256 denominator);

/// @notice Thrown when the resultant value in {mulDiv18} overflows uint256.
error PRBMath_MulDiv18_Overflow(uint256 x, uint256 y);

/// @notice Thrown when one of the inputs passed to {mulDivSigned} is `type(int256).min`.
error PRBMath_MulDivSigned_InputTooSmall();

/// @notice Thrown when the resultant value in {mulDivSigned} overflows int256.
error PRBMath_MulDivSigned_Overflow(int256 x, int256 y);

/*//////////////////////////////////////////////////////////////////////////
                                    CONSTANTS
//////////////////////////////////////////////////////////////////////////*/

/// @dev The maximum value a uint128 number can have.
uint128 constant MAX_UINT128 = type(uint128).max;

/// @dev The maximum value a uint40 number can have.
uint40 constant MAX_UINT40 = type(uint40).max;

/// @dev The maximum value a uint64 number can have.
uint64 constant MAX_UINT64 = type(uint64).max;

/// @dev The unit number, which the decimal precision of the fixed-point types.
uint256 constant UNIT = 1e18;

/// @dev The unit number inverted mod 2^256.
uint256 constant UNIT_INVERSE = 78156646155174841979727994598816262306175212592076161876661_508869554232690281;

/// @dev The the largest power of two that divides the decimal value of `UNIT`. The logarithm of this value is the least significant
/// bit in the binary representation of `UNIT`.
uint256 constant UNIT_LPOTD = 262144;

/*//////////////////////////////////////////////////////////////////////////
                                    FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Calculates the binary exponent of x using the binary fraction method.
/// @dev Has to use 192.64-bit fixed-point numbers. See https://ethereum.stackexchange.com/a/96594/24693.
/// @param x The exponent as an unsigned 192.64-bit fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function exp2(uint256 x) pure returns (uint256 result) {
    unchecked {
        // Start from 0.5 in the 192.64-bit fixed-point format.
        result = 0x800000000000000000000000000000000000000000000000;

        // The following logic multiplies the result by $\sqrt{2^{-i}}$ when the bit at position i is 1. Key points:
        //
        // 1. Intermediate results will not overflow, as the starting point is 2^191 and all magic factors are under 2^65.
        // 2. The rationale for organizing the if statements into groups of 8 is gas savings. If the result of performing
        // a bitwise AND operation between x and any value in the array [0x80; 0x40; 0x20; 0x10; 0x08; 0x04; 0x02; 0x01] is 1,
        // we know that `x & 0xFF` is also 1.
        if (x & 0xFF00000000000000 > 0) {
            if (x & 0x8000000000000000 > 0) {
                result = (result * 0x16A09E667F3BCC909) >> 64;
            }
            if (x & 0x4000000000000000 > 0) {
                result = (result * 0x1306FE0A31B7152DF) >> 64;
            }
            if (x & 0x2000000000000000 > 0) {
                result = (result * 0x1172B83C7D517ADCE) >> 64;
            }
            if (x & 0x1000000000000000 > 0) {
                result = (result * 0x10B5586CF9890F62A) >> 64;
            }
            if (x & 0x800000000000000 > 0) {
                result = (result * 0x1059B0D31585743AE) >> 64;
            }
            if (x & 0x400000000000000 > 0) {
                result = (result * 0x102C9A3E778060EE7) >> 64;
            }
            if (x & 0x200000000000000 > 0) {
                result = (result * 0x10163DA9FB33356D8) >> 64;
            }
            if (x & 0x100000000000000 > 0) {
                result = (result * 0x100B1AFA5ABCBED61) >> 64;
            }
        }

        if (x & 0xFF000000000000 > 0) {
            if (x & 0x80000000000000 > 0) {
                result = (result * 0x10058C86DA1C09EA2) >> 64;
            }
            if (x & 0x40000000000000 > 0) {
                result = (result * 0x1002C605E2E8CEC50) >> 64;
            }
            if (x & 0x20000000000000 > 0) {
                result = (result * 0x100162F3904051FA1) >> 64;
            }
            if (x & 0x10000000000000 > 0) {
                result = (result * 0x1000B175EFFDC76BA) >> 64;
            }
            if (x & 0x8000000000000 > 0) {
                result = (result * 0x100058BA01FB9F96D) >> 64;
            }
            if (x & 0x4000000000000 > 0) {
                result = (result * 0x10002C5CC37DA9492) >> 64;
            }
            if (x & 0x2000000000000 > 0) {
                result = (result * 0x1000162E525EE0547) >> 64;
            }
            if (x & 0x1000000000000 > 0) {
                result = (result * 0x10000B17255775C04) >> 64;
            }
        }

        if (x & 0xFF0000000000 > 0) {
            if (x & 0x800000000000 > 0) {
                result = (result * 0x1000058B91B5BC9AE) >> 64;
            }
            if (x & 0x400000000000 > 0) {
                result = (result * 0x100002C5C89D5EC6D) >> 64;
            }
            if (x & 0x200000000000 > 0) {
                result = (result * 0x10000162E43F4F831) >> 64;
            }
            if (x & 0x100000000000 > 0) {
                result = (result * 0x100000B1721BCFC9A) >> 64;
            }
            if (x & 0x80000000000 > 0) {
                result = (result * 0x10000058B90CF1E6E) >> 64;
            }
            if (x & 0x40000000000 > 0) {
                result = (result * 0x1000002C5C863B73F) >> 64;
            }
            if (x & 0x20000000000 > 0) {
                result = (result * 0x100000162E430E5A2) >> 64;
            }
            if (x & 0x10000000000 > 0) {
                result = (result * 0x1000000B172183551) >> 64;
            }
        }

        if (x & 0xFF00000000 > 0) {
            if (x & 0x8000000000 > 0) {
                result = (result * 0x100000058B90C0B49) >> 64;
            }
            if (x & 0x4000000000 > 0) {
                result = (result * 0x10000002C5C8601CC) >> 64;
            }
            if (x & 0x2000000000 > 0) {
                result = (result * 0x1000000162E42FFF0) >> 64;
            }
            if (x & 0x1000000000 > 0) {
                result = (result * 0x10000000B17217FBB) >> 64;
            }
            if (x & 0x800000000 > 0) {
                result = (result * 0x1000000058B90BFCE) >> 64;
            }
            if (x & 0x400000000 > 0) {
                result = (result * 0x100000002C5C85FE3) >> 64;
            }
            if (x & 0x200000000 > 0) {
                result = (result * 0x10000000162E42FF1) >> 64;
            }
            if (x & 0x100000000 > 0) {
                result = (result * 0x100000000B17217F8) >> 64;
            }
        }

        if (x & 0xFF000000 > 0) {
            if (x & 0x80000000 > 0) {
                result = (result * 0x10000000058B90BFC) >> 64;
            }
            if (x & 0x40000000 > 0) {
                result = (result * 0x1000000002C5C85FE) >> 64;
            }
            if (x & 0x20000000 > 0) {
                result = (result * 0x100000000162E42FF) >> 64;
            }
            if (x & 0x10000000 > 0) {
                result = (result * 0x1000000000B17217F) >> 64;
            }
            if (x & 0x8000000 > 0) {
                result = (result * 0x100000000058B90C0) >> 64;
            }
            if (x & 0x4000000 > 0) {
                result = (result * 0x10000000002C5C860) >> 64;
            }
            if (x & 0x2000000 > 0) {
                result = (result * 0x1000000000162E430) >> 64;
            }
            if (x & 0x1000000 > 0) {
                result = (result * 0x10000000000B17218) >> 64;
            }
        }

        if (x & 0xFF0000 > 0) {
            if (x & 0x800000 > 0) {
                result = (result * 0x1000000000058B90C) >> 64;
            }
            if (x & 0x400000 > 0) {
                result = (result * 0x100000000002C5C86) >> 64;
            }
            if (x & 0x200000 > 0) {
                result = (result * 0x10000000000162E43) >> 64;
            }
            if (x & 0x100000 > 0) {
                result = (result * 0x100000000000B1721) >> 64;
            }
            if (x & 0x80000 > 0) {
                result = (result * 0x10000000000058B91) >> 64;
            }
            if (x & 0x40000 > 0) {
                result = (result * 0x1000000000002C5C8) >> 64;
            }
            if (x & 0x20000 > 0) {
                result = (result * 0x100000000000162E4) >> 64;
            }
            if (x & 0x10000 > 0) {
                result = (result * 0x1000000000000B172) >> 64;
            }
        }

        if (x & 0xFF00 > 0) {
            if (x & 0x8000 > 0) {
                result = (result * 0x100000000000058B9) >> 64;
            }
            if (x & 0x4000 > 0) {
                result = (result * 0x10000000000002C5D) >> 64;
            }
            if (x & 0x2000 > 0) {
                result = (result * 0x1000000000000162E) >> 64;
            }
            if (x & 0x1000 > 0) {
                result = (result * 0x10000000000000B17) >> 64;
            }
            if (x & 0x800 > 0) {
                result = (result * 0x1000000000000058C) >> 64;
            }
            if (x & 0x400 > 0) {
                result = (result * 0x100000000000002C6) >> 64;
            }
            if (x & 0x200 > 0) {
                result = (result * 0x10000000000000163) >> 64;
            }
            if (x & 0x100 > 0) {
                result = (result * 0x100000000000000B1) >> 64;
            }
        }

        if (x & 0xFF > 0) {
            if (x & 0x80 > 0) {
                result = (result * 0x10000000000000059) >> 64;
            }
            if (x & 0x40 > 0) {
                result = (result * 0x1000000000000002C) >> 64;
            }
            if (x & 0x20 > 0) {
                result = (result * 0x10000000000000016) >> 64;
            }
            if (x & 0x10 > 0) {
                result = (result * 0x1000000000000000B) >> 64;
            }
            if (x & 0x8 > 0) {
                result = (result * 0x10000000000000006) >> 64;
            }
            if (x & 0x4 > 0) {
                result = (result * 0x10000000000000003) >> 64;
            }
            if (x & 0x2 > 0) {
                result = (result * 0x10000000000000001) >> 64;
            }
            if (x & 0x1 > 0) {
                result = (result * 0x10000000000000001) >> 64;
            }
        }

        // In the code snippet below, two operations are executed simultaneously:
        //
        // 1. The result is multiplied by $(2^n + 1)$, where $2^n$ represents the integer part, and the additional 1
        // accounts for the initial guess of 0.5. This is achieved by subtracting from 191 instead of 192.
        // 2. The result is then converted to an unsigned 60.18-decimal fixed-point format.
        //
        // The underlying logic is based on the relationship $2^{191-ip} = 2^{ip} / 2^{191}$, where $ip$ denotes the,
        // integer part, $2^n$.
        result *= UNIT;
        result >>= (191 - (x >> 64));
    }
}

/// @notice Finds the zero-based index of the first 1 in the binary representation of x.
///
/// @dev See the note on "msb" in this Wikipedia article: https://en.wikipedia.org/wiki/Find_first_set
///
/// Each step in this implementation is equivalent to this high-level code:
///
/// ```solidity
/// if (x >= 2 ** 128) {
///     x >>= 128;
///     result += 128;
/// }
/// ```
///
/// Where 128 is replaced with each respective power of two factor. See the full high-level implementation here:
/// https://gist.github.com/PaulRBerg/f932f8693f2733e30c4d479e8e980948
///
/// The Yul instructions used below are:
///
/// - "gt" is "greater than"
/// - "or" is the OR bitwise operator
/// - "shl" is "shift left"
/// - "shr" is "shift right"
///
/// @param x The uint256 number for which to find the index of the most significant bit.
/// @return result The index of the most significant bit as a uint256.
/// @custom:smtchecker abstract-function-nondet
function msb(uint256 x) pure returns (uint256 result) {
    // 2^128
    assembly ("memory-safe") {
        let factor := shl(7, gt(x, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^64
    assembly ("memory-safe") {
        let factor := shl(6, gt(x, 0xFFFFFFFFFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^32
    assembly ("memory-safe") {
        let factor := shl(5, gt(x, 0xFFFFFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^16
    assembly ("memory-safe") {
        let factor := shl(4, gt(x, 0xFFFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^8
    assembly ("memory-safe") {
        let factor := shl(3, gt(x, 0xFF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^4
    assembly ("memory-safe") {
        let factor := shl(2, gt(x, 0xF))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^2
    assembly ("memory-safe") {
        let factor := shl(1, gt(x, 0x3))
        x := shr(factor, x)
        result := or(result, factor)
    }
    // 2^1
    // No need to shift x any more.
    assembly ("memory-safe") {
        let factor := gt(x, 0x1)
        result := or(result, factor)
    }
}

/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev Credits to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - The denominator must not be zero.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as a uint256.
/// @param y The multiplier as a uint256.
/// @param denominator The divisor as a uint256.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function mulDiv(uint256 x, uint256 y, uint256 denominator) pure returns (uint256 result) {
    // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
    // use the Chinese Remainder Theorem to reconstruct the 512-bit result. The result is stored in two 256
    // variables such that product = prod1 * 2^256 + prod0.
    uint256 prod0; // Least significant 256 bits of the product
    uint256 prod1; // Most significant 256 bits of the product
    assembly ("memory-safe") {
        let mm := mulmod(x, y, not(0))
        prod0 := mul(x, y)
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
    }

    // Handle non-overflow cases, 256 by 256 division.
    if (prod1 == 0) {
        unchecked {
            return prod0 / denominator;
        }
    }

    // Make sure the result is less than 2^256. Also prevents denominator == 0.
    if (prod1 >= denominator) {
        revert PRBMath_MulDiv_Overflow(x, y, denominator);
    }

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

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

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

    unchecked {
        // Calculate the largest power of two divisor of the denominator using the unary operator ~. This operation cannot overflow
        // because the denominator cannot be zero at this point in the function execution. The result is always >= 1.
        // For more detail, see https://cs.stackexchange.com/q/138556/92363.
        uint256 lpotdod = denominator & (~denominator + 1);
        uint256 flippedLpotdod;

        assembly ("memory-safe") {
            // Factor powers of two out of denominator.
            denominator := div(denominator, lpotdod)

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

            // Get the flipped value `2^256 / lpotdod`. If the `lpotdod` is zero, the flipped value is one.
            // `sub(0, lpotdod)` produces the two's complement version of `lpotdod`, which is equivalent to flipping all the bits.
            // However, `div` interprets this value as an unsigned value: https://ethereum.stackexchange.com/q/147168/24693
            flippedLpotdod := add(div(sub(0, lpotdod), lpotdod), 1)
        }

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

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

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

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

/// @notice Calculates x*y÷1e18 with 512-bit precision.
///
/// @dev A variant of {mulDiv} with constant folding, i.e. in which the denominator is hard coded to 1e18.
///
/// Notes:
/// - The body is purposely left uncommented; to understand how this works, see the documentation in {mulDiv}.
/// - The result is rounded toward zero.
/// - We take as an axiom that the result cannot be `MAX_UINT256` when x and y solve the following system of equations:
///
/// $$
/// \begin{cases}
///     x * y = MAX\_UINT256 * UNIT \\
///     (x * y) \% UNIT \geq \frac{UNIT}{2}
/// \end{cases}
/// $$
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - The result must fit in uint256.
///
/// @param x The multiplicand as an unsigned 60.18-decimal fixed-point number.
/// @param y The multiplier as an unsigned 60.18-decimal fixed-point number.
/// @return result The result as an unsigned 60.18-decimal fixed-point number.
/// @custom:smtchecker abstract-function-nondet
function mulDiv18(uint256 x, uint256 y) pure returns (uint256 result) {
    uint256 prod0;
    uint256 prod1;
    assembly ("memory-safe") {
        let mm := mulmod(x, y, not(0))
        prod0 := mul(x, y)
        prod1 := sub(sub(mm, prod0), lt(mm, prod0))
    }

    if (prod1 == 0) {
        unchecked {
            return prod0 / UNIT;
        }
    }

    if (prod1 >= UNIT) {
        revert PRBMath_MulDiv18_Overflow(x, y);
    }

    uint256 remainder;
    assembly ("memory-safe") {
        remainder := mulmod(x, y, UNIT)
        result :=
            mul(
                or(
                    div(sub(prod0, remainder), UNIT_LPOTD),
                    mul(sub(prod1, gt(remainder, prod0)), add(div(sub(0, UNIT_LPOTD), UNIT_LPOTD), 1))
                ),
                UNIT_INVERSE
            )
    }
}

/// @notice Calculates x*y÷denominator with 512-bit precision.
///
/// @dev This is an extension of {mulDiv} for signed numbers, which works by computing the signs and the absolute values separately.
///
/// Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {mulDiv}.
/// - None of the inputs can be `type(int256).min`.
/// - The result must fit in int256.
///
/// @param x The multiplicand as an int256.
/// @param y The multiplier as an int256.
/// @param denominator The divisor as an int256.
/// @return result The result as an int256.
/// @custom:smtchecker abstract-function-nondet
function mulDivSigned(int256 x, int256 y, int256 denominator) pure returns (int256 result) {
    if (x == type(int256).min || y == type(int256).min || denominator == type(int256).min) {
        revert PRBMath_MulDivSigned_InputTooSmall();
    }

    // Get hold of the absolute values of x, y and the denominator.
    uint256 xAbs;
    uint256 yAbs;
    uint256 dAbs;
    unchecked {
        xAbs = x < 0 ? uint256(-x) : uint256(x);
        yAbs = y < 0 ? uint256(-y) : uint256(y);
        dAbs = denominator < 0 ? uint256(-denominator) : uint256(denominator);
    }

    // Compute the absolute value of x*y÷denominator. The result must fit in int256.
    uint256 resultAbs = mulDiv(xAbs, yAbs, dAbs);
    if (resultAbs > uint256(type(int256).max)) {
        revert PRBMath_MulDivSigned_Overflow(x, y);
    }

    // Get the signs of x, y and the denominator.
    uint256 sx;
    uint256 sy;
    uint256 sd;
    assembly ("memory-safe") {
        // "sgt" is the "signed greater than" assembly instruction and "sub(0,1)" is -1 in two's complement.
        sx := sgt(x, sub(0, 1))
        sy := sgt(y, sub(0, 1))
        sd := sgt(denominator, sub(0, 1))
    }

    // XOR over sx, sy and sd. What this does is to check whether there are 1 or 3 negative signs in the inputs.
    // If there are, the result should be negative. Otherwise, it should be positive.
    unchecked {
        result = sx ^ sy ^ sd == 0 ? -int256(resultAbs) : int256(resultAbs);
    }
}

/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - If x is not a perfect square, the result is rounded down.
/// - Credits to OpenZeppelin for the explanations in comments below.
///
/// @param x The uint256 number for which to calculate the square root.
/// @return result The result as a uint256.
/// @custom:smtchecker abstract-function-nondet
function sqrt(uint256 x) pure returns (uint256 result) {
    if (x == 0) {
        return 0;
    }

    // For our first guess, we calculate the biggest power of 2 which is smaller than the square root of x.
    //
    // We know that the "msb" (most significant bit) of x is a power of 2 such that we have:
    //
    // $$
    // msb(x) <= x <= 2*msb(x)$
    // $$
    //
    // We write $msb(x)$ as $2^k$, and we get:
    //
    // $$
    // k = log_2(x)
    // $$
    //
    // Thus, we can write the initial inequality as:
    //
    // $$
    // 2^{log_2(x)} <= x <= 2*2^{log_2(x)+1} \\
    // sqrt(2^k) <= sqrt(x) < sqrt(2^{k+1}) \\
    // 2^{k/2} <= sqrt(x) < 2^{(k+1)/2} <= 2^{(k/2)+1}
    // $$
    //
    // Consequently, $2^{log_2(x) /2} is a good first approximation of sqrt(x) with at least one correct bit.
    uint256 xAux = uint256(x);
    result = 1;
    if (xAux >= 2 ** 128) {
        xAux >>= 128;
        result <<= 64;
    }
    if (xAux >= 2 ** 64) {
        xAux >>= 64;
        result <<= 32;
    }
    if (xAux >= 2 ** 32) {
        xAux >>= 32;
        result <<= 16;
    }
    if (xAux >= 2 ** 16) {
        xAux >>= 16;
        result <<= 8;
    }
    if (xAux >= 2 ** 8) {
        xAux >>= 8;
        result <<= 4;
    }
    if (xAux >= 2 ** 4) {
        xAux >>= 4;
        result <<= 2;
    }
    if (xAux >= 2 ** 2) {
        result <<= 1;
    }

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

        // If x is not a perfect square, round the result toward zero.
        uint256 roundedResult = x / result;
        if (result >= roundedResult) {
            result = roundedResult;
        }
    }
}

File 29 of 50 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD1x18 } from "./ValueType.sol";

/// @dev Euler's number as an SD1x18 number.
SD1x18 constant E = SD1x18.wrap(2_718281828459045235);

/// @dev The maximum value an SD1x18 number can have.
int64 constant uMAX_SD1x18 = 9_223372036854775807;
SD1x18 constant MAX_SD1x18 = SD1x18.wrap(uMAX_SD1x18);

/// @dev The minimum value an SD1x18 number can have.
int64 constant uMIN_SD1x18 = -9_223372036854775808;
SD1x18 constant MIN_SD1x18 = SD1x18.wrap(uMIN_SD1x18);

/// @dev PI as an SD1x18 number.
SD1x18 constant PI = SD1x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of SD1x18.
SD1x18 constant UNIT = SD1x18.wrap(1e18);
int64 constant uUNIT = 1e18;

File 30 of 50 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;

/// @notice The signed 1.18-decimal fixed-point number representation, which can have up to 1 digit and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int64. This is useful when end users want to use int64 to save gas, e.g. with tight variable packing in contract
/// storage.
type SD1x18 is int64;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD59x18,
    Casting.intoUD60x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for SD1x18 global;

File 31 of 50 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD21x18 } from "./ValueType.sol";

/// @dev Euler's number as an SD21x18 number.
SD21x18 constant E = SD21x18.wrap(2_718281828459045235);

/// @dev The maximum value an SD21x18 number can have.
int128 constant uMAX_SD21x18 = 170141183460469231731_687303715884105727;
SD21x18 constant MAX_SD21x18 = SD21x18.wrap(uMAX_SD21x18);

/// @dev The minimum value an SD21x18 number can have.
int128 constant uMIN_SD21x18 = -170141183460469231731_687303715884105728;
SD21x18 constant MIN_SD21x18 = SD21x18.wrap(uMIN_SD21x18);

/// @dev PI as an SD21x18 number.
SD21x18 constant PI = SD21x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of SD21x18.
SD21x18 constant UNIT = SD21x18.wrap(1e18);
int128 constant uUNIT = 1e18;

File 32 of 50 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;

/// @notice The signed 21.18-decimal fixed-point number representation, which can have up to 21 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int128. This is useful when end users want to use int128 to save gas, e.g. with tight variable packing in contract
/// storage.
type SD21x18 is int128;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD59x18,
    Casting.intoUD60x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for SD21x18 global;

File 33 of 50 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD59x18 } from "./ValueType.sol";

// NOTICE: the "u" prefix stands for "unwrapped".

/// @dev Euler's number as an SD59x18 number.
SD59x18 constant E = SD59x18.wrap(2_718281828459045235);

/// @dev The maximum input permitted in {exp}.
int256 constant uEXP_MAX_INPUT = 133_084258667509499440;
SD59x18 constant EXP_MAX_INPUT = SD59x18.wrap(uEXP_MAX_INPUT);

/// @dev Any value less than this returns 0 in {exp}.
int256 constant uEXP_MIN_THRESHOLD = -41_446531673892822322;
SD59x18 constant EXP_MIN_THRESHOLD = SD59x18.wrap(uEXP_MIN_THRESHOLD);

/// @dev The maximum input permitted in {exp2}.
int256 constant uEXP2_MAX_INPUT = 192e18 - 1;
SD59x18 constant EXP2_MAX_INPUT = SD59x18.wrap(uEXP2_MAX_INPUT);

/// @dev Any value less than this returns 0 in {exp2}.
int256 constant uEXP2_MIN_THRESHOLD = -59_794705707972522261;
SD59x18 constant EXP2_MIN_THRESHOLD = SD59x18.wrap(uEXP2_MIN_THRESHOLD);

/// @dev Half the UNIT number.
int256 constant uHALF_UNIT = 0.5e18;
SD59x18 constant HALF_UNIT = SD59x18.wrap(uHALF_UNIT);

/// @dev $log_2(10)$ as an SD59x18 number.
int256 constant uLOG2_10 = 3_321928094887362347;
SD59x18 constant LOG2_10 = SD59x18.wrap(uLOG2_10);

/// @dev $log_2(e)$ as an SD59x18 number.
int256 constant uLOG2_E = 1_442695040888963407;
SD59x18 constant LOG2_E = SD59x18.wrap(uLOG2_E);

/// @dev The maximum value an SD59x18 number can have.
int256 constant uMAX_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_792003956564819967;
SD59x18 constant MAX_SD59x18 = SD59x18.wrap(uMAX_SD59x18);

/// @dev The maximum whole value an SD59x18 number can have.
int256 constant uMAX_WHOLE_SD59x18 = 57896044618658097711785492504343953926634992332820282019728_000000000000000000;
SD59x18 constant MAX_WHOLE_SD59x18 = SD59x18.wrap(uMAX_WHOLE_SD59x18);

/// @dev The minimum value an SD59x18 number can have.
int256 constant uMIN_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_792003956564819968;
SD59x18 constant MIN_SD59x18 = SD59x18.wrap(uMIN_SD59x18);

/// @dev The minimum whole value an SD59x18 number can have.
int256 constant uMIN_WHOLE_SD59x18 = -57896044618658097711785492504343953926634992332820282019728_000000000000000000;
SD59x18 constant MIN_WHOLE_SD59x18 = SD59x18.wrap(uMIN_WHOLE_SD59x18);

/// @dev PI as an SD59x18 number.
SD59x18 constant PI = SD59x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of SD59x18.
int256 constant uUNIT = 1e18;
SD59x18 constant UNIT = SD59x18.wrap(1e18);

/// @dev The unit number squared.
int256 constant uUNIT_SQUARED = 1e36;
SD59x18 constant UNIT_SQUARED = SD59x18.wrap(uUNIT_SQUARED);

/// @dev Zero as an SD59x18 number.
SD59x18 constant ZERO = SD59x18.wrap(0);

File 34 of 50 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;
import "./Helpers.sol" as Helpers;
import "./Math.sol" as Math;

/// @notice The signed 59.18-decimal fixed-point number representation, which can have up to 59 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type int256.
type SD59x18 is int256;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoInt256,
    Casting.intoSD1x18,
    Casting.intoSD21x18,
    Casting.intoUD2x18,
    Casting.intoUD21x18,
    Casting.intoUD60x18,
    Casting.intoUint256,
    Casting.intoUint128,
    Casting.intoUint40,
    Casting.unwrap
} for SD59x18 global;

/*//////////////////////////////////////////////////////////////////////////
                            MATHEMATICAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

using {
    Math.abs,
    Math.avg,
    Math.ceil,
    Math.div,
    Math.exp,
    Math.exp2,
    Math.floor,
    Math.frac,
    Math.gm,
    Math.inv,
    Math.log10,
    Math.log2,
    Math.ln,
    Math.mul,
    Math.pow,
    Math.powu,
    Math.sqrt
} for SD59x18 global;

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

using {
    Helpers.add,
    Helpers.and,
    Helpers.eq,
    Helpers.gt,
    Helpers.gte,
    Helpers.isZero,
    Helpers.lshift,
    Helpers.lt,
    Helpers.lte,
    Helpers.mod,
    Helpers.neq,
    Helpers.not,
    Helpers.or,
    Helpers.rshift,
    Helpers.sub,
    Helpers.uncheckedAdd,
    Helpers.uncheckedSub,
    Helpers.uncheckedUnary,
    Helpers.xor
} for SD59x18 global;

/*//////////////////////////////////////////////////////////////////////////
                                    OPERATORS
//////////////////////////////////////////////////////////////////////////*/

// The global "using for" directive makes it possible to use these operators on the SD59x18 type.
using {
    Helpers.add as +,
    Helpers.and2 as &,
    Math.div as /,
    Helpers.eq as ==,
    Helpers.gt as >,
    Helpers.gte as >=,
    Helpers.lt as <,
    Helpers.lte as <=,
    Helpers.mod as %,
    Math.mul as *,
    Helpers.neq as !=,
    Helpers.not as ~,
    Helpers.or as |,
    Helpers.sub as -,
    Helpers.unary as -,
    Helpers.xor as ^
} for SD59x18 global;

File 35 of 50 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD2x18 } from "./ValueType.sol";

/// @dev Euler's number as a UD2x18 number.
UD2x18 constant E = UD2x18.wrap(2_718281828459045235);

/// @dev The maximum value a UD2x18 number can have.
uint64 constant uMAX_UD2x18 = 18_446744073709551615;
UD2x18 constant MAX_UD2x18 = UD2x18.wrap(uMAX_UD2x18);

/// @dev PI as a UD2x18 number.
UD2x18 constant PI = UD2x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of UD2x18.
UD2x18 constant UNIT = UD2x18.wrap(1e18);
uint64 constant uUNIT = 1e18;

File 36 of 50 : Constants.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD21x18 } from "./ValueType.sol";

/// @dev Euler's number as a UD21x18 number.
UD21x18 constant E = UD21x18.wrap(2_718281828459045235);

/// @dev The maximum value a UD21x18 number can have.
uint128 constant uMAX_UD21x18 = 340282366920938463463_374607431768211455;
UD21x18 constant MAX_UD21x18 = UD21x18.wrap(uMAX_UD21x18);

/// @dev PI as a UD21x18 number.
UD21x18 constant PI = UD21x18.wrap(3_141592653589793238);

/// @dev The unit number, which gives the decimal precision of UD21x18.
uint256 constant uUNIT = 1e18;
UD21x18 constant UNIT = UD21x18.wrap(1e18);

File 37 of 50 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;

/// @notice The unsigned 2.18-decimal fixed-point number representation, which can have up to 2 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type uint64. This is useful when end users want to use uint64 to save gas, e.g. with tight variable packing in contract
/// storage.
type UD2x18 is uint64;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD59x18,
    Casting.intoUD60x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for UD2x18 global;

File 38 of 50 : ValueType.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Casting.sol" as Casting;

/// @notice The unsigned 21.18-decimal fixed-point number representation, which can have up to 21 digits and up to 18
/// decimals. The values of this are bound by the minimum and the maximum values permitted by the underlying Solidity
/// type uint128. This is useful when end users want to use uint128 to save gas, e.g. with tight variable packing in contract
/// storage.
type UD21x18 is uint128;

/*//////////////////////////////////////////////////////////////////////////
                                    CASTING
//////////////////////////////////////////////////////////////////////////*/

using {
    Casting.intoSD59x18,
    Casting.intoUD60x18,
    Casting.intoUint128,
    Casting.intoUint256,
    Casting.intoUint40,
    Casting.unwrap
} for UD21x18 global;

File 39 of 50 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as CastingErrors;
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD1x18 } from "./ValueType.sol";

/// @notice Casts an SD1x18 number into SD59x18.
/// @dev There is no overflow check because SD1x18 ⊆ SD59x18.
function intoSD59x18(SD1x18 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(int256(SD1x18.unwrap(x)));
}

/// @notice Casts an SD1x18 number into UD60x18.
/// @dev Requirements:
/// - x ≥ 0
function intoUD60x18(SD1x18 x) pure returns (UD60x18 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUD60x18_Underflow(x);
    }
    result = UD60x18.wrap(uint64(xInt));
}

/// @notice Casts an SD1x18 number into uint128.
/// @dev Requirements:
/// - x ≥ 0
function intoUint128(SD1x18 x) pure returns (uint128 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUint128_Underflow(x);
    }
    result = uint128(uint64(xInt));
}

/// @notice Casts an SD1x18 number into uint256.
/// @dev Requirements:
/// - x ≥ 0
function intoUint256(SD1x18 x) pure returns (uint256 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUint256_Underflow(x);
    }
    result = uint256(uint64(xInt));
}

/// @notice Casts an SD1x18 number into uint40.
/// @dev Requirements:
/// - x ≥ 0
/// - x ≤ MAX_UINT40
function intoUint40(SD1x18 x) pure returns (uint40 result) {
    int64 xInt = SD1x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD1x18_ToUint40_Underflow(x);
    }
    if (xInt > int64(uint64(Common.MAX_UINT40))) {
        revert CastingErrors.PRBMath_SD1x18_ToUint40_Overflow(x);
    }
    result = uint40(uint64(xInt));
}

/// @notice Alias for {wrap}.
function sd1x18(int64 x) pure returns (SD1x18 result) {
    result = SD1x18.wrap(x);
}

/// @notice Unwraps an SD1x18 number into int64.
function unwrap(SD1x18 x) pure returns (int64 result) {
    result = SD1x18.unwrap(x);
}

/// @notice Wraps an int64 number into SD1x18.
function wrap(int64 x) pure returns (SD1x18 result) {
    result = SD1x18.wrap(x);
}

File 40 of 50 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as CastingErrors;
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD21x18 } from "./ValueType.sol";

/// @notice Casts an SD21x18 number into SD59x18.
/// @dev There is no overflow check because SD21x18 ⊆ SD59x18.
function intoSD59x18(SD21x18 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(int256(SD21x18.unwrap(x)));
}

/// @notice Casts an SD21x18 number into UD60x18.
/// @dev Requirements:
/// - x ≥ 0
function intoUD60x18(SD21x18 x) pure returns (UD60x18 result) {
    int128 xInt = SD21x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD21x18_ToUD60x18_Underflow(x);
    }
    result = UD60x18.wrap(uint128(xInt));
}

/// @notice Casts an SD21x18 number into uint128.
/// @dev Requirements:
/// - x ≥ 0
function intoUint128(SD21x18 x) pure returns (uint128 result) {
    int128 xInt = SD21x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD21x18_ToUint128_Underflow(x);
    }
    result = uint128(xInt);
}

/// @notice Casts an SD21x18 number into uint256.
/// @dev Requirements:
/// - x ≥ 0
function intoUint256(SD21x18 x) pure returns (uint256 result) {
    int128 xInt = SD21x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD21x18_ToUint256_Underflow(x);
    }
    result = uint256(uint128(xInt));
}

/// @notice Casts an SD21x18 number into uint40.
/// @dev Requirements:
/// - x ≥ 0
/// - x ≤ MAX_UINT40
function intoUint40(SD21x18 x) pure returns (uint40 result) {
    int128 xInt = SD21x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD21x18_ToUint40_Underflow(x);
    }
    if (xInt > int128(uint128(Common.MAX_UINT40))) {
        revert CastingErrors.PRBMath_SD21x18_ToUint40_Overflow(x);
    }
    result = uint40(uint128(xInt));
}

/// @notice Alias for {wrap}.
function sd21x18(int128 x) pure returns (SD21x18 result) {
    result = SD21x18.wrap(x);
}

/// @notice Unwraps an SD21x18 number into int128.
function unwrap(SD21x18 x) pure returns (int128 result) {
    result = SD21x18.unwrap(x);
}

/// @notice Wraps an int128 number into SD21x18.
function wrap(int128 x) pure returns (SD21x18 result) {
    result = SD21x18.wrap(x);
}

File 41 of 50 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "./Errors.sol" as CastingErrors;
import { MAX_UINT128, MAX_UINT40 } from "../Common.sol";
import { uMAX_SD1x18, uMIN_SD1x18 } from "../sd1x18/Constants.sol";
import { SD1x18 } from "../sd1x18/ValueType.sol";
import { uMAX_SD21x18, uMIN_SD21x18 } from "../sd21x18/Constants.sol";
import { SD21x18 } from "../sd21x18/ValueType.sol";
import { uMAX_UD2x18 } from "../ud2x18/Constants.sol";
import { UD2x18 } from "../ud2x18/ValueType.sol";
import { uMAX_UD21x18 } from "../ud21x18/Constants.sol";
import { UD21x18 } from "../ud21x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { SD59x18 } from "./ValueType.sol";

/// @notice Casts an SD59x18 number into int256.
/// @dev This is basically a functional alias for {unwrap}.
function intoInt256(SD59x18 x) pure returns (int256 result) {
    result = SD59x18.unwrap(x);
}

/// @notice Casts an SD59x18 number into SD1x18.
/// @dev Requirements:
/// - x ≥ uMIN_SD1x18
/// - x ≤ uMAX_SD1x18
function intoSD1x18(SD59x18 x) pure returns (SD1x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < uMIN_SD1x18) {
        revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Underflow(x);
    }
    if (xInt > uMAX_SD1x18) {
        revert CastingErrors.PRBMath_SD59x18_IntoSD1x18_Overflow(x);
    }
    result = SD1x18.wrap(int64(xInt));
}

/// @notice Casts an SD59x18 number into SD21x18.
/// @dev Requirements:
/// - x ≥ uMIN_SD21x18
/// - x ≤ uMAX_SD21x18
function intoSD21x18(SD59x18 x) pure returns (SD21x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < uMIN_SD21x18) {
        revert CastingErrors.PRBMath_SD59x18_IntoSD21x18_Underflow(x);
    }
    if (xInt > uMAX_SD21x18) {
        revert CastingErrors.PRBMath_SD59x18_IntoSD21x18_Overflow(x);
    }
    result = SD21x18.wrap(int128(xInt));
}

/// @notice Casts an SD59x18 number into UD2x18.
/// @dev Requirements:
/// - x ≥ 0
/// - x ≤ uMAX_UD2x18
function intoUD2x18(SD59x18 x) pure returns (UD2x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Underflow(x);
    }
    if (xInt > int256(uint256(uMAX_UD2x18))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD2x18_Overflow(x);
    }
    result = UD2x18.wrap(uint64(uint256(xInt)));
}

/// @notice Casts an SD59x18 number into UD21x18.
/// @dev Requirements:
/// - x ≥ 0
/// - x ≤ uMAX_UD21x18
function intoUD21x18(SD59x18 x) pure returns (UD21x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD21x18_Underflow(x);
    }
    if (xInt > int256(uint256(uMAX_UD21x18))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD21x18_Overflow(x);
    }
    result = UD21x18.wrap(uint128(uint256(xInt)));
}

/// @notice Casts an SD59x18 number into UD60x18.
/// @dev Requirements:
/// - x ≥ 0
function intoUD60x18(SD59x18 x) pure returns (UD60x18 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUD60x18_Underflow(x);
    }
    result = UD60x18.wrap(uint256(xInt));
}

/// @notice Casts an SD59x18 number into uint256.
/// @dev Requirements:
/// - x ≥ 0
function intoUint256(SD59x18 x) pure returns (uint256 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint256_Underflow(x);
    }
    result = uint256(xInt);
}

/// @notice Casts an SD59x18 number into uint128.
/// @dev Requirements:
/// - x ≥ 0
/// - x ≤ uMAX_UINT128
function intoUint128(SD59x18 x) pure returns (uint128 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint128_Underflow(x);
    }
    if (xInt > int256(uint256(MAX_UINT128))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint128_Overflow(x);
    }
    result = uint128(uint256(xInt));
}

/// @notice Casts an SD59x18 number into uint40.
/// @dev Requirements:
/// - x ≥ 0
/// - x ≤ MAX_UINT40
function intoUint40(SD59x18 x) pure returns (uint40 result) {
    int256 xInt = SD59x18.unwrap(x);
    if (xInt < 0) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint40_Underflow(x);
    }
    if (xInt > int256(uint256(MAX_UINT40))) {
        revert CastingErrors.PRBMath_SD59x18_IntoUint40_Overflow(x);
    }
    result = uint40(uint256(xInt));
}

/// @notice Alias for {wrap}.
function sd(int256 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(x);
}

/// @notice Alias for {wrap}.
function sd59x18(int256 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(x);
}

/// @notice Unwraps an SD59x18 number into int256.
function unwrap(SD59x18 x) pure returns (int256 result) {
    result = SD59x18.unwrap(x);
}

/// @notice Wraps an int256 number into SD59x18.
function wrap(int256 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(x);
}

File 42 of 50 : Helpers.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { wrap } from "./Casting.sol";
import { SD59x18 } from "./ValueType.sol";

/// @notice Implements the checked addition operation (+) in the SD59x18 type.
function add(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    return wrap(x.unwrap() + y.unwrap());
}

/// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
function and(SD59x18 x, int256 bits) pure returns (SD59x18 result) {
    return wrap(x.unwrap() & bits);
}

/// @notice Implements the AND (&) bitwise operation in the SD59x18 type.
function and2(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    return wrap(x.unwrap() & y.unwrap());
}

/// @notice Implements the equal (=) operation in the SD59x18 type.
function eq(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() == y.unwrap();
}

/// @notice Implements the greater than operation (>) in the SD59x18 type.
function gt(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() > y.unwrap();
}

/// @notice Implements the greater than or equal to operation (>=) in the SD59x18 type.
function gte(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() >= y.unwrap();
}

/// @notice Implements a zero comparison check function in the SD59x18 type.
function isZero(SD59x18 x) pure returns (bool result) {
    result = x.unwrap() == 0;
}

/// @notice Implements the left shift operation (<<) in the SD59x18 type.
function lshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() << bits);
}

/// @notice Implements the lower than operation (<) in the SD59x18 type.
function lt(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() < y.unwrap();
}

/// @notice Implements the lower than or equal to operation (<=) in the SD59x18 type.
function lte(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() <= y.unwrap();
}

/// @notice Implements the unchecked modulo operation (%) in the SD59x18 type.
function mod(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() % y.unwrap());
}

/// @notice Implements the not equal operation (!=) in the SD59x18 type.
function neq(SD59x18 x, SD59x18 y) pure returns (bool result) {
    result = x.unwrap() != y.unwrap();
}

/// @notice Implements the NOT (~) bitwise operation in the SD59x18 type.
function not(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(~x.unwrap());
}

/// @notice Implements the OR (|) bitwise operation in the SD59x18 type.
function or(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() | y.unwrap());
}

/// @notice Implements the right shift operation (>>) in the SD59x18 type.
function rshift(SD59x18 x, uint256 bits) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() >> bits);
}

/// @notice Implements the checked subtraction operation (-) in the SD59x18 type.
function sub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() - y.unwrap());
}

/// @notice Implements the checked unary minus operation (-) in the SD59x18 type.
function unary(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(-x.unwrap());
}

/// @notice Implements the unchecked addition operation (+) in the SD59x18 type.
function uncheckedAdd(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    unchecked {
        result = wrap(x.unwrap() + y.unwrap());
    }
}

/// @notice Implements the unchecked subtraction operation (-) in the SD59x18 type.
function uncheckedSub(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    unchecked {
        result = wrap(x.unwrap() - y.unwrap());
    }
}

/// @notice Implements the unchecked unary minus operation (-) in the SD59x18 type.
function uncheckedUnary(SD59x18 x) pure returns (SD59x18 result) {
    unchecked {
        result = wrap(-x.unwrap());
    }
}

/// @notice Implements the XOR (^) bitwise operation in the SD59x18 type.
function xor(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() ^ y.unwrap());
}

File 43 of 50 : Math.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import {
    uEXP_MAX_INPUT,
    uEXP2_MAX_INPUT,
    uEXP_MIN_THRESHOLD,
    uEXP2_MIN_THRESHOLD,
    uHALF_UNIT,
    uLOG2_10,
    uLOG2_E,
    uMAX_SD59x18,
    uMAX_WHOLE_SD59x18,
    uMIN_SD59x18,
    uMIN_WHOLE_SD59x18,
    UNIT,
    uUNIT,
    uUNIT_SQUARED,
    ZERO
} from "./Constants.sol";
import { wrap } from "./Helpers.sol";
import { SD59x18 } from "./ValueType.sol";

/// @notice Calculates the absolute value of x.
///
/// @dev Requirements:
/// - x > MIN_SD59x18.
///
/// @param x The SD59x18 number for which to calculate the absolute value.
/// @return result The absolute value of x as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function abs(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt == uMIN_SD59x18) {
        revert Errors.PRBMath_SD59x18_Abs_MinSD59x18();
    }
    result = xInt < 0 ? wrap(-xInt) : x;
}

/// @notice Calculates the arithmetic average of x and y.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// @param x The first operand as an SD59x18 number.
/// @param y The second operand as an SD59x18 number.
/// @return result The arithmetic average as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function avg(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();

    unchecked {
        // This operation is equivalent to `x / 2 +  y / 2`, and it can never overflow.
        int256 sum = (xInt >> 1) + (yInt >> 1);

        if (sum < 0) {
            // If at least one of x and y is odd, add 1 to the result, because shifting negative numbers to the right
            // rounds toward negative infinity. The right part is equivalent to `sum + (x % 2 == 1 || y % 2 == 1)`.
            assembly ("memory-safe") {
                result := add(sum, and(or(xInt, yInt), 1))
            }
        } else {
            // Add 1 if both x and y are odd to account for the double 0.5 remainder truncated after shifting.
            result = wrap(sum + (xInt & yInt & 1));
        }
    }
}

/// @notice Yields the smallest whole number greater than or equal to x.
///
/// @dev Optimized for fractional value inputs, because every whole value has (1e18 - 1) fractional counterparts.
/// See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x ≤ MAX_WHOLE_SD59x18
///
/// @param x The SD59x18 number to ceil.
/// @return result The smallest whole number greater than or equal to x, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function ceil(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt > uMAX_WHOLE_SD59x18) {
        revert Errors.PRBMath_SD59x18_Ceil_Overflow(x);
    }

    int256 remainder = xInt % uUNIT;
    if (remainder == 0) {
        result = x;
    } else {
        unchecked {
            // Solidity uses C fmod style, which returns a modulus with the same sign as x.
            int256 resultInt = xInt - remainder;
            if (xInt > 0) {
                resultInt += uUNIT;
            }
            result = wrap(resultInt);
        }
    }
}

/// @notice Divides two SD59x18 numbers, returning a new SD59x18 number.
///
/// @dev This is an extension of {Common.mulDiv} for signed numbers, which works by computing the signs and the absolute
/// values separately.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv}.
/// - The result is rounded toward zero.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv}.
/// - None of the inputs can be `MIN_SD59x18`.
/// - The denominator must not be zero.
/// - The result must fit in SD59x18.
///
/// @param x The numerator as an SD59x18 number.
/// @param y The denominator as an SD59x18 number.
/// @return result The quotient as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function div(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();
    if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
        revert Errors.PRBMath_SD59x18_Div_InputTooSmall();
    }

    // Get hold of the absolute values of x and y.
    uint256 xAbs;
    uint256 yAbs;
    unchecked {
        xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
        yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
    }

    // Compute the absolute value (x*UNIT÷y). The resulting value must fit in SD59x18.
    uint256 resultAbs = Common.mulDiv(xAbs, uint256(uUNIT), yAbs);
    if (resultAbs > uint256(uMAX_SD59x18)) {
        revert Errors.PRBMath_SD59x18_Div_Overflow(x, y);
    }

    // Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
    // negative, 0 for positive or zero).
    bool sameSign = (xInt ^ yInt) > -1;

    // If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
    unchecked {
        result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
    }
}

/// @notice Calculates the natural exponent of x using the following formula:
///
/// $$
/// e^x = 2^{x * log_2{e}}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {exp2}.
///
/// Requirements:
/// - Refer to the requirements in {exp2}.
/// - x < 133_084258667509499441.
///
/// @param x The exponent as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();

    // Any input less than the threshold returns zero.
    // This check also prevents an overflow for very small numbers.
    if (xInt < uEXP_MIN_THRESHOLD) {
        return ZERO;
    }

    // This check prevents values greater than 192e18 from being passed to {exp2}.
    if (xInt > uEXP_MAX_INPUT) {
        revert Errors.PRBMath_SD59x18_Exp_InputTooBig(x);
    }

    unchecked {
        // Inline the fixed-point multiplication to save gas.
        int256 doubleUnitProduct = xInt * uLOG2_E;
        result = exp2(wrap(doubleUnitProduct / uUNIT));
    }
}

/// @notice Calculates the binary exponent of x using the binary fraction method using the following formula:
///
/// $$
/// 2^{-x} = \frac{1}{2^x}
/// $$
///
/// @dev See https://ethereum.stackexchange.com/q/79903/24693.
///
/// Notes:
/// - If x < -59_794705707972522261, the result is zero.
///
/// Requirements:
/// - x < 192e18.
/// - The result must fit in SD59x18.
///
/// @param x The exponent as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function exp2(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < 0) {
        // The inverse of any number less than the threshold is truncated to zero.
        if (xInt < uEXP2_MIN_THRESHOLD) {
            return ZERO;
        }

        unchecked {
            // Inline the fixed-point inversion to save gas.
            result = wrap(uUNIT_SQUARED / exp2(wrap(-xInt)).unwrap());
        }
    } else {
        // Numbers greater than or equal to 192e18 don't fit in the 192.64-bit format.
        if (xInt > uEXP2_MAX_INPUT) {
            revert Errors.PRBMath_SD59x18_Exp2_InputTooBig(x);
        }

        unchecked {
            // Convert x to the 192.64-bit fixed-point format.
            uint256 x_192x64 = uint256((xInt << 64) / uUNIT);

            // It is safe to cast the result to int256 due to the checks above.
            result = wrap(int256(Common.exp2(x_192x64)));
        }
    }
}

/// @notice Yields the greatest whole number less than or equal to x.
///
/// @dev Optimized for fractional value inputs, because for every whole value there are (1e18 - 1) fractional
/// counterparts. See https://en.wikipedia.org/wiki/Floor_and_ceiling_functions.
///
/// Requirements:
/// - x ≥ MIN_WHOLE_SD59x18
///
/// @param x The SD59x18 number to floor.
/// @return result The greatest whole number less than or equal to x, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function floor(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < uMIN_WHOLE_SD59x18) {
        revert Errors.PRBMath_SD59x18_Floor_Underflow(x);
    }

    int256 remainder = xInt % uUNIT;
    if (remainder == 0) {
        result = x;
    } else {
        unchecked {
            // Solidity uses C fmod style, which returns a modulus with the same sign as x.
            int256 resultInt = xInt - remainder;
            if (xInt < 0) {
                resultInt -= uUNIT;
            }
            result = wrap(resultInt);
        }
    }
}

/// @notice Yields the excess beyond the floor of x for positive numbers and the part of the number to the right.
/// of the radix point for negative numbers.
/// @dev Based on the odd function definition. https://en.wikipedia.org/wiki/Fractional_part
/// @param x The SD59x18 number to get the fractional part of.
/// @return result The fractional part of x as an SD59x18 number.
function frac(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(x.unwrap() % uUNIT);
}

/// @notice Calculates the geometric mean of x and y, i.e. $\sqrt{x * y}$.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x * y must fit in SD59x18.
/// - x * y must not be negative, since complex numbers are not supported.
///
/// @param x The first operand as an SD59x18 number.
/// @param y The second operand as an SD59x18 number.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function gm(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();
    if (xInt == 0 || yInt == 0) {
        return ZERO;
    }

    unchecked {
        // Equivalent to `xy / x != y`. Checking for overflow this way is faster than letting Solidity do it.
        int256 xyInt = xInt * yInt;
        if (xyInt / xInt != yInt) {
            revert Errors.PRBMath_SD59x18_Gm_Overflow(x, y);
        }

        // The product must not be negative, since complex numbers are not supported.
        if (xyInt < 0) {
            revert Errors.PRBMath_SD59x18_Gm_NegativeProduct(x, y);
        }

        // We don't need to multiply the result by `UNIT` here because the x*y product picked up a factor of `UNIT`
        // during multiplication. See the comments in {Common.sqrt}.
        uint256 resultUint = Common.sqrt(uint256(xyInt));
        result = wrap(int256(resultUint));
    }
}

/// @notice Calculates the inverse of x.
///
/// @dev Notes:
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x must not be zero.
///
/// @param x The SD59x18 number for which to calculate the inverse.
/// @return result The inverse as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function inv(SD59x18 x) pure returns (SD59x18 result) {
    result = wrap(uUNIT_SQUARED / x.unwrap());
}

/// @notice Calculates the natural logarithm of x using the following formula:
///
/// $$
/// ln{x} = log_2{x} / log_2{e}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
/// - The precision isn't sufficiently fine-grained to return exactly `UNIT` when the input is `E`.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The SD59x18 number for which to calculate the natural logarithm.
/// @return result The natural logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function ln(SD59x18 x) pure returns (SD59x18 result) {
    // Inline the fixed-point multiplication to save gas. This is overflow-safe because the maximum value that
    // {log2} can return is ~195_205294292027477728.
    result = wrap(log2(x).unwrap() * uUNIT / uLOG2_E);
}

/// @notice Calculates the common logarithm of x using the following formula:
///
/// $$
/// log_{10}{x} = log_2{x} / log_2{10}
/// $$
///
/// However, if x is an exact power of ten, a hard coded value is returned.
///
/// @dev Notes:
/// - Refer to the notes in {log2}.
///
/// Requirements:
/// - Refer to the requirements in {log2}.
///
/// @param x The SD59x18 number for which to calculate the common logarithm.
/// @return result The common logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function log10(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < 0) {
        revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
    }

    // Note that the `mul` in this block is the standard multiplication operation, not {SD59x18.mul}.
    // prettier-ignore
    assembly ("memory-safe") {
        switch x
        case 1 { result := mul(uUNIT, sub(0, 18)) }
        case 10 { result := mul(uUNIT, sub(1, 18)) }
        case 100 { result := mul(uUNIT, sub(2, 18)) }
        case 1000 { result := mul(uUNIT, sub(3, 18)) }
        case 10000 { result := mul(uUNIT, sub(4, 18)) }
        case 100000 { result := mul(uUNIT, sub(5, 18)) }
        case 1000000 { result := mul(uUNIT, sub(6, 18)) }
        case 10000000 { result := mul(uUNIT, sub(7, 18)) }
        case 100000000 { result := mul(uUNIT, sub(8, 18)) }
        case 1000000000 { result := mul(uUNIT, sub(9, 18)) }
        case 10000000000 { result := mul(uUNIT, sub(10, 18)) }
        case 100000000000 { result := mul(uUNIT, sub(11, 18)) }
        case 1000000000000 { result := mul(uUNIT, sub(12, 18)) }
        case 10000000000000 { result := mul(uUNIT, sub(13, 18)) }
        case 100000000000000 { result := mul(uUNIT, sub(14, 18)) }
        case 1000000000000000 { result := mul(uUNIT, sub(15, 18)) }
        case 10000000000000000 { result := mul(uUNIT, sub(16, 18)) }
        case 100000000000000000 { result := mul(uUNIT, sub(17, 18)) }
        case 1000000000000000000 { result := 0 }
        case 10000000000000000000 { result := uUNIT }
        case 100000000000000000000 { result := mul(uUNIT, 2) }
        case 1000000000000000000000 { result := mul(uUNIT, 3) }
        case 10000000000000000000000 { result := mul(uUNIT, 4) }
        case 100000000000000000000000 { result := mul(uUNIT, 5) }
        case 1000000000000000000000000 { result := mul(uUNIT, 6) }
        case 10000000000000000000000000 { result := mul(uUNIT, 7) }
        case 100000000000000000000000000 { result := mul(uUNIT, 8) }
        case 1000000000000000000000000000 { result := mul(uUNIT, 9) }
        case 10000000000000000000000000000 { result := mul(uUNIT, 10) }
        case 100000000000000000000000000000 { result := mul(uUNIT, 11) }
        case 1000000000000000000000000000000 { result := mul(uUNIT, 12) }
        case 10000000000000000000000000000000 { result := mul(uUNIT, 13) }
        case 100000000000000000000000000000000 { result := mul(uUNIT, 14) }
        case 1000000000000000000000000000000000 { result := mul(uUNIT, 15) }
        case 10000000000000000000000000000000000 { result := mul(uUNIT, 16) }
        case 100000000000000000000000000000000000 { result := mul(uUNIT, 17) }
        case 1000000000000000000000000000000000000 { result := mul(uUNIT, 18) }
        case 10000000000000000000000000000000000000 { result := mul(uUNIT, 19) }
        case 100000000000000000000000000000000000000 { result := mul(uUNIT, 20) }
        case 1000000000000000000000000000000000000000 { result := mul(uUNIT, 21) }
        case 10000000000000000000000000000000000000000 { result := mul(uUNIT, 22) }
        case 100000000000000000000000000000000000000000 { result := mul(uUNIT, 23) }
        case 1000000000000000000000000000000000000000000 { result := mul(uUNIT, 24) }
        case 10000000000000000000000000000000000000000000 { result := mul(uUNIT, 25) }
        case 100000000000000000000000000000000000000000000 { result := mul(uUNIT, 26) }
        case 1000000000000000000000000000000000000000000000 { result := mul(uUNIT, 27) }
        case 10000000000000000000000000000000000000000000000 { result := mul(uUNIT, 28) }
        case 100000000000000000000000000000000000000000000000 { result := mul(uUNIT, 29) }
        case 1000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 30) }
        case 10000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 31) }
        case 100000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 32) }
        case 1000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 33) }
        case 10000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 34) }
        case 100000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 35) }
        case 1000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 36) }
        case 10000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 37) }
        case 100000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 38) }
        case 1000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 39) }
        case 10000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 40) }
        case 100000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 41) }
        case 1000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 42) }
        case 10000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 43) }
        case 100000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 44) }
        case 1000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 45) }
        case 10000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 46) }
        case 100000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 47) }
        case 1000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 48) }
        case 10000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 49) }
        case 100000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 50) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 51) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 52) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 53) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 54) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 55) }
        case 100000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 56) }
        case 1000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 57) }
        case 10000000000000000000000000000000000000000000000000000000000000000000000000000 { result := mul(uUNIT, 58) }
        default { result := uMAX_SD59x18 }
    }

    if (result.unwrap() == uMAX_SD59x18) {
        unchecked {
            // Inline the fixed-point division to save gas.
            result = wrap(log2(x).unwrap() * uUNIT / uLOG2_10);
        }
    }
}

/// @notice Calculates the binary logarithm of x using the iterative approximation algorithm:
///
/// $$
/// log_2{x} = n + log_2{y}, \text{ where } y = x*2^{-n}, \ y \in [1, 2)
/// $$
///
/// For $0 \leq x \lt 1$, the input is inverted:
///
/// $$
/// log_2{x} = -log_2{\frac{1}{x}}
/// $$
///
/// @dev See https://en.wikipedia.org/wiki/Binary_logarithm#Iterative_approximation.
///
/// Notes:
/// - Due to the lossy precision of the iterative approximation, the results are not perfectly accurate to the last decimal.
///
/// Requirements:
/// - x > 0
///
/// @param x The SD59x18 number for which to calculate the binary logarithm.
/// @return result The binary logarithm as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function log2(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt <= 0) {
        revert Errors.PRBMath_SD59x18_Log_InputTooSmall(x);
    }

    unchecked {
        int256 sign;
        if (xInt >= uUNIT) {
            sign = 1;
        } else {
            sign = -1;
            // Inline the fixed-point inversion to save gas.
            xInt = uUNIT_SQUARED / xInt;
        }

        // Calculate the integer part of the logarithm.
        uint256 n = Common.msb(uint256(xInt / uUNIT));

        // This is the integer part of the logarithm as an SD59x18 number. The operation can't overflow
        // because n is at most 255, `UNIT` is 1e18, and the sign is either 1 or -1.
        int256 resultInt = int256(n) * uUNIT;

        // Calculate $y = x * 2^{-n}$.
        int256 y = xInt >> n;

        // If y is the unit number, the fractional part is zero.
        if (y == uUNIT) {
            return wrap(resultInt * sign);
        }

        // Calculate the fractional part via the iterative approximation.
        // The `delta >>= 1` part is equivalent to `delta /= 2`, but shifting bits is more gas efficient.
        int256 DOUBLE_UNIT = 2e18;
        for (int256 delta = uHALF_UNIT; delta > 0; delta >>= 1) {
            y = (y * y) / uUNIT;

            // Is y^2 >= 2e18 and so in the range [2e18, 4e18)?
            if (y >= DOUBLE_UNIT) {
                // Add the 2^{-m} factor to the logarithm.
                resultInt = resultInt + delta;

                // Halve y, which corresponds to z/2 in the Wikipedia article.
                y >>= 1;
            }
        }
        resultInt *= sign;
        result = wrap(resultInt);
    }
}

/// @notice Multiplies two SD59x18 numbers together, returning a new SD59x18 number.
///
/// @dev Notes:
/// - Refer to the notes in {Common.mulDiv18}.
///
/// Requirements:
/// - Refer to the requirements in {Common.mulDiv18}.
/// - None of the inputs can be `MIN_SD59x18`.
/// - The result must fit in SD59x18.
///
/// @param x The multiplicand as an SD59x18 number.
/// @param y The multiplier as an SD59x18 number.
/// @return result The product as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function mul(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();
    if (xInt == uMIN_SD59x18 || yInt == uMIN_SD59x18) {
        revert Errors.PRBMath_SD59x18_Mul_InputTooSmall();
    }

    // Get hold of the absolute values of x and y.
    uint256 xAbs;
    uint256 yAbs;
    unchecked {
        xAbs = xInt < 0 ? uint256(-xInt) : uint256(xInt);
        yAbs = yInt < 0 ? uint256(-yInt) : uint256(yInt);
    }

    // Compute the absolute value (x*y÷UNIT). The resulting value must fit in SD59x18.
    uint256 resultAbs = Common.mulDiv18(xAbs, yAbs);
    if (resultAbs > uint256(uMAX_SD59x18)) {
        revert Errors.PRBMath_SD59x18_Mul_Overflow(x, y);
    }

    // Check if x and y have the same sign using two's complement representation. The left-most bit represents the sign (1 for
    // negative, 0 for positive or zero).
    bool sameSign = (xInt ^ yInt) > -1;

    // If the inputs have the same sign, the result should be positive. Otherwise, it should be negative.
    unchecked {
        result = wrap(sameSign ? int256(resultAbs) : -int256(resultAbs));
    }
}

/// @notice Raises x to the power of y using the following formula:
///
/// $$
/// x^y = 2^{log_2{x} * y}
/// $$
///
/// @dev Notes:
/// - Refer to the notes in {exp2}, {log2}, and {mul}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - Refer to the requirements in {exp2}, {log2}, and {mul}.
///
/// @param x The base as an SD59x18 number.
/// @param y Exponent to raise x to, as an SD59x18 number
/// @return result x raised to power y, as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function pow(SD59x18 x, SD59x18 y) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    int256 yInt = y.unwrap();

    // If both x and y are zero, the result is `UNIT`. If just x is zero, the result is always zero.
    if (xInt == 0) {
        return yInt == 0 ? UNIT : ZERO;
    }
    // If x is `UNIT`, the result is always `UNIT`.
    else if (xInt == uUNIT) {
        return UNIT;
    }

    // If y is zero, the result is always `UNIT`.
    if (yInt == 0) {
        return UNIT;
    }
    // If y is `UNIT`, the result is always x.
    else if (yInt == uUNIT) {
        return x;
    }

    // Calculate the result using the formula.
    result = exp2(mul(log2(x), y));
}

/// @notice Raises x (an SD59x18 number) to the power y (an unsigned basic integer) using the well-known
/// algorithm "exponentiation by squaring".
///
/// @dev See https://en.wikipedia.org/wiki/Exponentiation_by_squaring.
///
/// Notes:
/// - Refer to the notes in {Common.mulDiv18}.
/// - Returns `UNIT` for 0^0.
///
/// Requirements:
/// - Refer to the requirements in {abs} and {Common.mulDiv18}.
/// - The result must fit in SD59x18.
///
/// @param x The base as an SD59x18 number.
/// @param y The exponent as a uint256.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function powu(SD59x18 x, uint256 y) pure returns (SD59x18 result) {
    uint256 xAbs = uint256(abs(x).unwrap());

    // Calculate the first iteration of the loop in advance.
    uint256 resultAbs = y & 1 > 0 ? xAbs : uint256(uUNIT);

    // Equivalent to `for(y /= 2; y > 0; y /= 2)`.
    uint256 yAux = y;
    for (yAux >>= 1; yAux > 0; yAux >>= 1) {
        xAbs = Common.mulDiv18(xAbs, xAbs);

        // Equivalent to `y % 2 == 1`.
        if (yAux & 1 > 0) {
            resultAbs = Common.mulDiv18(resultAbs, xAbs);
        }
    }

    // The result must fit in SD59x18.
    if (resultAbs > uint256(uMAX_SD59x18)) {
        revert Errors.PRBMath_SD59x18_Powu_Overflow(x, y);
    }

    unchecked {
        // Is the base negative and the exponent odd? If yes, the result should be negative.
        int256 resultInt = int256(resultAbs);
        bool isNegative = x.unwrap() < 0 && y & 1 == 1;
        if (isNegative) {
            resultInt = -resultInt;
        }
        result = wrap(resultInt);
    }
}

/// @notice Calculates the square root of x using the Babylonian method.
///
/// @dev See https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method.
///
/// Notes:
/// - Only the positive root is returned.
/// - The result is rounded toward zero.
///
/// Requirements:
/// - x ≥ 0, since complex numbers are not supported.
/// - x ≤ MAX_SD59x18 / UNIT
///
/// @param x The SD59x18 number for which to calculate the square root.
/// @return result The result as an SD59x18 number.
/// @custom:smtchecker abstract-function-nondet
function sqrt(SD59x18 x) pure returns (SD59x18 result) {
    int256 xInt = x.unwrap();
    if (xInt < 0) {
        revert Errors.PRBMath_SD59x18_Sqrt_NegativeInput(x);
    }
    if (xInt > uMAX_SD59x18 / uUNIT) {
        revert Errors.PRBMath_SD59x18_Sqrt_Overflow(x);
    }

    unchecked {
        // Multiply x by `UNIT` to account for the factor of `UNIT` picked up when multiplying two SD59x18 numbers.
        // In this case, the two numbers are both the square root.
        uint256 resultUint = Common.sqrt(uint256(xInt * uUNIT));
        result = wrap(int256(resultUint));
    }
}

File 44 of 50 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { UD2x18 } from "./ValueType.sol";

/// @notice Casts a UD2x18 number into SD59x18.
/// @dev There is no overflow check because UD2x18 ⊆ SD59x18.
function intoSD59x18(UD2x18 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(int256(uint256(UD2x18.unwrap(x))));
}

/// @notice Casts a UD2x18 number into UD60x18.
/// @dev There is no overflow check because UD2x18 ⊆ UD60x18.
function intoUD60x18(UD2x18 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(UD2x18.unwrap(x));
}

/// @notice Casts a UD2x18 number into uint128.
/// @dev There is no overflow check because UD2x18 ⊆ uint128.
function intoUint128(UD2x18 x) pure returns (uint128 result) {
    result = uint128(UD2x18.unwrap(x));
}

/// @notice Casts a UD2x18 number into uint256.
/// @dev There is no overflow check because UD2x18 ⊆ uint256.
function intoUint256(UD2x18 x) pure returns (uint256 result) {
    result = uint256(UD2x18.unwrap(x));
}

/// @notice Casts a UD2x18 number into uint40.
/// @dev Requirements:
/// - x ≤ MAX_UINT40
function intoUint40(UD2x18 x) pure returns (uint40 result) {
    uint64 xUint = UD2x18.unwrap(x);
    if (xUint > uint64(Common.MAX_UINT40)) {
        revert Errors.PRBMath_UD2x18_IntoUint40_Overflow(x);
    }
    result = uint40(xUint);
}

/// @notice Alias for {wrap}.
function ud2x18(uint64 x) pure returns (UD2x18 result) {
    result = UD2x18.wrap(x);
}

/// @notice Unwrap a UD2x18 number into uint64.
function unwrap(UD2x18 x) pure returns (uint64 result) {
    result = UD2x18.unwrap(x);
}

/// @notice Wraps a uint64 number into UD2x18.
function wrap(uint64 x) pure returns (UD2x18 result) {
    result = UD2x18.wrap(x);
}

File 45 of 50 : Casting.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import "../Common.sol" as Common;
import "./Errors.sol" as Errors;
import { SD59x18 } from "../sd59x18/ValueType.sol";
import { UD60x18 } from "../ud60x18/ValueType.sol";
import { UD21x18 } from "./ValueType.sol";

/// @notice Casts a UD21x18 number into SD59x18.
/// @dev There is no overflow check because UD21x18 ⊆ SD59x18.
function intoSD59x18(UD21x18 x) pure returns (SD59x18 result) {
    result = SD59x18.wrap(int256(uint256(UD21x18.unwrap(x))));
}

/// @notice Casts a UD21x18 number into UD60x18.
/// @dev There is no overflow check because UD21x18 ⊆ UD60x18.
function intoUD60x18(UD21x18 x) pure returns (UD60x18 result) {
    result = UD60x18.wrap(UD21x18.unwrap(x));
}

/// @notice Casts a UD21x18 number into uint128.
/// @dev This is basically an alias for {unwrap}.
function intoUint128(UD21x18 x) pure returns (uint128 result) {
    result = UD21x18.unwrap(x);
}

/// @notice Casts a UD21x18 number into uint256.
/// @dev There is no overflow check because UD21x18 ⊆ uint256.
function intoUint256(UD21x18 x) pure returns (uint256 result) {
    result = uint256(UD21x18.unwrap(x));
}

/// @notice Casts a UD21x18 number into uint40.
/// @dev Requirements:
/// - x ≤ MAX_UINT40
function intoUint40(UD21x18 x) pure returns (uint40 result) {
    uint128 xUint = UD21x18.unwrap(x);
    if (xUint > uint128(Common.MAX_UINT40)) {
        revert Errors.PRBMath_UD21x18_IntoUint40_Overflow(x);
    }
    result = uint40(xUint);
}

/// @notice Alias for {wrap}.
function ud21x18(uint128 x) pure returns (UD21x18 result) {
    result = UD21x18.wrap(x);
}

/// @notice Unwrap a UD21x18 number into uint128.
function unwrap(UD21x18 x) pure returns (uint128 result) {
    result = UD21x18.unwrap(x);
}

/// @notice Wraps a uint128 number into UD21x18.
function wrap(uint128 x) pure returns (UD21x18 result) {
    result = UD21x18.wrap(x);
}

File 46 of 50 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD1x18 } from "./ValueType.sol";

/// @notice Thrown when trying to cast an SD1x18 number that doesn't fit in UD60x18.
error PRBMath_SD1x18_ToUD60x18_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast an SD1x18 number that doesn't fit in uint128.
error PRBMath_SD1x18_ToUint128_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast an SD1x18 number that doesn't fit in uint256.
error PRBMath_SD1x18_ToUint256_Underflow(SD1x18 x);

/// @notice Thrown when trying to cast an SD1x18 number that doesn't fit in uint40.
error PRBMath_SD1x18_ToUint40_Overflow(SD1x18 x);

/// @notice Thrown when trying to cast an SD1x18 number that doesn't fit in uint40.
error PRBMath_SD1x18_ToUint40_Underflow(SD1x18 x);

File 47 of 50 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD21x18 } from "./ValueType.sol";

/// @notice Thrown when trying to cast an SD21x18 number that doesn't fit in uint128.
error PRBMath_SD21x18_ToUint128_Underflow(SD21x18 x);

/// @notice Thrown when trying to cast an SD21x18 number that doesn't fit in UD60x18.
error PRBMath_SD21x18_ToUD60x18_Underflow(SD21x18 x);

/// @notice Thrown when trying to cast an SD21x18 number that doesn't fit in uint256.
error PRBMath_SD21x18_ToUint256_Underflow(SD21x18 x);

/// @notice Thrown when trying to cast an SD21x18 number that doesn't fit in uint40.
error PRBMath_SD21x18_ToUint40_Overflow(SD21x18 x);

/// @notice Thrown when trying to cast an SD21x18 number that doesn't fit in uint40.
error PRBMath_SD21x18_ToUint40_Underflow(SD21x18 x);

File 48 of 50 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { SD59x18 } from "./ValueType.sol";

/// @notice Thrown when taking the absolute value of `MIN_SD59x18`.
error PRBMath_SD59x18_Abs_MinSD59x18();

/// @notice Thrown when ceiling a number overflows SD59x18.
error PRBMath_SD59x18_Ceil_Overflow(SD59x18 x);

/// @notice Thrown when converting a basic integer to the fixed-point format overflows SD59x18.
error PRBMath_SD59x18_Convert_Overflow(int256 x);

/// @notice Thrown when converting a basic integer to the fixed-point format underflows SD59x18.
error PRBMath_SD59x18_Convert_Underflow(int256 x);

/// @notice Thrown when dividing two numbers and one of them is `MIN_SD59x18`.
error PRBMath_SD59x18_Div_InputTooSmall();

/// @notice Thrown when dividing two numbers and one of the intermediary unsigned results overflows SD59x18.
error PRBMath_SD59x18_Div_Overflow(SD59x18 x, SD59x18 y);

/// @notice Thrown when taking the natural exponent of a base greater than 133_084258667509499441.
error PRBMath_SD59x18_Exp_InputTooBig(SD59x18 x);

/// @notice Thrown when taking the binary exponent of a base greater than 192e18.
error PRBMath_SD59x18_Exp2_InputTooBig(SD59x18 x);

/// @notice Thrown when flooring a number underflows SD59x18.
error PRBMath_SD59x18_Floor_Underflow(SD59x18 x);

/// @notice Thrown when taking the geometric mean of two numbers and their product is negative.
error PRBMath_SD59x18_Gm_NegativeProduct(SD59x18 x, SD59x18 y);

/// @notice Thrown when taking the geometric mean of two numbers and multiplying them overflows SD59x18.
error PRBMath_SD59x18_Gm_Overflow(SD59x18 x, SD59x18 y);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in SD1x18.
error PRBMath_SD59x18_IntoSD1x18_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in SD1x18.
error PRBMath_SD59x18_IntoSD1x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in SD21x18.
error PRBMath_SD59x18_IntoSD21x18_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in SD21x18.
error PRBMath_SD59x18_IntoSD21x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in UD2x18.
error PRBMath_SD59x18_IntoUD2x18_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in UD2x18.
error PRBMath_SD59x18_IntoUD2x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in UD21x18.
error PRBMath_SD59x18_IntoUD21x18_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in UD21x18.
error PRBMath_SD59x18_IntoUD21x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in UD60x18.
error PRBMath_SD59x18_IntoUD60x18_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in uint128.
error PRBMath_SD59x18_IntoUint128_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in uint128.
error PRBMath_SD59x18_IntoUint128_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in uint256.
error PRBMath_SD59x18_IntoUint256_Underflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in uint40.
error PRBMath_SD59x18_IntoUint40_Overflow(SD59x18 x);

/// @notice Thrown when trying to cast an SD59x18 number that doesn't fit in uint40.
error PRBMath_SD59x18_IntoUint40_Underflow(SD59x18 x);

/// @notice Thrown when taking the logarithm of a number less than or equal to zero.
error PRBMath_SD59x18_Log_InputTooSmall(SD59x18 x);

/// @notice Thrown when multiplying two numbers and one of the inputs is `MIN_SD59x18`.
error PRBMath_SD59x18_Mul_InputTooSmall();

/// @notice Thrown when multiplying two numbers and the intermediary absolute result overflows SD59x18.
error PRBMath_SD59x18_Mul_Overflow(SD59x18 x, SD59x18 y);

/// @notice Thrown when raising a number to a power and the intermediary absolute result overflows SD59x18.
error PRBMath_SD59x18_Powu_Overflow(SD59x18 x, uint256 y);

/// @notice Thrown when taking the square root of a negative number.
error PRBMath_SD59x18_Sqrt_NegativeInput(SD59x18 x);

/// @notice Thrown when the calculating the square root overflows SD59x18.
error PRBMath_SD59x18_Sqrt_Overflow(SD59x18 x);

File 49 of 50 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD2x18 } from "./ValueType.sol";

/// @notice Thrown when trying to cast a UD2x18 number that doesn't fit in uint40.
error PRBMath_UD2x18_IntoUint40_Overflow(UD2x18 x);

File 50 of 50 : Errors.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;

import { UD21x18 } from "./ValueType.sol";

/// @notice Thrown when trying to cast a UD21x18 number that doesn't fit in uint40.
error PRBMath_UD21x18_IntoUint40_Overflow(UD21x18 x);

Settings
{
  "remappings": [
    "@openzeppelin-contracts-5/=dependencies/@openzeppelin-contracts-5.5.0/",
    "@sablier-lockup-2.0.1/=dependencies/@sablier-lockup-2.0.1/",
    "@uniswap-v4-core-4.0.0/=dependencies/@uniswap-v4-core-4.0.0/",
    "@uniswap-v4-periphery-1.0.3/=dependencies/@uniswap-v4-periphery-1.0.3/",
    "forge-std-1/=dependencies/forge-std-1.14.0/",
    "prb-math-4.1.0/=dependencies/prb-math-4.1.0/",
    "solmate-1.0.0/=dependencies/solmate-1.0.0/",
    "forge-std/=dependencies/forge-std-1.14.0/src/",
    "@openzeppelin/contracts/=dependencies/@openzeppelin-contracts-5.5.0/",
    "@uniswap/v4-core/=dependencies/@uniswap-v4-core-4.0.0/",
    "@uniswap/v4-periphery/=dependencies/@uniswap-v4-periphery-1.0.3/",
    "@sablier/lockup/=dependencies/@sablier-lockup-2.0.1/src/",
    "@prb/math/=dependencies/prb-math-4.1.0/",
    "solmate/=dependencies/solmate-1.0.0/",
    "@ensdomains/=dependencies/@uniswap-v4-core-4.0.0/node_modules/@ensdomains/",
    "@openzeppelin-contracts-5.5.0/=dependencies/@openzeppelin-contracts-5.5.0/",
    "ds-test/=dependencies/@uniswap-v4-core-4.0.0/lib/forge-std/lib/ds-test/src/",
    "forge-std-1.14.0/=dependencies/forge-std-1.14.0/src/",
    "hardhat/=dependencies/@uniswap-v4-core-4.0.0/node_modules/hardhat/",
    "openzeppelin-contracts/=dependencies/@uniswap-v4-periphery-1.0.3/lib/v4-core/lib/openzeppelin-contracts/",
    "solady/=dependencies/@sablier-lockup-2.0.1/node_modules/solady/",
    "solarray/=dependencies/@sablier-lockup-2.0.1/node_modules/solarray/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "cancun",
  "viaIR": true
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"address","name":"dutchToken_","type":"address"},{"internalType":"address","name":"dutchVaultAddress_","type":"address"},{"internalType":"address","name":"opsWallet_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ArrayLengthMismatch","type":"error"},{"inputs":[],"name":"BatchSizeTooLarge","type":"error"},{"inputs":[],"name":"BondingHookNotSet","type":"error"},{"inputs":[],"name":"CollectionNotWhitelisted","type":"error"},{"inputs":[],"name":"ETHRefundFailed","type":"error"},{"inputs":[],"name":"ETHTransferFailed","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidAmount","type":"error"},{"inputs":[],"name":"InvalidListing","type":"error"},{"inputs":[],"name":"InvalidSplitConfig","type":"error"},{"inputs":[],"name":"NFTAlreadyListed","type":"error"},{"inputs":[],"name":"NFTTransferFailed","type":"error"},{"inputs":[],"name":"NoFeeRequired","type":"error"},{"inputs":[],"name":"NotApproved","type":"error"},{"inputs":[],"name":"NotNFTOwner","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"actual","type":"uint256"}],"name":"SlippageExceeded","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newDuration_","type":"uint256"}],"name":"AuctionDurationUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"collection_","type":"address"},{"indexed":false,"internalType":"bool","name":"allowed_","type":"bool"}],"name":"CollectionAllowedSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"enabled_","type":"bool"}],"name":"CollectionWhitelistSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAddress_","type":"address"}],"name":"DutchVaultAddressUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"listingId_","type":"uint256"}],"name":"ListingCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"listingId_","type":"uint256"},{"indexed":true,"internalType":"address","name":"seller_","type":"address"},{"indexed":true,"internalType":"address","name":"nftContract_","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxPrice_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minPrice_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"startTime_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"duration_","type":"uint256"}],"name":"ListingCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"vaultBps_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"opsBps_","type":"uint256"}],"name":"ListingFeeSplitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"listingId_","type":"uint256"},{"indexed":true,"internalType":"address","name":"buyer_","type":"address"},{"indexed":false,"internalType":"uint256","name":"priceETH_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"priceDUTCH_","type":"uint256"}],"name":"ListingSettled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFee_","type":"uint256"}],"name":"MaxListingFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newAddress_","type":"address"}],"name":"OpsWalletUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"vaultBps_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"opsBps_","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"burnBps_","type":"uint256"}],"name":"SellerFeeOnSettledSplitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFeeBps_","type":"uint256"}],"name":"SellerFeeOnSettledUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFeeBps_","type":"uint256"}],"name":"SellerListingFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"listingIds_","type":"uint256[]"},{"internalType":"uint256[]","name":"maxPrices_","type":"uint256[]"}],"name":"batchSettle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"}],"name":"cancelListing","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"},{"internalType":"uint256","name":"maxPrice_","type":"uint256"},{"internalType":"uint256","name":"minPrice_","type":"uint256"}],"name":"createListing","outputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"},{"internalType":"uint256","name":"maxPrice_","type":"uint256"},{"internalType":"uint256","name":"minPrice_","type":"uint256"}],"name":"createListingWithBurn","outputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getAllowedCollections","outputs":[{"internalType":"address[]","name":"collections_","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllowedCollectionsCount","outputs":[{"internalType":"uint256","name":"count_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAuctionDuration","outputs":[{"internalType":"uint256","name":"duration_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBondingCurve","outputs":[{"internalType":"address","name":"bondingCurve_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCollectionWhitelistEnabled","outputs":[{"internalType":"bool","name":"enabled_","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"}],"name":"getCurrentPrice","outputs":[{"components":[{"internalType":"uint256","name":"priceETH","type":"uint256"},{"internalType":"uint256","name":"priceDUTCH","type":"uint256"}],"internalType":"struct DutchAuctionMarketplace.CurrentPrice","name":"price_","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDutchToken","outputs":[{"internalType":"address","name":"dutchToken_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDutchVaultAddress","outputs":[{"internalType":"address","name":"vaultAddress_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collection_","type":"address"}],"name":"getIsCollectionAllowed","outputs":[{"internalType":"bool","name":"allowed_","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"}],"name":"getListing","outputs":[{"components":[{"internalType":"address","name":"seller","type":"address"},{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"settled","type":"bool"},{"internalType":"bool","name":"cancelled","type":"bool"},{"internalType":"address","name":"proceedsRecipient","type":"address"}],"internalType":"struct DutchAuctionMarketplace.Listing","name":"listing_","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"nftContract_","type":"address"},{"internalType":"uint256","name":"tokenId_","type":"uint256"}],"name":"getListingByNFT","outputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"},{"components":[{"internalType":"address","name":"seller","type":"address"},{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"settled","type":"bool"},{"internalType":"bool","name":"cancelled","type":"bool"},{"internalType":"address","name":"proceedsRecipient","type":"address"}],"internalType":"struct DutchAuctionMarketplace.Listing","name":"listing_","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getListingFeeSplitConfig","outputs":[{"internalType":"uint256","name":"vaultBps_","type":"uint256"},{"internalType":"uint256","name":"opsBps_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"offset_","type":"uint256"},{"internalType":"uint256","name":"limit_","type":"uint256"}],"name":"getListingsPaginated","outputs":[{"components":[{"internalType":"address","name":"seller","type":"address"},{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"bool","name":"settled","type":"bool"},{"internalType":"bool","name":"cancelled","type":"bool"},{"internalType":"address","name":"proceedsRecipient","type":"address"}],"internalType":"struct DutchAuctionMarketplace.Listing[]","name":"listings_","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxListingFee","outputs":[{"internalType":"uint256","name":"fee_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOpsWallet","outputs":[{"internalType":"address","name":"wallet_","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSellerFeeOnSettled","outputs":[{"internalType":"uint256","name":"feeBps_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSellerFeeOnSettledSplitConfig","outputs":[{"internalType":"uint256","name":"vaultBps_","type":"uint256"},{"internalType":"uint256","name":"opsBps_","type":"uint256"},{"internalType":"uint256","name":"burnBps_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSellerListingFee","outputs":[{"internalType":"uint256","name":"feeBps_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalListings","outputs":[{"internalType":"uint256","name":"total_","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"duration_","type":"uint256"}],"name":"setAuctionDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address payable","name":"bondingCurve_","type":"address"}],"name":"setBondingCurve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collection_","type":"address"},{"internalType":"bool","name":"allowed_","type":"bool"}],"name":"setCollectionAllowed","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"enabled_","type":"bool"}],"name":"setCollectionWhitelistEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAddress_","type":"address"}],"name":"setDutchVaultAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"vaultBps_","type":"uint256"},{"internalType":"uint256","name":"opsBps_","type":"uint256"}],"name":"setListingFeeSplitConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"fee_","type":"uint256"}],"name":"setMaxListingFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAddress_","type":"address"}],"name":"setOpsWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feeBps_","type":"uint256"}],"name":"setSellerFeeOnSettled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"vaultBps_","type":"uint256"},{"internalType":"uint256","name":"opsBps_","type":"uint256"},{"internalType":"uint256","name":"burnBps_","type":"uint256"}],"name":"setSellerFeeOnSettledSplitConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feeBps_","type":"uint256"}],"name":"setSellerListingFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"},{"internalType":"uint256","name":"maxPriceDUTCH_","type":"uint256"}],"name":"settle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"listingId_","type":"uint256"},{"internalType":"uint256","name":"maxPriceETH_","type":"uint256"}],"name":"settleWithETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

60a0346101e457601f612b6938819003918201601f19168301916001600160401b038311848410176101e8578084926060946040528339810103126101e457610047816101fc565b906100606040610059602084016101fc565b92016101fc565b33156101d157600180546001600160a01b03199081169091555f805433928116831782556040519592916001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a360017f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00556001600160a01b03169182156101c2576001600160a01b03169081156101c2576001600160a01b03169182156101c25760805260018060a01b0319600254161760025560018060a01b0319600354161760035560ff19600e5416600e5560c8600655603260075567016345785d8a000060085561a8c06005555f601155611d4c6009556109c4600a555f600b555f600c55612710600d5561295890816102118239608051818181610b8501528181610e8d01528181611b10015281816122be01528181612532015281816125dc015281816126030152818161269101526126c20152f35b63e6c4247b60e01b5f5260045ffd5b631e4fbdf760e01b5f525f60045260245ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b03821682036101e45756fe608080604052600436101561001c575b50361561001a575f80fd5b005b5f905f3560e01c90816302d4e01814611db257508063070ddaf314611d0a578063107a274a14611cc857806311b3b6e414611c975780631cc9e78514611aac578063216b862b14611a4a578063305a67a8146119325780633a51f3831461186a5780633f4ba83a146117fc578063410da6ac146117d457806343ff1fb8146117895780635c975abb14611764578063704c41961461174757806370cdec3d1461135b578063715018a6146112f857806378cb51af146112db57806379ba5097146112565780638456cb59146111f557806384a5b37e146111935780638842bb5d14610f5a5780638da5cb5b14610f335780639a9c29f614610dde5780639b2caf6314610d3f578063a497e67414610cec578063a75b34db14610c7b578063ad483b3e14610c25578063ba8f63c914610bb4578063bc0bae5714610b70578063c0c133d114610b33578063c55d0f5614610ae2578063c694e3cb14610ac0578063d495be8c14610a6a578063d803096614610a46578063d8cf6aa7146109f3578063e05df03c146109d6578063e30c3978146109ae578063e69e8d9214610991578063e73f2ee2146107bf578063ea1ff6bd14610797578063f2fde38b1461072a578063f3e677e9146102ba578063fa35a09a14610251578063faac38ef146102285763fbf30c350361000f57346102255780600319360112610225576020601154604051908152f35b80fd5b50346102255780600319360112610225576004546040516001600160a01b039091168152602090f35b50346102255761026961026336611e86565b906120e4565b90604051918291602083016020845282518091526020604085019301915b818110610295575050500390f35b919350916020610140826102ac6001948851611de2565b019401910191849392610287565b6102c336611e9c565b9190926102ce61221a565b6102d66121fc565b6001600160a01b031690811561071b5783158015610713575b610704578284106107045760ff600e5416806106ed575b806106d8575b6106c9576040516331a9108f60e11b815260048101829052602081602481865afa908115610651575f916106aa575b50336001600160a01b039091160361069b5760405163e985e9c560e01b8152336004820152306024820152602081604481865afa908115610651575f9161065c575b5015806105e4575b6105d557815f52601460205260405f20815f5260205260405f20546012548110610567575b50346105585760209360125493600554856104a46040516103ca81611eca565b338152898101888152604080830189815260608401898152608085018981524260a0870190815260c08701998a525f60e08801818152610100890182815261dead6101208b019081529b8352601360205296909120975188546001600160a01b039182166001600160a01b0319918216178a55975160018a01805491909216981697909717909655925160028701559051600386015551600485015551600584015594516006830155516007909101805460ff191691151560ff16919091178155925b51835461ff00191690151560081b61ff0016178355565b51815462010000600160b01b03191660109190911b62010000600160b01b03161790556012546104d390612252565b6012556104e1601154612252565b601155835f526014865260405f20835f5286528460405f2055600554916040519384528684015260408301524260608301526080820152827f909c9d2c265582c1497417c66016d06c829b8138a8f8f6caeb35246b7168a50960a03393a460015f8051602061290383398151915255604051908152f35b630603415960e11b5f5260045ffd5b5f90815260136020526040902060018101546001600160a01b03168314806105c8575b156103aa576007015460ff81161590816105b9575b506105aa57846103aa565b631a53300b60e21b5f5260045ffd5b60ff915060081c16158561059f565b508160028201541461058a565b63c19f17a960e01b5f5260045ffd5b5060405163020604bf60e21b815260048101829052602081602481865afa908115610651575f91610622575b506001600160a01b0316301415610385565b610644915060203d60201161064a575b61063c8183611ee7565b81019061200c565b85610610565b503d610632565b6040513d5f823e3d90fd5b90506020813d602011610693575b8161067760209383611ee7565b8101031261068f5751801515810361068f578561037d565b5f80fd5b3d915061066a565b631022318760e21b5f5260045ffd5b6106c3915060203d60201161064a5761063c8183611ee7565b8561033b565b632f579acf60e11b5f5260045ffd5b506002546001600160a01b031633141561030c565b50815f52600f60205260ff60405f20541615610306565b63162908e360e11b5f5260045ffd5b5082156102ef565b63e6c4247b60e01b5f5260045ffd5b3461068f57602036600319011261068f57610743611dcc565b61074b6121e9565b60018060a01b0316806001600160601b0360a01b600154161760015560018060a01b035f54167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b3461068f575f36600319011261068f576003546040516001600160a01b039091168152602090f35b6107c836611e86565b906107d16121fc565b6107d961221a565b6004546001600160a01b0316156109825760125481101561097357805f52601360205260405f20916007830180549360ff85166109735760ff8560081c1661097357546002546001600160a01b0391821691160361097357610839612050565b9361084e610846856126f6565b8087526127e3565b6020860152845183811161095d575060019060ff1916179055602060018060a01b03600454169301906060825160446040518097819363ea37821360e01b8352600483015230602483015234905af1938415610651575f94610926575b5080841161090f576108cb846108c5858551833392612291565b34612043565b806108e4575b60015f8051602061290383398151915255005b5f80808093335af16108f4612079565b501561090057806108d1565b633fdf366b60e11b5f5260045ffd5b83906371c4efed60e01b5f5260045260245260445ffd5b9093506060813d606011610955575b8161094260609383611ee7565b8101031261068f576020015192846108ab565b3d9150610935565b836371c4efed60e01b5f5260045260245260445ffd5b631ab4dc1760e31b5f5260045ffd5b632ac8248760e11b5f5260045ffd5b3461068f575f36600319011261068f576020601054604051908152f35b3461068f575f36600319011261068f576001546040516001600160a01b039091168152602090f35b3461068f575f36600319011261068f576020600854604051908152f35b3461068f57602036600319011261068f57600435610a0f6121e9565b8015610704576020817f91e1d497b3d5069f06770109642b05e1c109f1d97f3d4fb2989ce433748ef46892600855604051908152a1005b3461068f575f36600319011261068f576040600954600a5482519182526020820152f35b3461068f57602036600319011261068f57600435610a866121e9565b6127108111610704576020817f7c2ea7d3e2ca8ee856d6d72ba0251fe8b80608875ceb0d5fa1b285fc7282dbb092600655604051908152a1005b3461068f575f36600319011261068f57602060ff600e54166040519015158152f35b3461068f57602036600319011261068f57610afb612050565b506040610b06612050565b610b1c610b146004356126f6565b8083526127e3565b602082015260208251918051835201516020820152f35b3461068f57602036600319011261068f576001600160a01b03610b54611dcc565b165f52600f602052602060ff60405f2054166040519015158152f35b3461068f575f36600319011261068f576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b3461068f57602036600319011261068f57610bcd611dcc565b610bd56121e9565b6001600160a01b0316801561071b576020817f272db2050c4be65ac2a61ffece982efc98fe5c0dde59bc9db9170fd81424f3ee926001600160601b0360a01b6003541617600355604051908152a1005b3461068f57602036600319011261068f57600435610c416121e9565b6127108111610704576020817f22cb7f3e16441cd2e99af60ce66b46fcf44ea0565d650c8e66b1cbd28705d4e092600755604051908152a1005b3461068f57602036600319011261068f57610c94611dcc565b610c9c6121e9565b6001600160a01b0316801561071b576020817fe84e5e1ba193f0917cc388b140f3ed63cdc890eec46cf537dbdcfce4824912f1926001600160601b0360a01b6002541617600255604051908152a1005b3461068f57602036600319011261068f57600435610d086121e9565b8015610704576020817faab6389d8f1c16ba1deb6e9831f5c5442cf4fcf99bf5bfa867460be408a9111892600555604051908152a1005b3461068f57606036600319011261068f57604435602435600435610d616121e9565b612710610d7784610d728585611fff565b611fff565b03610dcf57610dca7f9e7429e4e9b470c4ed2517263fe69c1b094d80c7a608240c295e0ea1a9d327919382600b5583600c5580600d55604051938493846040919493926060820195825260208201520152565b0390a1005b6382cb17ef60e01b5f5260045ffd5b3461068f57610dec36611e86565b610df46121fc565b610dfc61221a565b6004546001600160a01b0316156109825760125482101561097357815f526013602052600760405f20019182549260ff84166109735760ff8460081c1661097357610e45612050565b93610e52610846846126f6565b6020860181815294808211610f1e57505060ff1916600117905581516040516323b872dd60e01b5f90815233600452306024526044929092527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316949160209060648180895af19060015f5114821615610efd575b6040525f60605215610eea576108d193505133925191612291565b83635274afe760e01b5f5260045260245ffd5b906001811516610f1557853b15153d15161690610ecf565b503d5f823e3d90fd5b6371c4efed60e01b5f5260045260245260445ffd5b3461068f575f36600319011261068f575f546040516001600160a01b039091168152602090f35b3461068f57604036600319011261068f57610f73611dcc565b6024359081159081159182840361068f57610f8c6121e9565b6001600160a01b03821693841561071b575f858152600f60205260409020805482151560ff90811660ff1983161790925516908061118b575b1561105957505060105491680100000000000000008310156110455761103c6020926110188560017f2b4d792442b95b7770811f178d187380f6e4db1d3ec428a2e78d6c56acfb2888970160105561202b565b81546001600160a01b0393841660039290921b91821b9390911b1916919091179055565b604051908152a2005b634e487b7160e01b5f52604160045260245ffd5b90915081611183575b50611091575b60207f2b4d792442b95b7770811f178d187380f6e4db1d3ec428a2e78d6c56acfb28889161103c565b601054905f5b8281106110a7575b509050611068565b836110b18261202b565b905460039190911b1c6001600160a01b0316146110d057600101611097565b90915f1981019190821161116f576110186110ed6111059361202b565b905460039190911b1c6001600160a01b03169161202b565b601054801561115b577f2b4d792442b95b7770811f178d187380f6e4db1d3ec428a2e78d6c56acfb2888916020915f190161113f8161202b565b81549060018060a01b039060031b1b191690556010559061109f565b634e487b7160e01b5f52603160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b905083611062565b508015610fc5565b3461068f57602036600319011261068f5760043580151580910361068f5760207fe83cdc9ed633b0703d413b4e4b1ddc94f2a89bd8d1ba736f48a53f000aa10408916111dd6121e9565b60ff19600e541660ff821617600e55604051908152a1005b3461068f575f36600319011261068f5761120d6121e9565b6112156121fc565b6001805460ff60a01b1916600160a01b1790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602090a1005b3461068f575f36600319011261068f57600154336001600160a01b03909116036112c857600180546001600160a01b03199081169091555f805433928116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b63118cdaa760e01b5f523360045260245ffd5b3461068f575f36600319011261068f576020600554604051908152f35b3461068f575f36600319011261068f576113106121e9565b600180546001600160a01b03199081169091555f80549182168155906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b61136436611e9c565b91909261136f61221a565b6113776121fc565b6001600160a01b031690811561071b578315801561173f575b610704578284106107045760ff600e541680611728575b80611713575b6106c9576040516331a9108f60e11b815260048101829052602081602481865afa908115610651575f916116f4575b50336001600160a01b039091160361069b5760405163e985e9c560e01b8152336004820152306024820152602081604481865afa908115610651575f916116b9575b50158061165c575b6105d557815f52601460205260405f20815f5260205260405f205460125481106115fd575b5061dead331415806115e8575b156115db5761271061146c60075485612260565b04600854908181115f146115d457505b803403610704575f8080806114a161271061149960095488612260565b048096612043565b6002549095906001600160a01b03165af16114ba612079565b50156115c5576003545f918291829182916001600160a01b03165af16114de612079565b50156115c5576020935b60125493600554856104a46040516114ff81611eca565b338082528a820189815260408084018a8152606085018a8152608086018a81524260a0880190815260c088019a8b525f60e089018181526101008a018281526101208b01998a529b82526013602052959020975188546001600160a01b039182166001600160a01b0319918216178a55965160018a0180549190921697169690961790955591516002870155516003860155516004850155905160058401559451600683015593516007909101805460ff191691151560ff16919091178155929161048d565b63b12d13eb60e01b5f5260045ffd5b905061147c565b34610558576020936114e8565b506002546001600160a01b0316331415611458565b5f90815260136020526040902060018101546001600160a01b031683148061164f575b1561144b576007015460ff8116159081611640575b506105aa578461144b565b60ff915060081c161585611635565b5081600282015414611620565b5060405163020604bf60e21b815260048101829052602081602481865afa908115610651575f9161169a575b506001600160a01b0316301415611426565b6116b3915060203d60201161064a5761063c8183611ee7565b85611688565b90506020813d6020116116ec575b816116d460209383611ee7565b8101031261068f5751801515810361068f578561141e565b3d91506116c7565b61170d915060203d60201161064a5761063c8183611ee7565b856113dc565b506002546001600160a01b03163314156113ad565b50815f52600f60205260ff60405f205416156113a7565b508215611390565b3461068f575f36600319011261068f576020600754604051908152f35b3461068f575f36600319011261068f57602060ff60015460a01c166040519015158152f35b3461068f57602036600319011261068f576004356001600160a01b0381169081900361068f576117b76121e9565b801561071b576001600160601b0360a01b60045416176004555f80f35b3461068f575f36600319011261068f576002546040516001600160a01b039091168152602090f35b3461068f575f36600319011261068f576118146121e9565b60015460ff8160a01c161561185b5760ff60a01b19166001556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa90602090a1005b638dfc202b60e01b5f5260045ffd5b3461068f575f36600319011261068f576040518060206010549283815201809260105f527f1b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672905f5b81811061191357505050816118c8910382611ee7565b604051918291602083019060208452518091526040830191905f5b8181106118f1575050500390f35b82516001600160a01b03168452859450602093840193909201916001016118e3565b82546001600160a01b03168452602090930192600192830192016118b2565b3461068f57602036600319011261068f5760043561194e6121fc565b60125481101561097357805f52601360205260405f20600781019081549060ff82166109735760ff8260081c166109735760018060a01b0360018201541690602060028201546024604051809581936331a9108f60e11b835260048301525afa918215610651575f92611a29575b50546001600160a01b03163314159081611a15575b50611a075761ff0019166101001790557f5d7a33421ffa4bc07eb8929c5ace6393d3aa5ec3775e4e2f442527876b7dbe885f80a2005b6282b42960e81b5f5260045ffd5b6001600160a01b03163314159050846119d1565b611a4391925060203d60201161064a5761063c8183611ee7565b90856119bc565b3461068f57611a5836611e86565b611a606121e9565b612710611a6d8284611fff565b03610dcf57816040917fcfbab40d6bb24e034057c02d55147ce0cdcf88013d9c55a2b6dab24fc50be48b9360095580600a5582519182526020820152a1005b3461068f57604036600319011261068f5760043567ffffffffffffffff811161068f57611add903690600401611e55565b9060243567ffffffffffffffff811161068f57611afe903690600401611e55565b92838103611c885760328111611c79577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031693925f5b828110611b4557005b611b50818484611fdb565b3590611b5d818787611fdb565b35611b666121fc565b611b6e61221a565b6004546001600160a01b0316156109825760125483101561097357825f526013602052600760405f20019081549160ff83166109735760ff8360081c1661097357611bb7612050565b92611bcc611bc4876126f6565b8086526127e3565b6020850181815293808211610f1e57505060019060ff19161790558860205f60648180865195604051966323b872dd60e01b835233600452306024526044525af19060015f5114821615611c61575b6040525f60605215611c4e57600193611c38925133925191612291565b815f805160206129038339815191525501611b3c565b88635274afe760e01b5f5260045260245ffd5b906001811516610f15578a3b15153d15161690611c1b565b631e80759560e21b5f5260045ffd5b63512509d360e11b5f5260045ffd5b3461068f575f36600319011261068f57600b54600c54600d5460408051938452602084019290925290820152606090f35b3461068f57602036600319011261068f57611ce1611f09565b506004355f526013602052610140611cfb60405f20611f53565b611d086040518092611de2565bf35b3461068f57604036600319011261068f57611d23611dcc565b60243590611d2f611f09565b5060018060a01b031690815f52601460205260405f20815f5260205260405f20549060125482101561097357815f52601360205260405f209260018060a01b036001850154161490811591611da3575b5061097357611d08611d9361016093611f53565b6040519283526020830190611de2565b90506002830154141583611d7f565b3461068f575f36600319011261068f576020906006548152f35b600435906001600160a01b038216820361068f57565b80516001600160a01b03908116835260208083015182169084015260408083015190840152606080830151908401526080808301519084015260a0808301519084015260c0808301519084015260e080830151151590840152610100808301511515908401526101209182015116910152565b9181601f8401121561068f5782359167ffffffffffffffff831161068f576020808501948460051b01011161068f57565b604090600319011261068f576004359060243590565b608090600319011261068f576004356001600160a01b038116810361068f5790602435906044359060643590565b610140810190811067ffffffffffffffff82111761104557604052565b90601f8019910116810190811067ffffffffffffffff82111761104557604052565b60405190611f1682611eca565b5f610120838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e0820152826101008201520152565b90604051611f6081611eca565b82546001600160a01b039081168252600184015481166020830152600284015460408301526003840154606083015260048401546080830152600584015460a0830152600684015460c083015260079093015460ff808216151560e0840152600882901c16151561010083015260101c909216610120830152565b9190811015611feb5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b9190820180921161116f57565b9081602091031261068f57516001600160a01b038116810361068f5790565b601054811015611feb5760105f5260205f2001905f90565b9190820391821161116f57565b604051906040820182811067ffffffffffffffff821117611045576040525f6020838281520152565b3d156120b3573d9067ffffffffffffffff821161104557604051916120a8601f8201601f191660200184611ee7565b82523d5f602084013e565b606090565b67ffffffffffffffff81116110455760051b60200190565b8051821015611feb5760209160051b010190565b919060125490818410156121ad5761211091612101859283611fff565b908082116121a5575b50612043565b612119816120b8565b6121266040519182611ee7565b818152601f19612135836120b8565b015f5b81811061218e57505080935f5b8381106121525750505050565b8061215f60019284611fff565b5f52601360205261217260405f20611f53565b61217c82866120d0565b5261218781856120d0565b5001612145565b602090612199611f09565b82828601015201612138565b90505f61210a565b50909150506040516121c0602082611ee7565b5f81525f805b8181106121d257505090565b6020906121dd611f09565b828286010152016121c6565b5f546001600160a01b031633036112c857565b60ff60015460a01c1661220b57565b63d93c066560e01b5f5260045ffd5b60025f8051602061290383398151915254146122435760025f8051602061290383398151915255565b633ee5aeb560e01b5f5260045ffd5b5f19811461116f5760010190565b8181029291811591840414171561116f57565b811561227d570490565b634e487b7160e01b5f52601260045260245ffd5b5f81815260136020526040812060078101805493959360101c6001600160a01b031661dead036124ff57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316803b156124fb57828091602460405180948193634964091960e01b83528960048401525af180156124b3579083916124e6575b50505b6001810180548254600284018054999093926001600160a01b039081169216823b156124e25790606487928360405195869485936323b872dd60e01b8552600485015260018060a01b03169e8f602485015260448401525af180156124d7579085916124be575b50505490546040516331a9108f60e11b8152600481019190915290602090829060249082906001600160a01b03165afa9081156124b3579087918491612494575b506001600160a01b03160361248557546002546001600160a01b039081169116811461241f575b50507fe3a7cfa26bda4fc90b8dce659eb0b58decd9d8e7161394f840f6a6db82cf6a5e9160409182519182526020820152a3565b803b15612481578180916084604051809481936332c132f760e21b83528a60048401528b60248401528960448401528860648401525af1801561247657156123eb5761246c828092611ee7565b61022557806123eb565b6040513d84823e3d90fd5b5080fd5b6308c0b8b360e31b8252600482fd5b6124ad915060203d60201161064a5761063c8183611ee7565b5f6123c4565b6040513d85823e3d90fd5b816124c891611ee7565b6124d357835f612383565b8380fd5b6040513d87823e3d90fd5b8680fd5b816124f091611ee7565b61248157815f612319565b8280fd5b815460025485916001600160a01b039182169116810361255c57509054612557919060101c6001600160a01b03908116907f00000000000000000000000000000000000000000000000000000000000000001661288a565b61231c565b905061271061256d60065487612260565b049061271061257e600b5484612260565b049061271061258f600c5485612260565b04906125a48261259f8587612043565b612043565b92806126bc575b50508061267b575b5080612601575b50906125c96125579286612043565b905460101c6001600160a01b03908116907f00000000000000000000000000000000000000000000000000000000000000001661288a565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690813b1561068f575f91602483926040519485938492634964091960e01b845260048401525af1801561065157156125ba57612557929194505f61266e91611ee7565b6125c95f949192506125ba565b6003546126b691906001600160a01b03908116907f00000000000000000000000000000000000000000000000000000000000000001661288a565b5f6125b3565b6126ef917f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661288a565b5f806125ab565b601254811015610973575f52601360205260405f20600781015460ff8160081c166109735760ff1661097357612730600582015442612043565b9081156127da57600681015491828110156127d057612759600460038401549301548093612043565b92670de0b6b3a76400008202918204670de0b6b3a76400000361116f5761277f91612273565b670de0b6b3a76400000391670de0b6b3a7640000831161116f576127c6670de0b6b3a764000091826127bf816127b8886127cd99612260565b0480612260565b0490612260565b0490611fff565b90565b5060049150015490565b60039150015490565b8015612885576004546001600160a01b0316801561287f576020600491604051928380926375c8e9bf60e11b82525afa908115610651575f9161284d575b50801561070457670de0b6b3a76400008202918204670de0b6b3a76400000361116f576127cd91612273565b90506020813d602011612877575b8161286860209383611ee7565b8101031261068f57515f612821565b3d915061285b565b50505f90565b505f90565b916040519163a9059cbb60e01b5f5260018060a01b031660045260245260205f60448180865af19060015f51148216156128ea575b604052156128ca5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b906001811516610f1557823b15153d151616906128bf56fe9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a264697066735822122016a018fb24a68bce61984393103ecf5fb85058ca3e9a180ccc6505402f14114364736f6c634300081a00330000000000000000000000007b2f34f323148262e145f60c3c715822a5430f0800000000000000000000000035ec3ebaaa5e471ec2b05ebc28ab3c8734c4a4b7000000000000000000000000da2cab7348bb63f4e8cf82a877ae280f0e48ed60

Deployed Bytecode

0x608080604052600436101561001c575b50361561001a575f80fd5b005b5f905f3560e01c90816302d4e01814611db257508063070ddaf314611d0a578063107a274a14611cc857806311b3b6e414611c975780631cc9e78514611aac578063216b862b14611a4a578063305a67a8146119325780633a51f3831461186a5780633f4ba83a146117fc578063410da6ac146117d457806343ff1fb8146117895780635c975abb14611764578063704c41961461174757806370cdec3d1461135b578063715018a6146112f857806378cb51af146112db57806379ba5097146112565780638456cb59146111f557806384a5b37e146111935780638842bb5d14610f5a5780638da5cb5b14610f335780639a9c29f614610dde5780639b2caf6314610d3f578063a497e67414610cec578063a75b34db14610c7b578063ad483b3e14610c25578063ba8f63c914610bb4578063bc0bae5714610b70578063c0c133d114610b33578063c55d0f5614610ae2578063c694e3cb14610ac0578063d495be8c14610a6a578063d803096614610a46578063d8cf6aa7146109f3578063e05df03c146109d6578063e30c3978146109ae578063e69e8d9214610991578063e73f2ee2146107bf578063ea1ff6bd14610797578063f2fde38b1461072a578063f3e677e9146102ba578063fa35a09a14610251578063faac38ef146102285763fbf30c350361000f57346102255780600319360112610225576020601154604051908152f35b80fd5b50346102255780600319360112610225576004546040516001600160a01b039091168152602090f35b50346102255761026961026336611e86565b906120e4565b90604051918291602083016020845282518091526020604085019301915b818110610295575050500390f35b919350916020610140826102ac6001948851611de2565b019401910191849392610287565b6102c336611e9c565b9190926102ce61221a565b6102d66121fc565b6001600160a01b031690811561071b5783158015610713575b610704578284106107045760ff600e5416806106ed575b806106d8575b6106c9576040516331a9108f60e11b815260048101829052602081602481865afa908115610651575f916106aa575b50336001600160a01b039091160361069b5760405163e985e9c560e01b8152336004820152306024820152602081604481865afa908115610651575f9161065c575b5015806105e4575b6105d557815f52601460205260405f20815f5260205260405f20546012548110610567575b50346105585760209360125493600554856104a46040516103ca81611eca565b338152898101888152604080830189815260608401898152608085018981524260a0870190815260c08701998a525f60e08801818152610100890182815261dead6101208b019081529b8352601360205296909120975188546001600160a01b039182166001600160a01b0319918216178a55975160018a01805491909216981697909717909655925160028701559051600386015551600485015551600584015594516006830155516007909101805460ff191691151560ff16919091178155925b51835461ff00191690151560081b61ff0016178355565b51815462010000600160b01b03191660109190911b62010000600160b01b03161790556012546104d390612252565b6012556104e1601154612252565b601155835f526014865260405f20835f5286528460405f2055600554916040519384528684015260408301524260608301526080820152827f909c9d2c265582c1497417c66016d06c829b8138a8f8f6caeb35246b7168a50960a03393a460015f8051602061290383398151915255604051908152f35b630603415960e11b5f5260045ffd5b5f90815260136020526040902060018101546001600160a01b03168314806105c8575b156103aa576007015460ff81161590816105b9575b506105aa57846103aa565b631a53300b60e21b5f5260045ffd5b60ff915060081c16158561059f565b508160028201541461058a565b63c19f17a960e01b5f5260045ffd5b5060405163020604bf60e21b815260048101829052602081602481865afa908115610651575f91610622575b506001600160a01b0316301415610385565b610644915060203d60201161064a575b61063c8183611ee7565b81019061200c565b85610610565b503d610632565b6040513d5f823e3d90fd5b90506020813d602011610693575b8161067760209383611ee7565b8101031261068f5751801515810361068f578561037d565b5f80fd5b3d915061066a565b631022318760e21b5f5260045ffd5b6106c3915060203d60201161064a5761063c8183611ee7565b8561033b565b632f579acf60e11b5f5260045ffd5b506002546001600160a01b031633141561030c565b50815f52600f60205260ff60405f20541615610306565b63162908e360e11b5f5260045ffd5b5082156102ef565b63e6c4247b60e01b5f5260045ffd5b3461068f57602036600319011261068f57610743611dcc565b61074b6121e9565b60018060a01b0316806001600160601b0360a01b600154161760015560018060a01b035f54167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e227005f80a3005b3461068f575f36600319011261068f576003546040516001600160a01b039091168152602090f35b6107c836611e86565b906107d16121fc565b6107d961221a565b6004546001600160a01b0316156109825760125481101561097357805f52601360205260405f20916007830180549360ff85166109735760ff8560081c1661097357546002546001600160a01b0391821691160361097357610839612050565b9361084e610846856126f6565b8087526127e3565b6020860152845183811161095d575060019060ff1916179055602060018060a01b03600454169301906060825160446040518097819363ea37821360e01b8352600483015230602483015234905af1938415610651575f94610926575b5080841161090f576108cb846108c5858551833392612291565b34612043565b806108e4575b60015f8051602061290383398151915255005b5f80808093335af16108f4612079565b501561090057806108d1565b633fdf366b60e11b5f5260045ffd5b83906371c4efed60e01b5f5260045260245260445ffd5b9093506060813d606011610955575b8161094260609383611ee7565b8101031261068f576020015192846108ab565b3d9150610935565b836371c4efed60e01b5f5260045260245260445ffd5b631ab4dc1760e31b5f5260045ffd5b632ac8248760e11b5f5260045ffd5b3461068f575f36600319011261068f576020601054604051908152f35b3461068f575f36600319011261068f576001546040516001600160a01b039091168152602090f35b3461068f575f36600319011261068f576020600854604051908152f35b3461068f57602036600319011261068f57600435610a0f6121e9565b8015610704576020817f91e1d497b3d5069f06770109642b05e1c109f1d97f3d4fb2989ce433748ef46892600855604051908152a1005b3461068f575f36600319011261068f576040600954600a5482519182526020820152f35b3461068f57602036600319011261068f57600435610a866121e9565b6127108111610704576020817f7c2ea7d3e2ca8ee856d6d72ba0251fe8b80608875ceb0d5fa1b285fc7282dbb092600655604051908152a1005b3461068f575f36600319011261068f57602060ff600e54166040519015158152f35b3461068f57602036600319011261068f57610afb612050565b506040610b06612050565b610b1c610b146004356126f6565b8083526127e3565b602082015260208251918051835201516020820152f35b3461068f57602036600319011261068f576001600160a01b03610b54611dcc565b165f52600f602052602060ff60405f2054166040519015158152f35b3461068f575f36600319011261068f576040517f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f086001600160a01b03168152602090f35b3461068f57602036600319011261068f57610bcd611dcc565b610bd56121e9565b6001600160a01b0316801561071b576020817f272db2050c4be65ac2a61ffece982efc98fe5c0dde59bc9db9170fd81424f3ee926001600160601b0360a01b6003541617600355604051908152a1005b3461068f57602036600319011261068f57600435610c416121e9565b6127108111610704576020817f22cb7f3e16441cd2e99af60ce66b46fcf44ea0565d650c8e66b1cbd28705d4e092600755604051908152a1005b3461068f57602036600319011261068f57610c94611dcc565b610c9c6121e9565b6001600160a01b0316801561071b576020817fe84e5e1ba193f0917cc388b140f3ed63cdc890eec46cf537dbdcfce4824912f1926001600160601b0360a01b6002541617600255604051908152a1005b3461068f57602036600319011261068f57600435610d086121e9565b8015610704576020817faab6389d8f1c16ba1deb6e9831f5c5442cf4fcf99bf5bfa867460be408a9111892600555604051908152a1005b3461068f57606036600319011261068f57604435602435600435610d616121e9565b612710610d7784610d728585611fff565b611fff565b03610dcf57610dca7f9e7429e4e9b470c4ed2517263fe69c1b094d80c7a608240c295e0ea1a9d327919382600b5583600c5580600d55604051938493846040919493926060820195825260208201520152565b0390a1005b6382cb17ef60e01b5f5260045ffd5b3461068f57610dec36611e86565b610df46121fc565b610dfc61221a565b6004546001600160a01b0316156109825760125482101561097357815f526013602052600760405f20019182549260ff84166109735760ff8460081c1661097357610e45612050565b93610e52610846846126f6565b6020860181815294808211610f1e57505060ff1916600117905581516040516323b872dd60e01b5f90815233600452306024526044929092527f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f086001600160a01b0316949160209060648180895af19060015f5114821615610efd575b6040525f60605215610eea576108d193505133925191612291565b83635274afe760e01b5f5260045260245ffd5b906001811516610f1557853b15153d15161690610ecf565b503d5f823e3d90fd5b6371c4efed60e01b5f5260045260245260445ffd5b3461068f575f36600319011261068f575f546040516001600160a01b039091168152602090f35b3461068f57604036600319011261068f57610f73611dcc565b6024359081159081159182840361068f57610f8c6121e9565b6001600160a01b03821693841561071b575f858152600f60205260409020805482151560ff90811660ff1983161790925516908061118b575b1561105957505060105491680100000000000000008310156110455761103c6020926110188560017f2b4d792442b95b7770811f178d187380f6e4db1d3ec428a2e78d6c56acfb2888970160105561202b565b81546001600160a01b0393841660039290921b91821b9390911b1916919091179055565b604051908152a2005b634e487b7160e01b5f52604160045260245ffd5b90915081611183575b50611091575b60207f2b4d792442b95b7770811f178d187380f6e4db1d3ec428a2e78d6c56acfb28889161103c565b601054905f5b8281106110a7575b509050611068565b836110b18261202b565b905460039190911b1c6001600160a01b0316146110d057600101611097565b90915f1981019190821161116f576110186110ed6111059361202b565b905460039190911b1c6001600160a01b03169161202b565b601054801561115b577f2b4d792442b95b7770811f178d187380f6e4db1d3ec428a2e78d6c56acfb2888916020915f190161113f8161202b565b81549060018060a01b039060031b1b191690556010559061109f565b634e487b7160e01b5f52603160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b905083611062565b508015610fc5565b3461068f57602036600319011261068f5760043580151580910361068f5760207fe83cdc9ed633b0703d413b4e4b1ddc94f2a89bd8d1ba736f48a53f000aa10408916111dd6121e9565b60ff19600e541660ff821617600e55604051908152a1005b3461068f575f36600319011261068f5761120d6121e9565b6112156121fc565b6001805460ff60a01b1916600160a01b1790556040513381527f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a25890602090a1005b3461068f575f36600319011261068f57600154336001600160a01b03909116036112c857600180546001600160a01b03199081169091555f805433928116831782556001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09080a3005b63118cdaa760e01b5f523360045260245ffd5b3461068f575f36600319011261068f576020600554604051908152f35b3461068f575f36600319011261068f576113106121e9565b600180546001600160a01b03199081169091555f80549182168155906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b61136436611e9c565b91909261136f61221a565b6113776121fc565b6001600160a01b031690811561071b578315801561173f575b610704578284106107045760ff600e541680611728575b80611713575b6106c9576040516331a9108f60e11b815260048101829052602081602481865afa908115610651575f916116f4575b50336001600160a01b039091160361069b5760405163e985e9c560e01b8152336004820152306024820152602081604481865afa908115610651575f916116b9575b50158061165c575b6105d557815f52601460205260405f20815f5260205260405f205460125481106115fd575b5061dead331415806115e8575b156115db5761271061146c60075485612260565b04600854908181115f146115d457505b803403610704575f8080806114a161271061149960095488612260565b048096612043565b6002549095906001600160a01b03165af16114ba612079565b50156115c5576003545f918291829182916001600160a01b03165af16114de612079565b50156115c5576020935b60125493600554856104a46040516114ff81611eca565b338082528a820189815260408084018a8152606085018a8152608086018a81524260a0880190815260c088019a8b525f60e089018181526101008a018281526101208b01998a529b82526013602052959020975188546001600160a01b039182166001600160a01b0319918216178a55965160018a0180549190921697169690961790955591516002870155516003860155516004850155905160058401559451600683015593516007909101805460ff191691151560ff16919091178155929161048d565b63b12d13eb60e01b5f5260045ffd5b905061147c565b34610558576020936114e8565b506002546001600160a01b0316331415611458565b5f90815260136020526040902060018101546001600160a01b031683148061164f575b1561144b576007015460ff8116159081611640575b506105aa578461144b565b60ff915060081c161585611635565b5081600282015414611620565b5060405163020604bf60e21b815260048101829052602081602481865afa908115610651575f9161169a575b506001600160a01b0316301415611426565b6116b3915060203d60201161064a5761063c8183611ee7565b85611688565b90506020813d6020116116ec575b816116d460209383611ee7565b8101031261068f5751801515810361068f578561141e565b3d91506116c7565b61170d915060203d60201161064a5761063c8183611ee7565b856113dc565b506002546001600160a01b03163314156113ad565b50815f52600f60205260ff60405f205416156113a7565b508215611390565b3461068f575f36600319011261068f576020600754604051908152f35b3461068f575f36600319011261068f57602060ff60015460a01c166040519015158152f35b3461068f57602036600319011261068f576004356001600160a01b0381169081900361068f576117b76121e9565b801561071b576001600160601b0360a01b60045416176004555f80f35b3461068f575f36600319011261068f576002546040516001600160a01b039091168152602090f35b3461068f575f36600319011261068f576118146121e9565b60015460ff8160a01c161561185b5760ff60a01b19166001556040513381527f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa90602090a1005b638dfc202b60e01b5f5260045ffd5b3461068f575f36600319011261068f576040518060206010549283815201809260105f527f1b6847dc741a1b0cd08d278845f9d819d87b734759afb55fe2de5cb82a9ae672905f5b81811061191357505050816118c8910382611ee7565b604051918291602083019060208452518091526040830191905f5b8181106118f1575050500390f35b82516001600160a01b03168452859450602093840193909201916001016118e3565b82546001600160a01b03168452602090930192600192830192016118b2565b3461068f57602036600319011261068f5760043561194e6121fc565b60125481101561097357805f52601360205260405f20600781019081549060ff82166109735760ff8260081c166109735760018060a01b0360018201541690602060028201546024604051809581936331a9108f60e11b835260048301525afa918215610651575f92611a29575b50546001600160a01b03163314159081611a15575b50611a075761ff0019166101001790557f5d7a33421ffa4bc07eb8929c5ace6393d3aa5ec3775e4e2f442527876b7dbe885f80a2005b6282b42960e81b5f5260045ffd5b6001600160a01b03163314159050846119d1565b611a4391925060203d60201161064a5761063c8183611ee7565b90856119bc565b3461068f57611a5836611e86565b611a606121e9565b612710611a6d8284611fff565b03610dcf57816040917fcfbab40d6bb24e034057c02d55147ce0cdcf88013d9c55a2b6dab24fc50be48b9360095580600a5582519182526020820152a1005b3461068f57604036600319011261068f5760043567ffffffffffffffff811161068f57611add903690600401611e55565b9060243567ffffffffffffffff811161068f57611afe903690600401611e55565b92838103611c885760328111611c79577f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f086001600160a01b031693925f5b828110611b4557005b611b50818484611fdb565b3590611b5d818787611fdb565b35611b666121fc565b611b6e61221a565b6004546001600160a01b0316156109825760125483101561097357825f526013602052600760405f20019081549160ff83166109735760ff8360081c1661097357611bb7612050565b92611bcc611bc4876126f6565b8086526127e3565b6020850181815293808211610f1e57505060019060ff19161790558860205f60648180865195604051966323b872dd60e01b835233600452306024526044525af19060015f5114821615611c61575b6040525f60605215611c4e57600193611c38925133925191612291565b815f805160206129038339815191525501611b3c565b88635274afe760e01b5f5260045260245ffd5b906001811516610f15578a3b15153d15161690611c1b565b631e80759560e21b5f5260045ffd5b63512509d360e11b5f5260045ffd5b3461068f575f36600319011261068f57600b54600c54600d5460408051938452602084019290925290820152606090f35b3461068f57602036600319011261068f57611ce1611f09565b506004355f526013602052610140611cfb60405f20611f53565b611d086040518092611de2565bf35b3461068f57604036600319011261068f57611d23611dcc565b60243590611d2f611f09565b5060018060a01b031690815f52601460205260405f20815f5260205260405f20549060125482101561097357815f52601360205260405f209260018060a01b036001850154161490811591611da3575b5061097357611d08611d9361016093611f53565b6040519283526020830190611de2565b90506002830154141583611d7f565b3461068f575f36600319011261068f576020906006548152f35b600435906001600160a01b038216820361068f57565b80516001600160a01b03908116835260208083015182169084015260408083015190840152606080830151908401526080808301519084015260a0808301519084015260c0808301519084015260e080830151151590840152610100808301511515908401526101209182015116910152565b9181601f8401121561068f5782359167ffffffffffffffff831161068f576020808501948460051b01011161068f57565b604090600319011261068f576004359060243590565b608090600319011261068f576004356001600160a01b038116810361068f5790602435906044359060643590565b610140810190811067ffffffffffffffff82111761104557604052565b90601f8019910116810190811067ffffffffffffffff82111761104557604052565b60405190611f1682611eca565b5f610120838281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e0820152826101008201520152565b90604051611f6081611eca565b82546001600160a01b039081168252600184015481166020830152600284015460408301526003840154606083015260048401546080830152600584015460a0830152600684015460c083015260079093015460ff808216151560e0840152600882901c16151561010083015260101c909216610120830152565b9190811015611feb5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b9190820180921161116f57565b9081602091031261068f57516001600160a01b038116810361068f5790565b601054811015611feb5760105f5260205f2001905f90565b9190820391821161116f57565b604051906040820182811067ffffffffffffffff821117611045576040525f6020838281520152565b3d156120b3573d9067ffffffffffffffff821161104557604051916120a8601f8201601f191660200184611ee7565b82523d5f602084013e565b606090565b67ffffffffffffffff81116110455760051b60200190565b8051821015611feb5760209160051b010190565b919060125490818410156121ad5761211091612101859283611fff565b908082116121a5575b50612043565b612119816120b8565b6121266040519182611ee7565b818152601f19612135836120b8565b015f5b81811061218e57505080935f5b8381106121525750505050565b8061215f60019284611fff565b5f52601360205261217260405f20611f53565b61217c82866120d0565b5261218781856120d0565b5001612145565b602090612199611f09565b82828601015201612138565b90505f61210a565b50909150506040516121c0602082611ee7565b5f81525f805b8181106121d257505090565b6020906121dd611f09565b828286010152016121c6565b5f546001600160a01b031633036112c857565b60ff60015460a01c1661220b57565b63d93c066560e01b5f5260045ffd5b60025f8051602061290383398151915254146122435760025f8051602061290383398151915255565b633ee5aeb560e01b5f5260045ffd5b5f19811461116f5760010190565b8181029291811591840414171561116f57565b811561227d570490565b634e487b7160e01b5f52601260045260245ffd5b5f81815260136020526040812060078101805493959360101c6001600160a01b031661dead036124ff57507f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f086001600160a01b0316803b156124fb57828091602460405180948193634964091960e01b83528960048401525af180156124b3579083916124e6575b50505b6001810180548254600284018054999093926001600160a01b039081169216823b156124e25790606487928360405195869485936323b872dd60e01b8552600485015260018060a01b03169e8f602485015260448401525af180156124d7579085916124be575b50505490546040516331a9108f60e11b8152600481019190915290602090829060249082906001600160a01b03165afa9081156124b3579087918491612494575b506001600160a01b03160361248557546002546001600160a01b039081169116811461241f575b50507fe3a7cfa26bda4fc90b8dce659eb0b58decd9d8e7161394f840f6a6db82cf6a5e9160409182519182526020820152a3565b803b15612481578180916084604051809481936332c132f760e21b83528a60048401528b60248401528960448401528860648401525af1801561247657156123eb5761246c828092611ee7565b61022557806123eb565b6040513d84823e3d90fd5b5080fd5b6308c0b8b360e31b8252600482fd5b6124ad915060203d60201161064a5761063c8183611ee7565b5f6123c4565b6040513d85823e3d90fd5b816124c891611ee7565b6124d357835f612383565b8380fd5b6040513d87823e3d90fd5b8680fd5b816124f091611ee7565b61248157815f612319565b8280fd5b815460025485916001600160a01b039182169116810361255c57509054612557919060101c6001600160a01b03908116907f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f081661288a565b61231c565b905061271061256d60065487612260565b049061271061257e600b5484612260565b049061271061258f600c5485612260565b04906125a48261259f8587612043565b612043565b92806126bc575b50508061267b575b5080612601575b50906125c96125579286612043565b905460101c6001600160a01b03908116907f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f081661288a565b7f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f086001600160a01b031690813b1561068f575f91602483926040519485938492634964091960e01b845260048401525af1801561065157156125ba57612557929194505f61266e91611ee7565b6125c95f949192506125ba565b6003546126b691906001600160a01b03908116907f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f081661288a565b5f6125b3565b6126ef917f0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f086001600160a01b031661288a565b5f806125ab565b601254811015610973575f52601360205260405f20600781015460ff8160081c166109735760ff1661097357612730600582015442612043565b9081156127da57600681015491828110156127d057612759600460038401549301548093612043565b92670de0b6b3a76400008202918204670de0b6b3a76400000361116f5761277f91612273565b670de0b6b3a76400000391670de0b6b3a7640000831161116f576127c6670de0b6b3a764000091826127bf816127b8886127cd99612260565b0480612260565b0490612260565b0490611fff565b90565b5060049150015490565b60039150015490565b8015612885576004546001600160a01b0316801561287f576020600491604051928380926375c8e9bf60e11b82525afa908115610651575f9161284d575b50801561070457670de0b6b3a76400008202918204670de0b6b3a76400000361116f576127cd91612273565b90506020813d602011612877575b8161286860209383611ee7565b8101031261068f57515f612821565b3d915061285b565b50505f90565b505f90565b916040519163a9059cbb60e01b5f5260018060a01b031660045260245260205f60448180865af19060015f51148216156128ea575b604052156128ca5750565b635274afe760e01b5f9081526001600160a01b0391909116600452602490fd5b906001811516610f1557823b15153d151616906128bf56fe9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a264697066735822122016a018fb24a68bce61984393103ecf5fb85058ca3e9a180ccc6505402f14114364736f6c634300081a0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f0800000000000000000000000035ec3ebaaa5e471ec2b05ebc28ab3c8734c4a4b7000000000000000000000000da2cab7348bb63f4e8cf82a877ae280f0e48ed60

-----Decoded View---------------
Arg [0] : dutchToken_ (address): 0x7B2F34F323148262E145f60C3C715822a5430F08
Arg [1] : dutchVaultAddress_ (address): 0x35EC3EBaaa5E471Ec2B05EBC28ab3C8734c4a4B7
Arg [2] : opsWallet_ (address): 0xDA2CAb7348Bb63f4e8cf82a877AE280F0e48ed60

-----Encoded View---------------
3 Constructor Arguments found :
Arg [0] : 0000000000000000000000007b2f34f323148262e145f60c3c715822a5430f08
Arg [1] : 00000000000000000000000035ec3ebaaa5e471ec2b05ebc28ab3c8734c4a4b7
Arg [2] : 000000000000000000000000da2cab7348bb63f4e8cf82a877ae280f0e48ed60


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.