ConvertibleDepositAuctioneer

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/policies/deposits/ConvertibleDepositAuctioneer.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0
/// forge-lint: disable-start(mixed-case-function, screaming-snake-case-const)
pragma solidity >=0.8.20;

// Libraries
import {ReentrancyGuard} from "@solmate-6.2.0/utils/ReentrancyGuard.sol";
import {FixedPointMathLib} from "@solmate-6.2.0/utils/FixedPointMathLib.sol";
import {FullMath} from "src/libraries/FullMath.sol";
import {EnumerableSet} from "@openzeppelin-5.3.0/utils/structs/EnumerableSet.sol";

// Interfaces
import {IERC20} from "src/interfaces/IERC20.sol";
import {IConvertibleDepositAuctioneer} from "src/policies/interfaces/deposits/IConvertibleDepositAuctioneer.sol";
import {IConvertibleDepositFacility} from "src/policies/interfaces/deposits/IConvertibleDepositFacility.sol";

// Bophades dependencies
import {Kernel, Keycode, Permissions, Policy, toKeycode} from "src/Kernel.sol";
import {ROLESv1} from "src/modules/ROLES/ROLES.v1.sol";
import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol";
import {ConvertibleDepositFacility} from "src/policies/deposits/ConvertibleDepositFacility.sol";

/// @title  Convertible Deposit Auctioneer
/// @notice Implementation of the {IConvertibleDepositAuctioneer} interface for a specific deposit token and 1 or more deposit periods
/// @dev    This contract implements an auction for convertible deposit tokens. It runs these auctions according to the following principles:
///         - Auctions are of infinite duration
///         - Auctions are of infinite capacity
///         - Users place bids by supplying an amount of the configured bid token
///         - The payout token is a receipt token (managed by {DepositManager}), which can be converted to OHM at the price that was set at the time of the bid
///         - During periods of greater demand, the conversion price will increase
///         - During periods of lower demand, the conversion price will decrease
///         - The auction has a minimum price, below which the conversion price will not decrease
///         - The auction has a target amount of convertible OHM to sell per day
///         - When the target is reached, the amount of OHM required to increase the conversion price will decrease, resulting in more rapid price increases (assuming there is demand)
///         - The auction parameters are able to be updated in order to tweak the auction's behaviour
contract ConvertibleDepositAuctioneer is
    IConvertibleDepositAuctioneer,
    Policy,
    PolicyEnabler,
    ReentrancyGuard
{
    using FullMath for uint256;
    using EnumerableSet for EnumerableSet.UintSet;
    using EnumerableSet for EnumerableSet.AddressSet;

    // ========== CONSTANTS ========== //

    /// @notice The role that can perform periodic actions, such as updating the auction parameters
    bytes32 public constant ROLE_EMISSION_MANAGER = "cd_emissionmanager";

    /// @notice Scale of the OHM token
    uint256 internal constant _ohmScale = 1e9;

    uint24 public constant ONE_HUNDRED_PERCENT = 100e2;

    /// @notice Fixed point scale (WAD)
    uint256 internal constant WAD = 1e18;

    /// @notice Minimum and maximum allowed tick size base (in WAD)
    uint256 internal constant TICK_SIZE_BASE_MIN = 1e18; // 1.0
    uint256 internal constant TICK_SIZE_BASE_MAX = 10e18; // 10.0

    /// @notice Maximum safe exponent for rpow to prevent overflow
    uint256 internal constant MAX_RPOW_EXP = 41;

    /// @notice Seconds in one day
    uint256 internal constant SECONDS_IN_DAY = 1 days;

    /// @notice The length of the enable parameters
    uint256 internal constant _ENABLE_PARAMS_LENGTH = 192;

    /// @notice The minimum tick size
    uint256 internal constant _TICK_SIZE_MINIMUM = 1;

    // ========== STRUCTS ========== //

    struct BidOutput {
        uint256 tickCapacity;
        uint256 tickPrice;
        uint256 tickSize;
        uint256 depositIn;
        uint256 ohmOut;
    }

    struct BidParams {
        uint8 depositPeriod;
        uint256 depositAmount;
        uint256 minOhmOut;
        bool wrapPosition;
        bool wrapReceipt;
    }

    struct PendingDepositPeriodChange {
        uint8 depositPeriod;
        bool enable;
    }

    // ========== STATE VARIABLES ========== //

    /// @notice Whether the deposit period is enabled
    mapping(uint8 depositPeriod => bool isDepositPeriodEnabled) internal _depositPeriodsEnabled;

    /// @notice The deposit asset
    IERC20 internal immutable _DEPOSIT_ASSET;

    /// @notice Array of enabled deposit periods
    EnumerableSet.UintSet internal _depositPeriods;

    /// @notice Previous tick for each deposit period
    /// @dev    Use `getCurrentTick()` to recalculate and access the latest data
    mapping(uint8 depositPeriod => Tick previousTick) internal _depositPeriodPreviousTicks;

    /// @notice Address of the Convertible Deposit Facility
    ConvertibleDepositFacility public immutable CD_FACILITY;

    /// @notice Auction parameters
    /// @dev    These values should only be set through the `setAuctionParameters()` function
    AuctionParameters internal _auctionParameters;

    /// @notice The current tick size
    uint256 internal _currentTickSize;

    /// @notice Auction state for the day
    Day internal _dayState;

    /// @notice The tick step
    /// @dev    See `getTickStep()` for more information
    uint24 internal _tickStep;

    /// @notice The minimum bid amount
    /// @dev    The minimum bid amount is the minimum amount of deposit asset that can be bid
    ///         See `getMinimumBid()` for more information
    uint256 internal _minimumBid;

    /// @notice The base used for exponential tick size reduction (by 1/(base^multiplier)) when the day target is crossed (WAD, 1e18 = 1.0)
    uint256 internal _tickSizeBase;

    /// @notice The index of the next auction result
    uint8 internal _auctionResultsNextIndex;

    /// @notice The number of days that auction results are tracked for
    uint8 internal _auctionTrackingPeriod;

    /// @notice The auction results, where a positive number indicates an over-subscription for the day.
    /// @dev    The length of this array is equal to the auction tracking period
    int256[] internal _auctionResults;

    /// @notice Queue of pending deposit period enable/disable changes
    PendingDepositPeriodChange[] internal _pendingDepositPeriodChanges;

    // ========== SETUP ========== //

    constructor(
        address kernel_,
        address cdFacility_,
        address depositAsset_
    ) Policy(Kernel(kernel_)) {
        if (cdFacility_ == address(0))
            revert ConvertibleDepositAuctioneer_InvalidParams("cd facility");
        if (depositAsset_ == address(0))
            revert ConvertibleDepositAuctioneer_InvalidParams("deposit asset");

        CD_FACILITY = ConvertibleDepositFacility(cdFacility_);
        _DEPOSIT_ASSET = IERC20(depositAsset_);

        // PolicyEnabler makes this disabled until enabled
    }

    /// @inheritdoc Policy
    function configureDependencies() external override returns (Keycode[] memory dependencies) {
        dependencies = new Keycode[](1);
        dependencies[0] = toKeycode("ROLES");

        ROLES = ROLESv1(getModuleAddress(dependencies[0]));
    }

    /// @inheritdoc Policy
    function requestPermissions()
        external
        view
        override
        returns (Permissions[] memory permissions)
    {}

    function VERSION() external pure returns (uint8 major, uint8 minor) {
        major = 1;
        minor = 0;

        return (major, minor);
    }

    // ========== AUCTION ========== //

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function performs the following:
    ///             - Updates the current tick based on the current state
    ///             - Determines the amount of OHM that can be purchased for the deposit amount, and the updated tick capacity and price
    ///             - Updates the day state, if necessary
    ///             - Creates a convertible deposit position using the deposit amount, the average conversion price and the deposit period
    ///
    ///             This function reverts if:
    ///             - The contract is not active
    ///             - The auction is disabled
    ///             - The bid amount is below the minimum bid
    ///             - Deposits are not enabled for the asset/period/operator
    ///             - The depositor has not approved the DepositManager to spend the deposit asset
    ///             - The depositor has an insufficient balance of the deposit asset
    ///             - The calculated amount of OHM out is 0
    ///             - The calculated amount of OHM out is < minOhmOut_
    function bid(
        uint8 depositPeriod_,
        uint256 depositAmount_,
        uint256 minOhmOut_,
        bool wrapPosition_,
        bool wrapReceipt_
    )
        external
        override
        nonReentrant
        onlyEnabled
        onlyDepositPeriodEnabled(depositPeriod_)
        returns (uint256, uint256, uint256, uint256)
    {
        return
            _bid(
                BidParams({
                    depositPeriod: depositPeriod_,
                    depositAmount: depositAmount_,
                    minOhmOut: minOhmOut_,
                    wrapPosition: wrapPosition_,
                    wrapReceipt: wrapReceipt_
                })
            );
    }

    /// @notice Internal function to submit an auction bid on the given deposit asset and period
    /// @dev    This function expects the calling function to have already validated the contract state and deposit asset and period
    function _bid(BidParams memory params) internal returns (uint256, uint256, uint256, uint256) {
        // If target is 0, auction is disabled - reject all bids
        if (_auctionParameters.target == 0) {
            revert ConvertibleDepositAuctioneer_ConvertedAmountZero();
        }

        // Check minimum bid requirement
        if (params.depositAmount < _minimumBid) {
            revert ConvertibleDepositAuctioneer_BidBelowMinimum(params.depositAmount, _minimumBid);
        }

        uint256 ohmOut;
        uint256 depositIn;
        {
            // Get the current tick for the deposit asset and period
            Tick memory updatedTick = _getCurrentTick(params.depositPeriod);

            // Get bid results
            BidOutput memory output = _previewBid(params.depositAmount, updatedTick);

            // Reject if the OHM out is 0
            if (output.ohmOut == 0) revert ConvertibleDepositAuctioneer_ConvertedAmountZero();

            // Reject if the OHM out is below the minimum OHM out
            if (output.ohmOut < params.minOhmOut)
                revert ConvertibleDepositAuctioneer_ConvertedAmountSlippage(
                    output.ohmOut,
                    params.minOhmOut
                );

            // Update state
            _dayState.convertible += output.ohmOut;

            // Update current tick
            updatedTick.price = output.tickPrice;
            updatedTick.capacity = output.tickCapacity;
            updatedTick.lastUpdate = uint48(block.timestamp);
            _depositPeriodPreviousTicks[params.depositPeriod] = updatedTick;

            // Update the current tick size
            if (output.tickSize != _currentTickSize) {
                if (_depositPeriods.length() > 1) {
                    // Before updating the global tick size, ensure that all other periods are updated
                    // This ensures that if the tick size changes, the change will not be
                    // applied retroactively
                    // Only required if there are other deposit periods than the one being bid on
                    _updateCurrentTicks(params.depositPeriod);
                }

                _currentTickSize = output.tickSize;
            }

            // Set values for the rest of the function
            ohmOut = output.ohmOut;
            depositIn = output.depositIn;
        }

        // Calculate average price based on the total deposit and ohmOut
        // This is the number of deposit tokens per OHM token
        // We round up to be conservative

        // Create the receipt tokens and position
        (uint256 positionId, uint256 receiptTokenId, uint256 actualAmount) = CD_FACILITY
            .createPosition(
                IConvertibleDepositFacility.CreatePositionParams({
                    asset: _DEPOSIT_ASSET,
                    periodMonths: params.depositPeriod,
                    depositor: msg.sender,
                    amount: depositIn,
                    conversionPrice: depositIn.mulDivUp(_ohmScale, ohmOut), // Assets per OHM, deposit token scale
                    wrapPosition: params.wrapPosition,
                    wrapReceipt: params.wrapReceipt
                })
            );

        // Emit event
        emit Bid(
            msg.sender,
            address(_DEPOSIT_ASSET),
            params.depositPeriod,
            depositIn,
            ohmOut,
            positionId
        );

        return (ohmOut, positionId, receiptTokenId, actualAmount);
    }

    /// @notice Internal function to preview the quantity of OHM tokens that can be purchased for a given deposit amount
    /// @dev    This function performs the following:
    ///         - Cycles through ticks until the deposit is fully converted
    ///         - If the current tick has enough capacity, it will be used
    ///         - If the current tick does not have enough capacity, the remaining capacity will be used. The current tick will then shift to the next tick, resulting in the capacity being filled to the tick size, and the price being multiplied by the tick step.
    ///
    ///         Notes:
    ///         - This function assumes that the auction is active (i.e. the target is non-zero) and the tick size is non-zero
    ///         - The function returns the updated tick capacity and price after the bid
    ///         - If the capacity of a tick is depleted (but does not cross into the next tick), the current tick will be shifted to the next one. This ensures that `getCurrentTick()` will not return a tick that has been depleted.
    ///
    /// @param  deposit_            The amount of deposit to be bid
    /// @return output              The output of the bid
    function _previewBid(
        uint256 deposit_,
        Tick memory tick_
    ) internal view returns (BidOutput memory output) {
        uint256 remainingDeposit = deposit_;
        output.tickCapacity = tick_.capacity;
        output.tickPrice = tick_.price;
        output.tickSize = _currentTickSize;

        // Load, as this will be used within the loop
        AuctionParameters memory auctionParams = _auctionParameters;
        Day memory dayState = _dayState;

        // Cycle through the ticks until the deposit is fully converted
        while (remainingDeposit > 0) {
            uint256 depositAmount = remainingDeposit;
            uint256 convertibleAmount = _getConvertedDeposit(remainingDeposit, output.tickPrice);

            // No point in continuing if the converted amount is 0
            if (convertibleAmount == 0) break;

            // Determine if we're crossing a threshold or depleting capacity
            uint256 baseConvertible = dayState.convertible + output.ohmOut;
            uint256 ohmUntilNextThreshold = _getOhmUntilNextThreshold(
                baseConvertible,
                auctionParams.target
            );
            bool willCrossThreshold = (ohmUntilNextThreshold > 0 &&
                ohmUntilNextThreshold <= convertibleAmount);
            bool willDepleteTick = (output.tickCapacity <= convertibleAmount);

            // If either condition triggers, we need to perform a tick transition
            if (willCrossThreshold || willDepleteTick) {
                // Use whichever limit is more restrictive
                if (willCrossThreshold && ohmUntilNextThreshold < output.tickCapacity) {
                    // Day target threshold is the limiting factor
                    convertibleAmount = ohmUntilNextThreshold;
                } else {
                    // Tick capacity is the limiting factor
                    convertibleAmount = output.tickCapacity;
                }

                // Calculate deposit amount for this limited chunk
                // Round in favour of the protocol
                depositAmount = convertibleAmount.mulDivUp(output.tickPrice, _ohmScale);

                // Trigger tick transition: increase price and recalculate tick size
                output.tickPrice = _getNewTickPrice(output.tickPrice, _tickStep);
                output.tickSize = _getNewTickSize(
                    baseConvertible + convertibleAmount,
                    auctionParams
                );
                output.tickCapacity = output.tickSize;
            }
            // Otherwise, the tick has enough capacity and we're not crossing threshold
            else {
                output.tickCapacity -= convertibleAmount;
            }

            // Record updates to the deposit and OHM
            if (depositAmount <= remainingDeposit) {
                remainingDeposit -= depositAmount;
            } else {
                remainingDeposit = 0;
            }
            output.ohmOut += convertibleAmount;
        }

        output.depositIn = deposit_ - remainingDeposit;

        return output;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function previewBid(
        uint8 depositPeriod_,
        uint256 bidAmount_
    )
        external
        view
        override
        onlyEnabled
        onlyDepositPeriodEnabled(depositPeriod_)
        returns (uint256 ohmOut)
    {
        // If target is 0, auction is disabled - return 0
        if (_auctionParameters.target == 0) {
            return 0;
        }

        // If bid amount is below minimum, return 0
        if (bidAmount_ < _minimumBid) {
            return 0;
        }

        // Get the updated tick based on the current state
        Tick memory currentTick = _getCurrentTick(depositPeriod_);

        // Preview the bid results
        BidOutput memory output = _previewBid(bidAmount_, currentTick);
        ohmOut = output.ohmOut;

        return ohmOut;
    }

    // ========== VIEW FUNCTIONS ========== //

    /// @notice Internal function to preview the quantity of OHM tokens that can be purchased for a given deposit amount
    /// @dev    This function does not take into account the capacity of the current tick
    ///
    /// @param  deposit_            The amount of deposit to be converted
    /// @param  price_              The price of the deposit in OHM
    /// @return convertibleAmount   The quantity of OHM tokens that can be purchased
    function _getConvertedDeposit(
        uint256 deposit_,
        uint256 price_
    ) internal pure returns (uint256 convertibleAmount) {
        // As price represents the number of bid tokens per OHM, we can convert the deposit to OHM by dividing by the price and adjusting for the decimal scale
        convertibleAmount = deposit_.mulDiv(_ohmScale, price_);
        return convertibleAmount;
    }

    /// @notice Internal function to preview the new price of the current tick after applying the tick step
    /// @dev    This function does not take into account the capacity of the current tick
    ///
    /// @param  currentPrice_       The current price of the tick in terms of the bid token
    /// @param  tickStep_           The step size of the tick
    /// @return newPrice            The new price of the tick
    function _getNewTickPrice(
        uint256 currentPrice_,
        uint256 tickStep_
    ) internal pure returns (uint256 newPrice) {
        newPrice = currentPrice_.mulDivUp(tickStep_, ONE_HUNDRED_PERCENT);
        return newPrice;
    }

    /// @notice Internal function to calculate the new tick size based on the amount of OHM that has been converted in the current day
    /// @dev    This implements exponential tick size reduction (by 1/(base^multiplier)) for each multiple of the day target that is reached
    ///         If the new tick size is 0 or a calculation would result in an overflow, the tick size is set to the minimum
    ///
    /// @param  ohmOut_     The amount of OHM that has been converted in the current day
    /// @return newTickSize The new tick size
    function _getNewTickSize(
        uint256 ohmOut_,
        AuctionParameters memory auctionParams_
    ) internal view returns (uint256 newTickSize) {
        // If the day target is zero, the tick size is always the configured size (which should be 0 to disable auction)
        if (auctionParams_.target == 0) {
            return auctionParams_.tickSize;
        }

        // Calculate the multiplier
        uint256 multiplier = ohmOut_ / auctionParams_.target;

        // If the day target has not been met, the tick size remains the standard
        if (multiplier == 0) {
            newTickSize = auctionParams_.tickSize;
            return newTickSize;
        }

        // If the multiplier would result in an overflow in rpow, return minimum tick size
        // For base = 1e18, rpow always returns 1e18 regardless of exponent, so overflow is impossible
        if (_tickSizeBase != TICK_SIZE_BASE_MIN && multiplier > MAX_RPOW_EXP) {
            return _TICK_SIZE_MINIMUM;
        }

        // Otherwise, the tick size is reduced by a factor of (base^multiplier) (WAD base)
        // divisor = _tickSizeBase^multiplier (scaled by 1e18 via rpow)
        uint256 divisor = FixedPointMathLib.rpow(_tickSizeBase, multiplier, WAD);
        if (divisor == 0) return _TICK_SIZE_MINIMUM;

        // newTickSize = tickSize * WAD / divisor (round down)
        newTickSize = auctionParams_.tickSize.mulDiv(WAD, divisor);

        // This can round down to zero (which would cause problems with calculations), so provide a fallback
        if (newTickSize == 0) return _TICK_SIZE_MINIMUM;

        return newTickSize;
    }

    /// @notice Internal function to calculate the amount of OHM remaining until the next day target threshold is reached
    ///
    /// @param  currentConvertible_ The current cumulative amount of OHM that has been converted
    /// @param  target_             The day target
    /// @return ohmUntilThreshold   The amount of OHM remaining until the next threshold
    function _getOhmUntilNextThreshold(
        uint256 currentConvertible_,
        uint256 target_
    ) internal pure returns (uint256 ohmUntilThreshold) {
        if (target_ == 0) return type(uint256).max;

        uint256 currentMultiplier = currentConvertible_ / target_;
        uint256 nextThreshold = (currentMultiplier + 1) * target_;

        return nextThreshold - currentConvertible_;
    }

    function _getCurrentTick(uint8 depositPeriod_) internal view returns (Tick memory tick) {
        Tick memory previousTick = _depositPeriodPreviousTicks[depositPeriod_];

        // If the target is 0, auction is disabled - return previous tick without decay
        if (_auctionParameters.target == 0) {
            return previousTick;
        }

        // Find amount of time passed and new capacity to add
        uint256 newCapacity;
        {
            uint256 timePassed = block.timestamp - previousTick.lastUpdate;
            // The capacity to add is the day target multiplied by the proportion of time passed in a day
            // It is also adjusted by the number of deposit periods that are enabled, otherwise each auction would have too much capacity added
            uint256 capacityToAdd = (_auctionParameters.target * timePassed) /
                SECONDS_IN_DAY /
                _depositPeriods.length();

            tick = previousTick;
            newCapacity = tick.capacity + capacityToAdd;
        }

        // Iterate over the ticks until the capacity is within the tick size
        // This is the opposite of what happens in the bid function
        // It uses the standard tick size (unaffected by the achievement of the day target),
        // otherwise the tick price would decay quickly
        uint256 tickSize = _auctionParameters.tickSize;
        while (newCapacity > tickSize) {
            // Reduce the capacity by the tick size
            newCapacity -= tickSize;

            // Adjust the tick price by the tick step, in the opposite direction to the bid function
            tick.price = tick.price.mulDivUp(ONE_HUNDRED_PERCENT, _tickStep);

            // Tick price does not go below the minimum
            if (tick.price < _auctionParameters.minPrice) {
                tick.price = _auctionParameters.minPrice;
                break;
            }
        }

        // Set the capacity, but ensure it doesn't exceed the current tick size
        // (which may have been reduced if the day target was met)
        tick.capacity = newCapacity > _currentTickSize ? _currentTickSize : newCapacity;

        return tick;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function calculates the tick at the current time.
    ///
    ///             It uses the following approach:
    ///             - Calculate the added capacity based on the time passed since the last bid, and add it to the current capacity to get the new capacity
    ///             - Until the new capacity is <= to the standard tick size, reduce the capacity by the standard tick size and reduce the price by the tick step
    ///             - If the calculated price is ever lower than the minimum price, the new price is set to the minimum price and the capacity is set to the standard tick size
    ///
    ///             Notes:
    ///             - If the target is 0, the price will not decay and the capacity will not change. It will only decay when a target is set again to a non-zero value.
    ///
    ///             This function reverts if:
    ///             - The deposit asset and period are not enabled
    function getCurrentTick(
        uint8 depositPeriod_
    ) external view onlyDepositPeriodEnabled(depositPeriod_) returns (Tick memory tick) {
        return _getCurrentTick(depositPeriod_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function returns the previous tick for the deposit period
    ///             If the deposit period is not configured, all values will be 0
    function getPreviousTick(uint8 depositPeriod_) public view override returns (Tick memory tick) {
        return _depositPeriodPreviousTicks[depositPeriod_];
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getCurrentTickSize() external view override returns (uint256) {
        return _currentTickSize;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getAuctionParameters() external view override returns (AuctionParameters memory) {
        return _auctionParameters;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function isAuctionActive() external view override returns (bool) {
        return _auctionParameters.target > 0;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getDayState() external view override returns (Day memory) {
        return _dayState;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getTickStep() external view override returns (uint24) {
        return _tickStep;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getMinimumBid() external view override returns (uint256) {
        return _minimumBid;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getAuctionTrackingPeriod() external view override returns (uint8) {
        return _auctionTrackingPeriod;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getAuctionResultsNextIndex() external view override returns (uint8) {
        return _auctionResultsNextIndex;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getAuctionResults() external view override returns (int256[] memory) {
        return _auctionResults;
    }

    // ========== PENDING CHANGES HELPERS ========== //

    /// @notice Validates that the requested action would not result in the same effective state, preventing redundant queue operations
    /// @param  depositPeriod_  The deposit period to check
    /// @param  enable_         Whether the requested operation is to enable (true) or disable (false)
    function _validateNoDuplicatePendingChange(uint8 depositPeriod_, bool enable_) internal view {
        // Get the effective state considering pending changes
        bool effectiveState = _getEffectiveDepositPeriodState(depositPeriod_);

        // Check if the requested change would result in the same state
        if (effectiveState == enable_) {
            revert ConvertibleDepositAuctioneer_DepositPeriodInvalidState(
                address(_DEPOSIT_ASSET),
                depositPeriod_,
                effectiveState
            );
        }
    }

    /// @notice Gets the effective state of a deposit period considering pending changes
    /// @param  depositPeriod_  The deposit period to check
    /// @return effectiveState  The effective enabled state (current state + pending changes)
    function _getEffectiveDepositPeriodState(
        uint8 depositPeriod_
    ) internal view returns (bool effectiveState) {
        // Start with current state
        effectiveState = _depositPeriodsEnabled[depositPeriod_];

        // Later entries take precedence; scan from the end and short-circuit
        for (uint256 i = _pendingDepositPeriodChanges.length; i > 0; ) {
            unchecked {
                i--;
            }
            PendingDepositPeriodChange memory c = _pendingDepositPeriodChanges[i];
            if (c.depositPeriod == depositPeriod_) {
                effectiveState = c.enable;
                break;
            }
        }

        return effectiveState;
    }

    /// @notice Processes all pending deposit period changes
    /// @param  tickSize_   The tick size to initialize new periods with
    /// @param  minPrice_   The minimum price to initialize new periods with
    function _processPendingDepositPeriodChanges(uint256 tickSize_, uint256 minPrice_) internal {
        uint256 len = _pendingDepositPeriodChanges.length;
        for (uint256 i = 0; i < len; ) {
            PendingDepositPeriodChange memory change = _pendingDepositPeriodChanges[i];

            if (change.enable) {
                _enableDepositPeriod(change.depositPeriod, tickSize_, minPrice_);
            } else {
                _disableDepositPeriod(change.depositPeriod);
            }
            unchecked {
                ++i;
            }
        }

        // Clear the pending changes queue
        delete _pendingDepositPeriodChanges;
    }

    /// @notice Internal function to actually enable a deposit period
    /// @param  depositPeriod_  The deposit period to enable
    /// @param  tickSize_       The tick size to initialize with
    /// @param  minPrice_       The minimum price to initialize with
    function _enableDepositPeriod(
        uint8 depositPeriod_,
        uint256 tickSize_,
        uint256 minPrice_
    ) internal {
        // Skip if already enabled (shouldn't happen due to validation, but defensive)
        if (_depositPeriodsEnabled[depositPeriod_]) {
            return;
        }

        // Enable the deposit period
        _depositPeriodsEnabled[depositPeriod_] = true;

        // Add the deposit period to the array
        // No check necessary, as EnumerableSet will handle duplicates
        _depositPeriods.add(depositPeriod_);

        // Initialize the tick with the new auction parameters
        _depositPeriodPreviousTicks[depositPeriod_] = Tick({
            price: minPrice_,
            capacity: tickSize_,
            lastUpdate: uint48(block.timestamp)
        });

        // Emit event for actual enabling
        emit DepositPeriodEnabled(address(_DEPOSIT_ASSET), depositPeriod_);
    }

    /// @notice Internal function to actually disable a deposit period
    /// @param  depositPeriod_  The deposit period to disable
    function _disableDepositPeriod(uint8 depositPeriod_) internal {
        // Skip if already disabled (shouldn't happen due to validation, but defensive)
        if (!_depositPeriodsEnabled[depositPeriod_]) {
            return;
        }

        // Disable the deposit period
        _depositPeriodsEnabled[depositPeriod_] = false;

        // Remove the deposit period from the array
        // No check necessary, as EnumerableSet will handle non-existence
        _depositPeriods.remove(depositPeriod_);

        // Remove the tick
        delete _depositPeriodPreviousTicks[depositPeriod_];

        // Emit event for actual disabling
        emit DepositPeriodDisabled(address(_DEPOSIT_ASSET), depositPeriod_);
    }

    // ========== ASSET CONFIGURATION ========== //

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getDepositAsset() external view override returns (IERC20) {
        return _DEPOSIT_ASSET;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getDepositPeriods() external view override returns (uint8[] memory) {
        uint256 length = _depositPeriods.length();
        uint8[] memory periods = new uint8[](length);
        for (uint256 i = 0; i < length; i++) {
            periods[i] = uint8(_depositPeriods.at(i));
        }

        return periods;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function isDepositPeriodEnabled(
        uint8 depositPeriod_
    ) public view override returns (bool isEnabled, bool isPendingEnabled) {
        isEnabled = _depositPeriodsEnabled[depositPeriod_];
        isPendingEnabled = _getEffectiveDepositPeriodState(depositPeriod_);
        return (isEnabled, isPendingEnabled);
    }

    function _onlyDepositPeriodEnabled(uint8 depositPeriod_) internal view {
        (bool isEnabled, ) = isDepositPeriodEnabled(depositPeriod_);
        if (!isEnabled) {
            revert ConvertibleDepositAuctioneer_DepositPeriodNotEnabled(
                address(_DEPOSIT_ASSET),
                depositPeriod_
            );
        }
    }

    /// @notice Modifier to check if a deposit period is enabled
    modifier onlyDepositPeriodEnabled(uint8 depositPeriod_) {
        _onlyDepositPeriodEnabled(depositPeriod_);
        _;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        Notes:
    ///             - Enabling a deposit period will queue the change to be processed at the next setAuctionParameters call
    ///             - Can be called while the contract is disabled (changes will be processed when contract is enabled)
    ///             - The deposit period will reset the minimum price and tick size to the standard values when actually enabled
    ///
    ///             This function will revert if:
    ///             - The caller is not a manager or admin
    ///             - The deposit period is 0
    ///             - The effective state would result in enabling an already enabled period
    function enableDepositPeriod(uint8 depositPeriod_) external override onlyManagerOrAdminRole {
        // Validate that the deposit period is not 0
        if (depositPeriod_ == 0)
            revert ConvertibleDepositAuctioneer_InvalidParams("deposit period");

        // Validate that this wouldn't result in a duplicate state
        _validateNoDuplicatePendingChange(depositPeriod_, true);

        // Add to pending changes queue
        _pendingDepositPeriodChanges.push(
            PendingDepositPeriodChange({depositPeriod: depositPeriod_, enable: true})
        );

        // Emit event to indicate the change has been queued
        emit DepositPeriodEnableQueued(address(_DEPOSIT_ASSET), depositPeriod_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        Notes:
    ///             - Disabling a deposit period will queue the change to be processed at the next setAuctionParameters call
    ///             - Can be called while the contract is disabled (changes will be processed when contract is enabled)
    ///
    ///             This function will revert if:
    ///             - The caller is not a manager or admin
    ///             - The deposit period is 0
    ///             - The effective state would result in disabling an already disabled period
    function disableDepositPeriod(uint8 depositPeriod_) external override onlyManagerOrAdminRole {
        // Validate that the deposit period is not 0
        if (depositPeriod_ == 0)
            revert ConvertibleDepositAuctioneer_InvalidParams("deposit period");

        // Validate that this wouldn't result in a duplicate state
        _validateNoDuplicatePendingChange(depositPeriod_, false);

        // Add to pending changes queue
        _pendingDepositPeriodChanges.push(
            PendingDepositPeriodChange({depositPeriod: depositPeriod_, enable: false})
        );

        // Emit event to indicate the change has been queued
        emit DepositPeriodDisableQueued(address(_DEPOSIT_ASSET), depositPeriod_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getDepositPeriodsCount() external view override returns (uint256) {
        return _depositPeriods.length();
    }

    /// @notice Gets the list of pending deposit period changes, from first to last
    function getPendingDepositPeriodChanges()
        external
        view
        returns (PendingDepositPeriodChange[] memory)
    {
        return _pendingDepositPeriodChanges;
    }

    // ========== ADMIN FUNCTIONS ========== //

    function _setAuctionParameters(uint256 target_, uint256 tickSize_, uint256 minPrice_) internal {
        // The target can be zero (disables auction)
        // When target is zero, tick size and min price are irrelevant since auction is disabled

        // Tick size must be non-zero when target is non-zero
        if (target_ > 0 && tickSize_ == 0)
            revert ConvertibleDepositAuctioneer_InvalidParams("tick size");

        // Min price must be non-zero when target is non-zero (can be zero when auction is disabled)
        if (target_ > 0 && minPrice_ == 0)
            revert ConvertibleDepositAuctioneer_InvalidParams("min price");

        // If the target is non-zero, the tick size must be <= target
        if (target_ > 0 && tickSize_ > target_)
            revert ConvertibleDepositAuctioneer_InvalidParams("tick size");

        _auctionParameters = AuctionParameters({
            target: target_,
            tickSize: tickSize_,
            minPrice: minPrice_
        });

        // Emit event
        emit AuctionParametersUpdated(address(_DEPOSIT_ASSET), target_, tickSize_, minPrice_);
    }

    function _storeAuctionResults(uint256 previousTarget_) internal {
        // Skip if inactive
        if (!isEnabled) return;

        // If the next index is 0, reset the results before inserting
        // This ensures that the previous results are available for 24 hours
        if (_auctionResultsNextIndex == 0) {
            _auctionResults = new int256[](_auctionTrackingPeriod);
        }

        // Store the auction results
        // Negative values will indicate under-selling
        /// forge-lint: disable-start(unsafe-typecast)
        _auctionResults[_auctionResultsNextIndex] =
            int256(_dayState.convertible) -
            int256(previousTarget_);
        /// forge-lint: disable-end(unsafe-typecast)

        // Emit event
        emit AuctionResult(
            address(_DEPOSIT_ASSET),
            _dayState.convertible,
            previousTarget_,
            _auctionResultsNextIndex
        );

        // Increment the index (or loop around)
        _auctionResultsNextIndex++;
        // Loop around if necessary
        if (_auctionResultsNextIndex >= _auctionTrackingPeriod) {
            _auctionResultsNextIndex = 0;
        }

        // Reset the day state
        _dayState = Day({initTimestamp: uint48(block.timestamp), convertible: 0});
    }

    /// @notice Sets tick parameters for all enabled deposit periods
    ///
    /// @param  tickSize_           If the new tick size is less than a tick's capacity (or `enforceCapacity_` is true), the tick capacity will be set to this
    /// @param  minPrice_           If the new minimum price is greater than a tick's price (or `enforceMinPrice_` is true), the tick price will be set to this
    /// @param  enforceCapacity_    If true, will set the capacity of each enabled deposit period to the value of `tickSize_`
    /// @param  enforceMinPrice_    If true, will set the price of each enabled deposit period to the value of `minPrice_`
    /// @param  setLastUpdate_      If true, will set the tick's last update to the current timestamp
    function _setNewTickParameters(
        uint256 tickSize_,
        uint256 minPrice_,
        bool enforceCapacity_,
        bool enforceMinPrice_,
        bool setLastUpdate_
    ) internal {
        // Iterate over periods
        uint256 periodLength = _depositPeriods.length();
        for (uint256 j = 0; j < periodLength; j++) {
            uint8 period = uint8(_depositPeriods.at(j));

            // Skip if the deposit period is not enabled
            if (!_depositPeriodsEnabled[period]) continue;

            // Get the previous tick
            Tick storage previousTick = _depositPeriodPreviousTicks[period];

            // Ensure that the tick capacity is not larger than the new tick size
            // Otherwise, excess OHM will be converted
            if (tickSize_ < previousTick.capacity || enforceCapacity_) {
                previousTick.capacity = tickSize_;
            }

            // Ensure that the minimum price is enforced
            // Otherwise, OHM will be converted at a price lower than the minimum
            if (minPrice_ > previousTick.price || enforceMinPrice_) {
                previousTick.price = minPrice_;
            }

            // Set the last update
            if (setLastUpdate_) {
                previousTick.lastUpdate = uint48(block.timestamp);
            }
        }

        // Set the tick size
        // This has the effect of resetting the tick size to the default
        // The tick size may have been adjusted for the previous day if the target was met
        _currentTickSize = tickSize_;
    }

    /// @notice Takes a snapshot of the current tick values for enabled deposit periods
    ///
    /// @param  excludedDepositPeriod_  The deposit period that should be excluded from updates. Provide 0 to not exclude (since 0 is an invalid deposit period).
    function _updateCurrentTicks(uint8 excludedDepositPeriod_) internal {
        // Iterate over periods
        uint256 periodLength = _depositPeriods.length();
        for (uint256 i; i < periodLength; i++) {
            uint8 period = uint8(_depositPeriods.at(i));

            // Skip if the deposit period is excluded
            if (period == excludedDepositPeriod_) continue;

            // Skip if the deposit period is not enabled
            if (!_depositPeriodsEnabled[period]) continue;

            // Get the current tick for the deposit asset and period
            Tick memory updatedTick = _getCurrentTick(period);
            updatedTick.lastUpdate = uint48(block.timestamp);

            // Update the current tick for the deposit period
            _depositPeriodPreviousTicks[period] = updatedTick;
        }
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function assumes that the the caller is only calling once per period (day), as the contract does not track epochs or timestamps.
    ///
    ///             This function performs the following:
    ///             - Performs validation of the inputs
    ///             - Captures the current tick state for all enabled deposit periods
    ///             - Stores the auction results for the previous period
    ///             - Sets the auction parameters
    ///             - Sets the tick parameters for all enabled deposit periods
    ///             - Processes any pending deposit period changes
    ///
    ///             This function reverts if:
    ///             - The caller does not have the ROLE_EMISSION_MANAGER role
    ///             - The new tick size is 0
    ///             - The new min price is 0
    function setAuctionParameters(
        uint256 target_,
        uint256 tickSize_,
        uint256 minPrice_
    ) external override onlyRole(ROLE_EMISSION_MANAGER) {
        uint256 previousTarget = _auctionParameters.target;

        // Update tick state for enabled assets and periods
        // This prevents retroactive application of new parameters
        _updateCurrentTicks(0);

        // Store the auction results based on the previous state, before any changes
        _storeAuctionResults(previousTarget);

        // Update global state
        _setAuctionParameters(target_, tickSize_, minPrice_);

        // The following can be done even if the contract is not active nor initialized, since activating/initializing will set the tick capacity and price

        // Ensure all existing ticks are updated with the new parameters
        _setNewTickParameters(tickSize_, minPrice_, false, false, false);

        // Process pending deposit period changes with the new parameters
        // This must be called after _setNewTickParameters to ensure new periods get correct parameters
        _processPendingDepositPeriodChanges(tickSize_, minPrice_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function will revert if:
    ///             - The caller does not have the ROLE_ADMIN role
    ///             - The new tick step is < 100e2
    ///
    /// @param      newStep_    The new tick step
    function setTickStep(uint24 newStep_) public override onlyManagerOrAdminRole {
        // Value must be more than 100e2
        if (newStep_ < ONE_HUNDRED_PERCENT)
            revert ConvertibleDepositAuctioneer_InvalidParams("tick step");

        // Capture the tick state, otherwise the tick step change will apply retroactively
        _updateCurrentTicks(0);

        // Set the tick step
        _tickStep = newStep_;

        // Emit event
        emit TickStepUpdated(address(_DEPOSIT_ASSET), newStep_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    function getTickSizeBase() external view override returns (uint256) {
        return _tickSizeBase;
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function will revert if:
    ///             - The caller does not have the ROLE_ADMIN or ROLE_MANAGER role
    ///             - The new tick size base is not within the bounds (1e18 ≤ base ≤ 10e18)
    ///
    /// @param      newBase_    The new tick size base
    function setTickSizeBase(uint256 newBase_) public override onlyManagerOrAdminRole {
        // Bounds: 1e18 ≤ base ≤ 10e18
        if (newBase_ < TICK_SIZE_BASE_MIN || newBase_ > TICK_SIZE_BASE_MAX)
            revert ConvertibleDepositAuctioneer_InvalidParams("tick size base");

        // Do not snapshot/update current ticks; applies during bidding only
        _tickSizeBase = newBase_;

        emit TickSizeBaseUpdated(address(_DEPOSIT_ASSET), newBase_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        Notes:
    ///             - Calling this function will erase the previous auction results, which in turn may affect the bond markets created to sell under-sold OHM capacity
    ///
    ///             This function will revert if:
    ///             - The caller does not have the ROLE_ADMIN role
    ///             - The new auction tracking period is 0
    ///
    /// @param      days_    The new auction tracking period
    function setAuctionTrackingPeriod(uint8 days_) public override onlyManagerOrAdminRole {
        // Value must be non-zero
        if (days_ == 0)
            revert ConvertibleDepositAuctioneer_InvalidParams("auction tracking period");

        _auctionTrackingPeriod = days_;

        // Reset the auction results and index and set to the new length
        _auctionResults = new int256[](days_);
        _auctionResultsNextIndex = 0;

        // Emit event
        emit AuctionTrackingPeriodUpdated(address(_DEPOSIT_ASSET), days_);
    }

    /// @inheritdoc IConvertibleDepositAuctioneer
    /// @dev        This function will revert if:
    ///             - The caller does not have the ROLE_ADMIN or ROLE_MANAGER role
    ///
    /// @param      minimumBid_    The new minimum bid amount
    function setMinimumBid(uint256 minimumBid_) external override onlyManagerOrAdminRole {
        _minimumBid = minimumBid_;

        // Emit event
        emit MinimumBidUpdated(address(_DEPOSIT_ASSET), minimumBid_);
    }

    // ========== ACTIVATION/DEACTIVATION ========== //

    /// @inheritdoc PolicyEnabler
    /// @dev        This function will revert if:
    ///             - The enable data is not the correct length
    ///             - The enable data is not an encoded `EnableParams` struct
    ///             - The auction parameters are invalid
    ///             - The tick step is invalid
    ///             - The auction tracking period is invalid
    ///
    ///             This function performs the following:
    ///             - Sets the auction parameters
    ///             - Sets the tick step
    ///             - Sets the auction tracking period
    ///             - Ensures all existing ticks have the current parameters
    ///             - Processes any pending deposit period changes with the new parameters (including any that were pending prior to disabling)
    ///             - Resets the day state
    ///             - Resets the auction results
    function _enable(bytes calldata enableData_) internal override {
        if (enableData_.length != _ENABLE_PARAMS_LENGTH)
            revert ConvertibleDepositAuctioneer_InvalidParams("enable data");

        // Decode the enable data
        EnableParams memory params = abi.decode(enableData_, (EnableParams));

        // Set the auction parameters
        _setAuctionParameters(params.target, params.tickSize, params.minPrice);

        // Set the tick step
        // OK to call this as the caller will have admin role
        setTickStep(params.tickStep);

        // Set the auction tracking period
        setAuctionTrackingPeriod(params.auctionTrackingPeriod);

        // Set the tick size base
        // OK to call this as the caller will have admin role
        setTickSizeBase(params.tickSizeBase);

        // Ensure all existing ticks have the current parameters
        // Also set the lastUpdate to the current block timestamp
        // Otherwise, getCurrentTick() will calculate a long period of time having passed
        _setNewTickParameters(params.tickSize, params.minPrice, true, true, true);

        // Process any pending deposit period changes with the new parameters
        // This enables queued changes to be processed immediately when contract is enabled
        _processPendingDepositPeriodChanges(params.tickSize, params.minPrice);

        // Reset the day state
        _dayState = Day({initTimestamp: uint48(block.timestamp), convertible: 0});

        // Reset the auction results
        _auctionResults = new int256[](_auctionTrackingPeriod);
        _auctionResultsNextIndex = 0;
    }
}
/// forge-lint: disable-end(mixed-case-function, screaming-snake-case-const)
"
    },
    "dependencies/solmate-6.2.0/src/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}
"
    },
    "dependencies/solmate-6.2.0/src/utils/FixedPointMathLib.sol": {
      "content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MAX_UINT256 = 2**256 - 1;

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // Divide x * y by the denominator.
            z := div(mul(x, y), denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Mod x by y. Note this will return
            // 0 instead of reverting if y is zero.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // Divide x by y. Note this will return
            // 0 instead of reverting if y is zero.
            r := div(x, y)
        }
    }

    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Add 1 to x * y if x % y > 0. Note this will
            // return 0 instead of reverting if y is zero.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}
"
    },
    "src/libraries/FullMath.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
    /// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
    /// @param a The multiplicand
    /// @param b The multiplier
    /// @param denominator The divisor
    /// @return result The 256-bit result
    /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
    function mulDiv(
        uint256 a,
        uint256 b,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = a * b
            // Compute the product mod 2**256 and mod 2**256 - 1
            // then 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
  

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Non-Fungible, Swap, Yield, Voting, Timelock, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xf35193da8c10e44af10853ba5a3a1a6f7529e39a|verified:true|block:23747616|tx:0x739449ef4f950be43b9201d029530c7a50c7368a782870b5f6e7ad80f04a29aa|first_check:1762528556

Submitted on: 2025-11-07 16:15:58

Comments

Log in to comment.

No comments yet.