InstantRedemption

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": {
    "smart-contracts-public/src/redemption/InstantRedemption.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "@openzeppelin/contracts/access/manager/AccessManaged.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../pricing/PriceRouter.sol";
import "./PayoutTokenRegistry.sol";
import "./RedemptionVault.sol";
import "../token/IShareToken.sol";
import "../compliance/IKYCRegistry.sol";
import "../deposit/FeeVault.sol";
import "../pricing/IRedemptionReserves.sol";

/**
 * @title InstantRedemption
 * @notice Processes redemptions immediately (when no window is open) with daily/global/user limits.
 * @dev Design notes for auditors (Certora):
 * - **Units**: Share price and payout-token price fetched from {PriceRouter} are WAD (1e18).
 *   Payout amounts are converted to token units using the token's decimals.
 * - **Limits**: Limits are derived from `highWatermark` (USD WAD) and expressed as bps; counters
 *   are tracked in *gross payout token units* for the active payout token and reset daily or
 *   when the active payout token changes intra-day (see `dayPayoutToken`).
 * - **Safety**: Reentrancy guarded; authorization via {AccessManaged}. KYC enforced.
 * - **Accounting**: High-watermark is updated on daily reset when capacity increases.
 * - **Events**: Emits detailed events for processing, resets and refills.
 * - **Fees**: If feeBps > 0, fees are paid into a configured {FeeVault} instance via the {recorder} API.
 */
contract InstantRedemption is AccessManaged, ReentrancyGuard, Pausable {
    using SafeERC20 for IERC20;
    using Math for uint256;

    // ============ State ============
    IShareToken public immutable shareToken;
    RedemptionVault public immutable vault;
    PayoutTokenRegistry public immutable payoutTokenRegistry;
    PriceRouter public immutable redemptionPriceRouter;
    IKYCRegistry public immutable kyc;
    address public immutable custodialWallet;

    uint256 public highWatermark; // In WAD format for comparisons
    uint256 public dailyLimitBps = 2000; // 20% default
    uint256 public userLimitBps = 1000; // 10% default
    uint256 public minRedemption = 10e18; // $10 minimum in WAD
    uint256 public maxRedemption = 1_000_000e18; // $1M maximum in WAD
    uint16 public feeBps; // Fee in basis points
    FeeVault public feeVault; // Set per-product

    uint256 public lastDailyReset; // Timestamp of last daily reset

    // Global daily tracking
    uint256 public currentDay;
    uint256 public dailyGrossSpending;

    /// @notice The payout token in effect for the current day's counters
    address public dayPayoutToken;

    /// @notice The redemption reserve calculator
    IRedemptionReserves public redemptionReserves;

    // User-specific daily tracking (properly resets per user)
    struct UserDaily {
        uint256 lastResetDay;
        uint256 spending;
    }

    mapping(address => UserDaily) public userDailyTracking;

    // ============ Constants ============
    uint256 private constant BPS_DENOMINATOR = 10000;
    uint256 private constant MAX_FEE_BPS = 1000; // 10%
    uint256 private constant WAD = 1e18;
    uint256 private constant DAY_DURATION = 1 days;
    uint256 private constant VAULT_LOW_BALANCE_BPS = 2500; // 25% threshold

    // ============ Events ============
    event InstantRedemptionProcessed(
        address indexed user,
        uint256 sharesBurned,
        uint256 grossPayout,
        uint256 feeAmount,
        uint256 netPayout
    );
    event HighWatermarkUpdated(uint256 oldValue, uint256 newValue);
    event LimitsUpdated(uint256 dailyBps, uint256 userBps);
    event RedemptionRangeUpdated(uint256 min, uint256 max);
    event FeeUpdated(uint16 feeBps);
    event DailyReset(uint256 totalCapacity, uint256 newHighWatermark);
    event RefillNeeded(uint256 currentBalance, uint256 threshold, uint256 dailyLimit);

    /// @notice Emitted when the active payout token changes mid-day and we hard-reset daily counters
    event DailyResetByTokenSwitch(
        address indexed previousToken, address indexed newToken, uint256 indexed day
    );
    event FeeVaultSet(address indexed feeVault);

    // ============ Errors ============
    error InvalidAmount();
    error BelowMinimum();
    error ExceedsMaximum();
    error GlobalLimitExceeded();
    error UserLimitExceeded();
    error InsufficientOutput();
    error InvalidConfiguration();
    error ZeroAddress();
    error KYCRequired();
    error InvalidDecimals();

    // ============ Constructor ============

    constructor(
        address _accessManager,
        address _shareToken,
        address _vault,
        address _tokenRegistry,
        address _priceRouter,
        address _kyc,
        address _custodialWallet,
        address _redemptionReserves
    ) AccessManaged(_accessManager) {
        if (_shareToken == address(0)) revert ZeroAddress();
        if (_vault == address(0)) revert ZeroAddress();
        if (_tokenRegistry == address(0)) revert ZeroAddress();
        if (_priceRouter == address(0)) revert ZeroAddress();
        if (_kyc == address(0)) revert ZeroAddress();
        if (_custodialWallet == address(0)) revert ZeroAddress();
        if (_redemptionReserves == address(0)) revert ZeroAddress();
        if (IERC20Metadata(_shareToken).decimals() != 18) revert InvalidDecimals();

        shareToken = IShareToken(_shareToken);
        vault = RedemptionVault(_vault);
        payoutTokenRegistry = PayoutTokenRegistry(_tokenRegistry);
        redemptionPriceRouter = PriceRouter(_priceRouter);
        kyc = IKYCRegistry(_kyc);
        custodialWallet = _custodialWallet;
        lastDailyReset = block.timestamp;
        redemptionReserves = IRedemptionReserves(_redemptionReserves);

        // Pin the current day and the payout token used for this day (if configured)
        currentDay = _getCurrentDay();
        dayPayoutToken = payoutTokenRegistry.activePayoutToken();

        // Roles are now managed by AccessManager

        // Initialize highWatermark from current capacity to prevent DoS
        // This ensures the system is immediately usable after deployment
        uint256 initialCapacity = _calculateCapacityWad();
        if (initialCapacity > 0) {
            highWatermark = initialCapacity;
            emit HighWatermarkUpdated(0, initialCapacity);
        }
    }

    // ============ Redemption Functions ============

    /**
     * @notice Process instant redemption for a user (gateway call)
     * @param user The actual user redeeming
     * @param shares Amount of shares to redeem
     * @param minPayout Minimum payout acceptable
     */
    function redeemFor(address user, uint256 shares, uint256 minPayout) external restricted {
        _processRedemption(user, shares, minPayout);
    }

    /**
     * @notice Internal redemption logic
     * @param user The user performing redemption
     * @param shares Amount of shares to redeem
     * @param minPayout Minimum payout acceptable
     */
    function _processRedemption(address user, uint256 shares, uint256 minPayout)
        internal
        nonReentrant
        whenNotPaused
    {
        if (shares == 0) revert InvalidAmount();
        if (!kyc.isKYCApproved(user)) revert KYCRequired();

        // Refresh daily state unconditionally: handles new day, high watermark updates,
        // and mid-day payout token switches atomically.
        _refreshDailyState();

        // Get payout token (post ensure)
        address payoutToken = payoutTokenRegistry.activePayoutToken();

        // Calculate USD value and payout amount
        (uint256 usdValue, uint256 grossPayout) = _calculateRedemption(shares, payoutToken);

        // Check redemption limits in USD
        if (usdValue < minRedemption) revert BelowMinimum();
        if (usdValue > maxRedemption) revert ExceedsMaximum();

        if (dailyGrossSpending + grossPayout > _getDailyLimit()) revert GlobalLimitExceeded();

        // Update user-specific daily tracking with proper reset
        uint256 userSpending = _getUserDailySpending(user);
        if (userSpending + grossPayout > _getUserLimit()) revert UserLimitExceeded();

        // Update spending
        dailyGrossSpending += grossPayout;

        // Update user tracking with proper day reset
        UserDaily storage userDaily = userDailyTracking[user];
        uint256 today = _getCurrentDay();
        if (today > userDaily.lastResetDay) {
            userDaily.lastResetDay = today;
            userDaily.spending = grossPayout;
        } else {
            userDaily.spending = userSpending + grossPayout;
        }

        // Calculate fee and net payout
        uint256 fee = (grossPayout * feeBps) / BPS_DENOMINATOR;
        uint256 netPayout = grossPayout - fee;

        // Check slippage protection
        if (netPayout < minPayout) revert InsufficientOutput();

        // Transfer shares from user directly (delegation pattern) and burn
        IERC20(address(shareToken)).safeTransferFrom(user, address(this), shares);
        shareToken.burn(address(this), shares);

        // Transfer payout
        vault.withdraw(payoutToken, user, netPayout);

        // Transfer fee if applicable
        if (fee > 0) {
            vault.withdraw(payoutToken, address(feeVault), fee);
            feeVault.recordFee(payoutToken, fee);
        }

        // Check if vault needs refill after withdrawal
        _checkVaultBalance(payoutToken);

        emit InstantRedemptionProcessed(user, shares, grossPayout, fee, netPayout);
    }

    // ============ Admin Functions ============

    /**
     * @notice Update high watermark to match current reserves
     * @dev Operator can set to any value including zero. System self-heals if zero.
     */
    function updateHighWatermark() external restricted {
        uint256 capacityWad = _calculateCapacityWad();
        uint256 oldWatermark = highWatermark;
        highWatermark = capacityWad;
        emit HighWatermarkUpdated(oldWatermark, capacityWad);
    }

    /**
     * @notice Update limit percentages
     */
    function updateLimitPercentages(uint256 _dailyBps, uint256 _userBps) external restricted {
        if (_dailyBps == 0 || _dailyBps > BPS_DENOMINATOR) revert InvalidConfiguration();
        if (_userBps == 0 || _userBps > _dailyBps) revert InvalidConfiguration();

        dailyLimitBps = _dailyBps;
        userLimitBps = _userBps;

        emit LimitsUpdated(_dailyBps, _userBps);
    }

    /**
     * @notice Update redemption range
     */
    function updateRedemptionRange(uint256 _min, uint256 _max) external restricted {
        if (_min == 0 || _max <= _min) revert InvalidConfiguration();

        minRedemption = _min;
        maxRedemption = _max;

        emit RedemptionRangeUpdated(_min, _max);
    }

    /**
     * @notice Update fee bps (0 to 1000 / 10%)
     */
    function updateFee(uint16 _feeBps) external restricted {
        if (_feeBps > MAX_FEE_BPS) revert InvalidConfiguration();
        if (_feeBps > 0 && address(feeVault) == address(0)) revert ZeroAddress();
        feeBps = _feeBps;
        emit FeeUpdated(_feeBps);
    }

    /**
     * @notice Pause redemptions
     */
    function pause() external restricted {
        _pause();
    }

    /**
     * @notice Unpause redemptions
     */
    function unpause() external restricted {
        _unpause();
    }

    // ============ Public View Functions ============

    /**
     * @notice Get total capacity (vault + custodial wallet)
     * @return Total payout tokens available in the system
     */
    function getTotalCapacity() public view returns (uint256) {
        address payoutToken = payoutTokenRegistry.activePayoutToken();
        uint256 vaultBalance = IERC20(payoutToken).balanceOf(address(vault));
        uint256 custodialBalance = IERC20(payoutToken).balanceOf(custodialWallet);
        return vaultBalance + custodialBalance;
    }

    // ============ View Functions ============

    /**
     * @notice Get current global daily redemption info
     */
    function getDailyRedemptions() external view returns (uint256 day, uint256 spending) {
        uint256 today = _getCurrentDay();
        address currentToken = payoutTokenRegistry.activePayoutToken();
        if (today > currentDay || (currentToken != address(0) && currentToken != dayPayoutToken)) {
            return (today, 0);
        }
        return (currentDay, dailyGrossSpending);
    }

    /**
     * @notice Get user's current daily spending
     * @param user User address
     * @return Current day's spending for the user
     */
    function getUserDailySpending(address user) external view returns (uint256) {
        return _getUserDailySpending(user);
    }

    /**
     * @notice Preview redemption without executing
     * @param user User address
     * @param shares Amount of shares
     * @return grossPayout Gross payout amount
     * @return fee Fee amount
     * @return netPayout Net payout after fee
     * @return available Whether redemption would succeed
     */
    function previewRedemption(address user, uint256 shares)
        external
        view
        returns (uint256 grossPayout, uint256 fee, uint256 netPayout, bool available)
    {
        // Check basic requirements
        if (shares == 0 || !kyc.isKYCApproved(user)) {
            return (0, 0, 0, false);
        }

        // Get payout token and calculate amounts
        address payoutToken = payoutTokenRegistry.activePayoutToken();
        (uint256 usdValue, uint256 gross) = _calculateRedemption(shares, payoutToken);

        // Check limits
        if (usdValue < minRedemption || usdValue > maxRedemption) {
            return (0, 0, 0, false);
        }

        // Check daily limits (simulate reset if needed)
        bool isNewDayOrToken = currentDay < _getCurrentDay()
            || (
                payoutTokenRegistry.activePayoutToken() != address(0)
                    && payoutTokenRegistry.activePayoutToken() != dayPayoutToken
            );

        uint256 todayGlobalSpending = isNewDayOrToken ? 0 : dailyGrossSpending;
        if (todayGlobalSpending + gross > _getDailyLimit()) {
            return (0, 0, 0, false);
        }

        uint256 userSpending = _getUserDailySpending(user);
        if (userSpending + gross > _getUserLimit()) {
            return (0, 0, 0, false);
        }

        // Calculate fee and net
        uint256 feeAmount = (gross * feeBps) / BPS_DENOMINATOR;
        uint256 net = gross - feeAmount;

        return (gross, feeAmount, net, true);
    }

    /**
     * @notice Get the vault low balance threshold (25% of daily limit)
     * @return Threshold amount in payout token decimals
     */
    function getVaultLowBalanceThreshold() public view returns (uint256) {
        uint256 dailyLimit = _getDailyLimit();
        return (dailyLimit * VAULT_LOW_BALANCE_BPS) / BPS_DENOMINATOR;
    }

    /**
     * @notice Get remaining daily limit
     * @return Amount remaining in payout token decimals
     */
    function getRemainingDailyLimit() public view returns (uint256) {
        bool isNewDayOrToken = currentDay < _getCurrentDay()
            || (
                payoutTokenRegistry.activePayoutToken() != address(0)
                    && payoutTokenRegistry.activePayoutToken() != dayPayoutToken
            );

        uint256 todaySpending = isNewDayOrToken ? 0 : dailyGrossSpending;
        uint256 dailyLimit = _getDailyLimit();
        return dailyLimit > todaySpending ? dailyLimit - todaySpending : 0;
    }

    /**
     * @notice Get remaining user limit for a specific user
     * @param user The user to check
     * @return Amount remaining in payout token decimals
     */
    function getRemainingUserLimit(address user) public view returns (uint256) {
        uint256 userSpending = _getUserDailySpending(user);
        uint256 userLimit = _getUserLimit();
        return userLimit > userSpending ? userLimit - userSpending : 0;
    }

    /**
     * @notice Get comprehensive redemption status for a user
     * @param user The user to check
     * @return kycApproved Whether user is KYC approved
     * @return remainingDaily Remaining daily limit in payout token decimals
     * @return remainingUser Remaining user limit in payout token decimals
     * @return minRedemptionAmount Minimum redemption amount in WAD
     * @return maxRedemptionAmount Maximum redemption amount in WAD
     * @return currentFee Current fee in basis points
     */
    function getRedemptionStatus(address user)
        public
        view
        returns (
            bool kycApproved,
            uint256 remainingDaily,
            uint256 remainingUser,
            uint256 minRedemptionAmount,
            uint256 maxRedemptionAmount,
            uint256 currentFee
        )
    {
        kycApproved = kyc.isKYCApproved(user);
        remainingDaily = getRemainingDailyLimit();
        remainingUser = getRemainingUserLimit(user);
        minRedemptionAmount = minRedemption;
        maxRedemptionAmount = maxRedemption;
        currentFee = feeBps;
    }

    /**
     * @notice Set the FeeVault used to collect protocol fees on claims
     */
    function setFeeVault(address _feeVault) external restricted {
        if (_feeVault == address(0)) revert ZeroAddress();
        feeVault = FeeVault(_feeVault);
        emit FeeVaultSet(_feeVault);
    }

    // ============ Internal Functions ============

    /**
     * @notice Calculate redemption amounts
     */
    function _calculateRedemption(uint256 shares, address payoutToken)
        private
        view
        returns (uint256 usdValue, uint256 grossPayout)
    {
        // Get prices from router
        uint256 sharePrice = redemptionPriceRouter.getPrice(address(shareToken));
        uint256 payoutPrice = redemptionPriceRouter.getPrice(payoutToken);

        // Calculate USD value of shares
        usdValue = Math.mulDiv(shares, sharePrice, WAD);

        // Calculate payout amount in token decimals
        uint8 payoutDecimals = IERC20Metadata(payoutToken).decimals();
        if (payoutDecimals > 24) revert InvalidDecimals();
        grossPayout = Math.mulDiv(usdValue, 10 ** payoutDecimals, payoutPrice);
    }

    /**
     * @notice Refresh daily state atomically.
     * @dev Consolidates daily reset and token switch handling.
     *
     * Invariants after return:
     * - `currentDay` equals `_getCurrentDay()`.
     * - `dayPayoutToken` matches `payoutTokenRegistry.activePayoutToken()`.
     * - `dailyGrossSpending` is zeroed when the UTC day or payout token changed.
     *
     * Emits:
     * - `DailyReset` when a new day starts (also updates `highWatermark` if capacity grew).
     * - `DailyResetByTokenSwitch` when the token changes mid-day.
     *
     * Should be called unconditionally at the start of `_processRedemption()`.
     */
    function _refreshDailyState() private {
        uint256 today = _getCurrentDay();
        uint256 lastResetDay = lastDailyReset / DAY_DURATION;
        address currentToken = payoutTokenRegistry.activePayoutToken();

        // New day or initialization path
        if (today > lastResetDay || highWatermark == 0) {
            // Calculate current capacity in USD WAD
            uint256 totalCapacityWad = _calculateCapacityWad();

            // Update high watermark if capacity increased
            uint256 oldWatermark = highWatermark;
            if (totalCapacityWad > highWatermark) {
                highWatermark = totalCapacityWad;
                emit HighWatermarkUpdated(oldWatermark, totalCapacityWad);
            }

            // Reset day timestamp & pin today's payout token
            lastDailyReset = block.timestamp;
            currentDay = today;
            dayPayoutToken = currentToken;

            // Reset global daily counters for the new day
            dailyGrossSpending = 0;

            emit DailyReset(totalCapacityWad, highWatermark);
            return;
        }

        // Same day: if payout token switched, hard reset today's counters
        if (currentToken != address(0) && currentToken != dayPayoutToken) {
            address previousToken = dayPayoutToken;
            dayPayoutToken = currentToken;
            dailyGrossSpending = 0;
            emit DailyResetByTokenSwitch(previousToken, currentToken, today);
        }

        // Safety: if currentDay lags today (shouldn't happen), sync it
        if (today > currentDay) {
            currentDay = today;
        }
    }

    /**
     * @notice Get user's daily spending with proper reset
     * @param user User address
     * @return Current spending amount
     */
    function _getUserDailySpending(address user) private view returns (uint256) {
        UserDaily storage userDaily = userDailyTracking[user];
        uint256 today = _getCurrentDay();
        address currentToken = payoutTokenRegistry.activePayoutToken();

        // Treat token mismatch like a new day for all users
        if (
            today > userDaily.lastResetDay
                || (currentToken != address(0) && currentToken != dayPayoutToken)
        ) {
            return 0;
        }
        return userDaily.spending;
    }

    /**
     * @notice Get current day index
     */
    function _getCurrentDay() private view returns (uint256) {
        return block.timestamp / DAY_DURATION;
    }

    /**
     * @notice Calculate current total capacity in USD WAD
     * @dev Converts token balances to USD value using price from router
     */
    function _calculateCapacityWad() private view returns (uint256) {
        address payoutToken = payoutTokenRegistry.activePayoutToken();
        if (payoutToken == address(0)) return 0;

        return redemptionReserves.calculateTotalReserveValue(address(vault), custodialWallet);
    }

    /**
     * @notice Get daily limit in payout token decimals
     * @dev Converts USD limit to token amount using current price
     */
    function _getDailyLimit() private view returns (uint256) {
        address payoutToken = payoutTokenRegistry.activePayoutToken();
        uint8 decimals = IERC20Metadata(payoutToken).decimals();
        if (decimals > 24) revert InvalidDecimals();

        // Calculate limit in USD WAD
        uint256 limitInUsdWad = (highWatermark * dailyLimitBps) / BPS_DENOMINATOR;

        // Get payout token price in USD WAD
        uint256 payoutPriceWad = redemptionPriceRouter.getPrice(payoutToken);

        // Convert USD limit to token amount
        // tokens = (USD * 10^decimals) / price
        return Math.mulDiv(limitInUsdWad, 10 ** decimals, payoutPriceWad);
    }

    /**
     * @notice Get user limit in payout token decimals
     * @dev Converts USD limit to token amount using current price
     */
    function _getUserLimit() private view returns (uint256) {
        address payoutToken = payoutTokenRegistry.activePayoutToken();
        uint8 decimals = IERC20Metadata(payoutToken).decimals();
        if (decimals > 24) revert InvalidDecimals();

        // Calculate limit in USD WAD
        uint256 limitInUsdWad = (highWatermark * userLimitBps) / BPS_DENOMINATOR;

        // Get payout token price in USD WAD
        uint256 payoutPriceWad = redemptionPriceRouter.getPrice(payoutToken);

        // Convert USD limit to token amount
        // tokens = (USD * 10^decimals) / price
        return Math.mulDiv(limitInUsdWad, 10 ** decimals, payoutPriceWad);
    }

    /**
     * @notice Check vault balance and emit warning if below threshold
     */
    function _checkVaultBalance(address payoutToken) private {
        uint256 vaultBalance = IERC20(payoutToken).balanceOf(address(vault));
        uint256 threshold = getVaultLowBalanceThreshold();
        uint256 dailyLimit = _getDailyLimit();

        if (vaultBalance < threshold) {
            emit RefillNeeded(vaultBalance, threshold, dailyLimit);
        }
    }
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/access/manager/AccessManaged.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (access/manager/AccessManaged.sol)

pragma solidity ^0.8.20;

import {AuthorityUtils} from "./AuthorityUtils.sol";
import {IAccessManager} from "./IAccessManager.sol";
import {IAccessManaged} from "./IAccessManaged.sol";
import {Context} from "../../utils/Context.sol";

/**
 * @dev This contract module makes available a {restricted} modifier. Functions decorated with this modifier will be
 * permissioned according to an "authority": a contract like {AccessManager} that follows the {IAuthority} interface,
 * implementing a policy that allows certain callers to access certain functions.
 *
 * IMPORTANT: The `restricted` modifier should never be used on `internal` functions, judiciously used in `public`
 * functions, and ideally only used in `external` functions. See {restricted}.
 */
abstract contract AccessManaged is Context, IAccessManaged {
    address private _authority;

    bool private _consumingSchedule;

    /**
     * @dev Initializes the contract connected to an initial authority.
     */
    constructor(address initialAuthority) {
        _setAuthority(initialAuthority);
    }

    /**
     * @dev Restricts access to a function as defined by the connected Authority for this contract and the
     * caller and selector of the function that entered the contract.
     *
     * [IMPORTANT]
     * ====
     * In general, this modifier should only be used on `external` functions. It is okay to use it on `public`
     * functions that are used as external entry points and are not called internally. Unless you know what you're
     * doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security
     * implications! This is because the permissions are determined by the function that entered the contract, i.e. the
     * function at the bottom of the call stack, and not the function where the modifier is visible in the source code.
     * ====
     *
     * [WARNING]
     * ====
     * Avoid adding this modifier to the https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function[`receive()`]
     * function or the https://docs.soliditylang.org/en/v0.8.20/contracts.html#fallback-function[`fallback()`]. These
     * functions are the only execution paths where a function selector cannot be unambiguously determined from the calldata
     * since the selector defaults to `0x00000000` in the `receive()` function and similarly in the `fallback()` function
     * if no calldata is provided. (See {_checkCanCall}).
     *
     * The `receive()` function will always panic whereas the `fallback()` may panic depending on the calldata length.
     * ====
     */
    modifier restricted() {
        _checkCanCall(_msgSender(), _msgData());
        _;
    }

    /// @inheritdoc IAccessManaged
    function authority() public view virtual returns (address) {
        return _authority;
    }

    /// @inheritdoc IAccessManaged
    function setAuthority(address newAuthority) public virtual {
        address caller = _msgSender();
        if (caller != authority()) {
            revert AccessManagedUnauthorized(caller);
        }
        if (newAuthority.code.length == 0) {
            revert AccessManagedInvalidAuthority(newAuthority);
        }
        _setAuthority(newAuthority);
    }

    /// @inheritdoc IAccessManaged
    function isConsumingScheduledOp() public view returns (bytes4) {
        return _consumingSchedule ? this.isConsumingScheduledOp.selector : bytes4(0);
    }

    /**
     * @dev Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the
     * permissions set by the current authority.
     */
    function _setAuthority(address newAuthority) internal virtual {
        _authority = newAuthority;
        emit AuthorityUpdated(newAuthority);
    }

    /**
     * @dev Reverts if the caller is not allowed to call the function identified by a selector. Panics if the calldata
     * is less than 4 bytes long.
     */
    function _checkCanCall(address caller, bytes calldata data) internal virtual {
        (bool immediate, uint32 delay) = AuthorityUtils.canCallWithDelay(
            authority(),
            caller,
            address(this),
            bytes4(data[0:4])
        );
        if (!immediate) {
            if (delay > 0) {
                _consumingSchedule = true;
                IAccessManager(authority()).consumeScheduledOp(caller, data);
                _consumingSchedule = false;
            } else {
                revert AccessManagedUnauthorized(caller);
            }
        }
    }
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

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

    uint256 private _status;

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

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

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

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = 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 _status == ENTERED;
    }
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/utils/Pausable.sol": {
      "content": "// 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());
    }
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol": {
      "content": "// 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);
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity >=0.6.2;

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

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.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 {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @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 {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @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 _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @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 _callOptionalReturnBool(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @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 {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @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 rely 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 rely 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}.
     * Opposedly, 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 high-level call (i.e. a regular function call to a contract), 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 data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), 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 data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "smart-contracts-public/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Return the 512-bit addition of two uint256.
     *
     * The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
     */
    function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        assembly ("memory-safe") {
            low := add(a, b)
            high := lt(low, a)
        }
    }

    /**
     * @dev Return the 512-bit multiplication of two uint256.
     *
     * The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
     */
    function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
        // 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
        // the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
        // variables such that product = high * 2²⁵⁶ + low.
        assembly ("memory-safe") {
            let mm := mulmod(a, b, not(0))
            low := mul(a, b)
            high := sub(sub(mm, low), lt(mm, low))
        }
    }

    /**
     * @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a + b;
            success = c >= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a - b;
            success = c <= a;
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            uint256 c = a * b;
            assembly ("memory-safe") {
                // Only true when the multiplication doesn't overflow
                // (c / a == b) || (a == 0)
                success := or(eq(div(c, a), b), iszero(a))
            }
            // equivalent to: success ? c : 0
            result = c * SafeCast.toUint(success);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `DIV` opcode returns zero when the denominator is 0.
                result := div(a, b)
            }
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
        unchecked {
            success = b > 0;
            assembly ("memory-safe") {
                // The `MOD` opcode returns zero when the denominator is 0.
                result := mod(a, b)
            }
        }
    }

    /**
     * @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryAdd(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
     */
    function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
        (, uint256 result) = trySub(a, b);
        return result;
    }

    /**
     * @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
     */
    function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
        (bool success, uint256 result) = tryMul(a, b);
        return ternary(success, result, type(uint256).max);
    }

    /**
     * @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
     *
     * IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
     * However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
     * one branch when needed, making this function more expensive.
     */
    function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
        unchecked {
            // branchless ternary works because:
            // b ^ (a ^ b) == a
            // b ^ 0 == b
            return b ^ ((a ^ b) * SafeCast.toUint(condition));
        }
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a > b, a, b);
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return ternary(a < b, a, b);
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            Panic.panic(Panic.DIVISION_BY_ZERO);
        }

        // The following calculation ensures accurate ceiling division without overflow.
        // Since a is non-zero, (a - 1) / b will not overflow.
        // The largest possible result occurs when (a - 1) / b is type(uint256).max,
        // but the largest value we can obtain is type(uint256).max - 1, which happens
        // when a = type(uint256).max and b = 1.
        unchecked {
            return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
        }
    }

    /**
     * @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
     * denominator == 0.
     *
     * Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
     * Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);

            // Handle non-overflow cases, 256 by 256 division.
            if (high == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return low / denominator;
            }

            // Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
            if (denominator <= high) {
                Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
            }

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

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

                // Subtract 256 bit number from 512 bit number.
                high := sub(high, gt(remainder, low))
                low := sub(low, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator.
            // Always >= 1. See https://cs.stackexchange.com/q/138556/92363.

            uint256 twos = denominator & (0 - denominator);
            assembly ("memory-safe") {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [high low] by twos.
                low := div(low, twos)

                // Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from high into low.
            low |= high * twos;

            // Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
            // that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv ≡ 1 mod 2⁴.
            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⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
            inverse *= 2 - denominator * inverse; // inverse mod 2³²
            inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
            inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
            inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶

            // 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²⁵⁶. Since the preconditions guarantee that the outcome is
            // less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
            // is no longer required.
            result = low * inverse;
            return result;
        }
    }

    /**
     * @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
    }

    /**
     * @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
     */
    function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
        unchecked {
            (uint256 high, uint256 low) = mul512(x, y);
            if (high >= 1 << n) {
                Panic.panic(Panic.UNDER_OVERFLOW);
            }
            return (high << (256 - n)) | (low >> n);
        }
    }

    /**
     * @dev Calculates x * y >> n with full precision, following the selected rounding direction.
     */
    function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
        return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
    }

    /**
     * @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
     *
     * If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
     * If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
     *
     * If the input value is not inversible, 0 is returned.
     *
     * NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
     * inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
     */
    function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
        unchecked {
            if (n == 0) return 0;

            // The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
            // Used to compute integers x and y such that: ax + ny = gcd(a, n).
            // When the gcd is 1, then the inverse of a modulo n exists and it's x.
            // ax + ny = 1
            // ax = 1 + (-y)n
            // ax ≡ 1 (mod n) # x is the inverse of a modulo n

            // If the remainder is 0 the gcd is n right away.
            uint256 remainder = a % n;
            uint256 gcd = n;

            // Therefore the initial coefficients are:
            // ax + ny = gcd(a, n) = n
            // 0a + 1n = n
            int256 x = 0;
            int256 y = 1;

            while (remainder != 0) {
                uint256 quotient = gcd / remainder;

                (gcd, remainder) = (
                    // The old remainder is the next gcd to try.
                    remainder,
                    // Compute the next remainder.
                    // Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
                    // where gcd is at most n (capped to type(uint256).max)
                    gcd - remainder * quotient
                );

                (x, y) = (
                    // Increment the coefficient of a.
                    y,
                    // Decrement the coefficient of n.
                    // Can overflow, but the result is casted to uint256 so that the
                    // next value of y is "wrapped around" to a value betw

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Pausable, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xa31deebb3680a3007120e74bcbdf4df36f042a40|verified:true|block:23477812|tx:0x99ca13d0e2b67b008a53e2fc7c0f02747770644b6f224088f8d63a841a61cf43|first_check:1759315004

Submitted on: 2025-10-01 12:36:45

Comments

Log in to comment.

No comments yet.