USDOExpressV2

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/extensions/USDOExpressV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
import "./USDOExpressPausable.sol";
import "./USDOMintRedeemLimiterV2.sol";
import "./DoubleQueueModified.sol";

import {IUSDO} from "../interfaces/IUSDO.sol";
import {ICUSDO} from "../interfaces/ICUSDO.sol";
import "../interfaces/IRedemption.sol";
import "../interfaces/IAssetRegistry.sol";

enum TxType {
    MINT,
    REDEEM,
    INSTANT_REDEEM
}

contract USDOExpressV2 is UUPSUpgradeable, AccessControlUpgradeable, USDOExpressPausable, USDOMintRedeemLimiter {
    using MathUpgradeable for uint256;
    using DoubleQueueModified for DoubleQueueModified.BytesDeque;

    // Roles
    bytes32 public constant MULTIPLIER_ROLE = keccak256("MULTIPLIER_ROLE");
    bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");
    bytes32 public constant WHITELIST_ROLE = keccak256("WHITELIST_ROLE");
    bytes32 public constant UPGRADE_ROLE = keccak256("UPGRADE_ROLE");
    bytes32 public constant MAINTAINER_ROLE = keccak256("MAINTAINER_ROLE");
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");

    // APY in base points, scaled by 1e4 (e.g., 100 = 1%)
    uint256 public _apy; // 500 => 5%

    // fee rate for mint, scaled by 1e4, e.g., 100 stands for 1%
    uint256 public _mintFeeRate;
    uint256 public _redeemFeeRate;

    // constants for base points and scaling
    uint256 private constant _BPS_BASE = 1e4;
    uint256 private constant _BASE = 1e18;

    // daily bonus multiplier increment, scaled by 1e18
    uint256 public _increment;

    // The last time the bonus multiplier was updated
    uint256 public _lastUpdateTS;

    // Time buffer for the operator to update the bonus multiplier
    uint256 public _timeBuffer;

    // core token addresses
    IUSDO public _usdo;
    address public _usdc;

    // #previous: _tbill
    address public RESERVE1;

    // the address to receive the tokens
    address public _treasury;
    // the address to receive the fees
    address public _feeTo;

    // Asset registry for pluggable asset management  #previous: _buidl;
    IAssetRegistry public _assetRegistry;

    // for instant redeem - pluggable redemption contract, #previous: _buidlRedemption
    IRedemption public _redemptionContract;

    // cUSDO contract , #previous: _buidlTreasury
    ICUSDO public _cusdo;

    // check if the user has deposited before
    mapping(address => bool) public _firstDeposit;

    // kyc list
    mapping(address => bool) public _kycList;

    // Queue for redemption requests
    DoubleQueueModified.BytesDeque private _redemptionQueue;

    // Track redemption amounts for users in the queue
    mapping(address => uint256) private _redemptionInfo;

    // fee rate for instant redeem, scaled by 1e4, e.g., 100 stands for 1%
    uint256 public _instantRedeemFeeRate;

    // Events
    event UpdateAPY(uint256 apy, uint256 increment);
    event UpdateCusdo(address cusdo);
    event UpdateMintFeeRate(uint256 fee);
    event UpdateRedeemFeeRate(uint256 fee);
    event UpdateInstantRedeemFee(uint256 fee);
    event UpdateTreasury(address treasury);
    event UpdateFeeTo(address feeTo);
    event UpdateTimeBuffer(uint256 timeBuffer);
    event InstantMint(
        address indexed underlying,
        address indexed from,
        address indexed to,
        uint256 reqAmt,
        uint256 receiveAmt,
        uint256 fee
    );
    event InstantMintAndWrap(
        address indexed underlying,
        address indexed from,
        address indexed to,
        uint256 reqAmt,
        uint256 usdoAmt,
        uint256 cusdoAmt,
        uint256 fee
    );
    event USDOKycGranted(address[] addresses);
    event USDOKycRevoked(address[] addresses);

    event InstantRedeem(
        address indexed from,
        address indexed to,
        uint256 reqAmt,
        uint256 receiveAmt,
        uint256 fee,
        uint256 payout,
        uint256 usycFee,
        uint256 minUsdcOut
    );
    event ManualRedeem(address indexed from, uint256 reqAmt, uint256 receiveAmt, uint256 fee);
    event UpdateFirstDeposit(address indexed account, bool flag);

    // Queue-related events
    event AddToRedemptionQueue(address indexed from, address indexed to, uint256 usdoAmt, bytes32 id);
    event ProcessRedeem(
        address indexed from,
        address indexed to,
        uint256 usdoAmt,
        uint256 usdcAmt,
        uint256 fee,
        bytes32 id
    );
    event ProcessRedemptionQueue(uint256 totalRedeemAssets, uint256 totalBurnUsdo, uint256 totalFees);
    event ProcessRedemptionCancel(address indexed from, address indexed to, uint256 usdoAmt, bytes32 id);
    event Cancel(uint256 len, uint256 totalUsdo);
    event SetRedemption(address redemptionContract);
    event AssetRegistryUpdated(address indexed newRegistry);
    event OffRamp(address indexed to, uint256 amount);

    error USDOExpressTooEarly(uint256 amount);
    error USDOExpressZeroAddress();
    error USDOExpressTokenNotSupported(address token);
    error USDOExpressReceiveUSDCFailed(uint256 amount, uint256 received);

    error MintLessThanMinimum(uint256 amount, uint256 minimum);
    error TotalSupplyCapExceeded();
    error FirstDepositLessThanRequired(uint256 amount, uint256 minimum);
    error USDOExpressNotInKycList(address from, address to);
    error USDOExpressInvalidInput(uint256 input);
    error USDOExpressInsufficientLiquidity(uint256 required, uint256 available);
    error InsufficientOutput(uint256 received, uint256 minimum);

    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initializes the contract.
     * @param usdo Address of the USDO contract.
     * @param usdc Address of the USDC contract.
     */
    function initialize(
        address usdo,
        address cusdo,
        address usdc,
        address treasury,
        address feeTo,
        address maintainer,
        address operator,
        address admin,
        address assetRegistry,
        USDOMintRedeemLimiterCfg memory cfg
    ) external initializer {
        __AccessControl_init();
        __UUPSUpgradeable_init();

        _usdo = IUSDO(usdo);
        _cusdo = ICUSDO(cusdo);
        _usdc = usdc;
        _treasury = treasury;
        _feeTo = feeTo;
        _assetRegistry = IAssetRegistry(assetRegistry);

        __USDOMintRedeemLimiter_init(
            cfg.mintMinimum,
            cfg.mintLimit,
            cfg.mintDuration,
            cfg.redeemMinimum,
            cfg.redeemLimit,
            cfg.redeemDuration,
            cfg.firstDepositAmount
        );

        // Grant roles
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(UPGRADE_ROLE, admin);

        _grantRole(WHITELIST_ROLE, maintainer);
        _grantRole(MAINTAINER_ROLE, maintainer);

        _grantRole(MULTIPLIER_ROLE, operator);
        _grantRole(PAUSE_ROLE, operator);
        _grantRole(OPERATOR_ROLE, operator);
    }

    function _authorizeUpgrade(address) internal override onlyRole(UPGRADE_ROLE) {}

    /**
     * @notice Updates the APY.
     * @dev This function can only be called by the owner.
     * @param newAPY The new APY value in base points, apy example: 514 = 5.14%
     */
    function updateAPY(uint256 newAPY) external onlyRole(MAINTAINER_ROLE) {
        _apy = newAPY;

        // 140821917808219
        // 140821917808219.1780821918
        _increment = newAPY.mulDiv(_BASE, 365) / (_BPS_BASE);
        emit UpdateAPY(newAPY, _increment);
    }

    /**
     * @notice Update the cUSDO contract.
     * @param cusdo The address of the cUSDO contract.
     */
    function updateCusdo(address cusdo) external onlyRole(MAINTAINER_ROLE) {
        if (cusdo == address(0)) revert USDOExpressZeroAddress();
        _cusdo = ICUSDO(cusdo);
        emit UpdateCusdo(cusdo);
    }

    /**
     * @notice Update the asset registry address
     * @param newRegistry The new asset registry address
     */
    function setAssetRegistry(address newRegistry) external onlyRole(MAINTAINER_ROLE) {
        if (newRegistry == address(0)) revert USDOExpressZeroAddress();
        _assetRegistry = IAssetRegistry(newRegistry);
        emit AssetRegistryUpdated(newRegistry);
    }

    /**
     *@notice Will be used to update the bonus multiplier in the USDO contract.
     */
    function addBonusMultiplier() external onlyRole(MULTIPLIER_ROLE) {
        if (_lastUpdateTS != 0) {
            if (block.timestamp < _lastUpdateTS + _timeBuffer) revert USDOExpressTooEarly(block.timestamp);
        }

        _usdo.addBonusMultiplier(_increment);
        _lastUpdateTS = block.timestamp;
    }

    /**
     * @notice Set the time buffer for the operator to update the bonus multiplier
     * @dev Can only be called by the contract operator
     * @param timeBuffer Time buffer in seconds
     */
    function updateTimeBuffer(uint256 timeBuffer) external onlyRole(MAINTAINER_ROLE) {
        _timeBuffer = timeBuffer;
        emit UpdateTimeBuffer(timeBuffer);
    }

    /**
     * @notice Updates the fee percentage.
     * @dev This function can only be called by the operator.
     * @param fee The new fee percentage in base points.
     */
    function updateMintFee(uint256 fee) external onlyRole(MAINTAINER_ROLE) {
        _mintFeeRate = fee;
        emit UpdateMintFeeRate(fee);
    }

    /**
     * @notice Updates the fee percentage for redeem.
     * @dev This function can only be called by the operator.
     * @param fee The new fee percentage in base points.
     */
    function updateRedeemFee(uint256 fee) external onlyRole(MAINTAINER_ROLE) {
        _redeemFeeRate = fee;
        emit UpdateRedeemFeeRate(fee);
    }

    /**
     * @notice Updates the fee percentage for instant redeem.
     * @dev This function can only be called by the operator.
     * @param fee The new fee percentage in base points.
     */
    function updateInstantRedeemFee(uint256 fee) external onlyRole(MAINTAINER_ROLE) {
        _instantRedeemFeeRate = fee;
        emit UpdateInstantRedeemFee(fee);
    }

    /**
     * @notice Allows a whitelisted user to perform an instant mint.
     * @param underlying The address of the token to mint USDO from.
     * @param to The address to mint the USDO to.
     * @param amt The supplied amount of the underlying token.
     */
    function instantMint(address underlying, address to, uint256 amt) external whenNotPausedMint {
        address from = _msgSender();
        if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);

        (uint256 usdoAmtCurr, uint256 fee) = _instantMintInternal(underlying, from, to, to, amt);
        emit InstantMint(underlying, from, to, amt, usdoAmtCurr, fee);
    }

    /**
     * @notice Allows a whitelisted user to perform an instant mint.
     * @param underlying The address of the token to mint USDO from.
     * @param to The address to mint the USDO to.
     * @param amt The supplied amount of the underlying token.
     */
    function instantMintAndWrap(address underlying, address to, uint256 amt) external whenNotPausedMint {
        address from = _msgSender();
        if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);

        (uint256 usdoAmtCurr, uint256 fee) = _instantMintInternal(underlying, from, address(this), to, amt);

        _usdo.approve(address(_cusdo), usdoAmtCurr);
        uint256 cusdoAmt = _cusdo.deposit(usdoAmtCurr, to);

        emit InstantMint(underlying, from, to, amt, usdoAmtCurr, fee);
        emit InstantMintAndWrap(underlying, from, to, amt, usdoAmtCurr, cusdoAmt, fee);
    }

    /**
     * @notice Allows a whitelisted user to perform an instant redeem with slippage protection.
     * @dev Will convert USDO to USDC using the configured redemption contract.
     * @param to The address to redeem the USDC to.
     * @param amt The requested amount of USDO to redeem.
     * @param minUsdcOut Minimum USDC amount to receive (slippage protection).
     */
    function instantRedeemSelf(address to, uint256 amt, uint256 minUsdcOut) external whenNotPausedRedeem {
        address from = _msgSender();
        if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);
        _checkRedeemLimit(amt);

        // 1. burn the USDO
        _usdo.burn(from, amt);

        // 2. calculate the USDO amount into USDC and request redemption
        uint256 usdcNeeded = convertToUnderlying(_usdc, amt);

        // 3. redeem through the redemption contract and process
        (uint256 payout, uint256 redemptionFee, ) = _redemptionContract.redeemFor(from, usdcNeeded);

        // 4. calculate fees
        uint256 feeInUsdc = txsFee(usdcNeeded, TxType.INSTANT_REDEEM);
        uint256 usdcToUser = payout - feeInUsdc;

        // 5. slippage protection
        if (minUsdcOut > 0 && usdcToUser < minUsdcOut) {
            revert InsufficientOutput(usdcToUser, minUsdcOut);
        }

        // 6. transfer USDC fee to feeTo and the rest to user
        _distributeUsdc(to, usdcToUser, feeInUsdc);
        emit InstantRedeem(from, to, amt, usdcToUser, feeInUsdc, payout, redemptionFee, minUsdcOut);
    }

    /**
     * @notice Queue a redemption request for manual processing.
     * @dev The redemption request will be put into a queue and processed later when sufficient USDC is available.
     * @param to The address to redeem the USDC to.
     * @param amt The requested amount of USDO to redeem.
     */
    function redeemRequest(address to, uint256 amt) external whenNotPausedRedeem {
        address from = _msgSender();
        if (!_kycList[from] || !_kycList[to]) revert USDOExpressNotInKycList(from, to);
        _checkRedeemLimit(amt);

        // Burn USDO from the user
        _usdo.burn(from, amt);
        _redemptionInfo[to] += amt;

        bytes32 id = keccak256(abi.encode(from, to, amt, block.timestamp, _redemptionQueue.length()));
        bytes memory data = abi.encode(from, to, amt, id);
        _redemptionQueue.pushBack(data);

        emit AddToRedemptionQueue(from, to, amt, id);
    }

    /**
     * @notice The redemption request will be processed manually.
     * @param amt The requested amount of USDO to redeem.
     */
    function redeem(uint256 amt) external whenNotPausedRedeem {
        address from = _msgSender();
        if (!_kycList[from]) revert USDOExpressNotInKycList(from, from);

        _checkRedeemLimit(amt);
        _usdo.burn(from, amt);

        (uint256 feeAmt, uint256 usdcAmt, ) = previewRedeem(amt, false);
        emit ManualRedeem(from, amt, usdcAmt, feeAmt);
    }

    /**
     * @notice Cancel the first _len redemption requests in the queue.
     * @dev Only operators can call this function.
     * @param _len The length of the cancel requests.
     */
    function cancel(uint256 _len) external onlyRole(MAINTAINER_ROLE) {
        if (_redemptionQueue.empty()) revert USDOExpressInvalidInput(0);
        if (_len > _redemptionQueue.length()) revert USDOExpressInvalidInput(_len);

        uint256 totalUsdo;
        uint256 originalLen = _len;

        while (_len > 0) {
            bytes memory data = _redemptionQueue.popFront();

            (address sender, address receiver, uint256 usdoAmt, bytes32 prevId) = _decodeData(data);

            unchecked {
                totalUsdo += usdoAmt;
                _redemptionInfo[receiver] -= usdoAmt;
                _len--;
            }

            // Mint USDO back to the user
            _usdo.mint(sender, usdoAmt);
            emit ProcessRedemptionCancel(sender, receiver, usdoAmt, prevId);
        }
        emit Cancel(originalLen, totalUsdo);
    }

    /**
     * @notice Process the redemption queue.
     * @dev Only operators can call this function.
     * @param _len The length of the queue to process, 0 means process all.
     */
    function processRedemptionQueue(uint256 _len) external onlyRole(OPERATOR_ROLE) {
        uint256 length = _redemptionQueue.length();
        if (length == 0) revert USDOExpressInvalidInput(0);
        if (_len > length) revert USDOExpressInvalidInput(_len);
        if (_len == 0) _len = length;

        uint256 totalRedeemAssets;
        uint256 totalBurnUsdo;
        uint256 totalFees;

        for (uint count = 0; count < _len; ) {
            bytes memory data = _redemptionQueue.front();
            (address sender, address receiver, uint256 usdoAmt, bytes32 prevId) = _decodeData(data);

            if (!_kycList[sender] || !_kycList[receiver]) revert USDOExpressNotInKycList(sender, receiver);

            // Convert USDO to USDC amount
            uint256 usdcAmt = convertToUnderlying(_usdc, usdoAmt);

            // Check if we have enough USDC liquidity
            uint256 availableLiquidity = getTokenBalance(_usdc);
            if (usdcAmt > availableLiquidity) {
                break; // Stop processing if not enough liquidity
            }

            // Calculate fees
            uint256 feeInUsdc = txsFee(usdcAmt, TxType.REDEEM);
            uint256 usdcToUser = usdcAmt - feeInUsdc;

            // Remove from queue
            _redemptionQueue.popFront();

            unchecked {
                ++count;
                totalRedeemAssets += usdcToUser;
                totalBurnUsdo += usdoAmt;
                totalFees += feeInUsdc;
                _redemptionInfo[receiver] -= usdoAmt;
            }

            _distributeUsdc(receiver, usdcToUser, feeInUsdc);
            emit ProcessRedeem(sender, receiver, usdoAmt, usdcToUser, feeInUsdc, prevId);
        }

        emit ProcessRedemptionQueue(totalRedeemAssets, totalBurnUsdo, totalFees);
    }

    function _distributeUsdc(address to, uint256 usdcToUser, uint256 fee) private {
        if (fee > 0) SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_usdc), _feeTo, fee);
        SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_usdc), to, usdcToUser);
    }

    /**
     * @notice Previews the instant redeem amounts.
     * @dev // USDC (6 decimals) to USDO (18 decimals), to scale to USDCO: amount * (10 ** (usdoDecimals - usdcDecimals));
     * @param token The token to provide the value in.
     * @param amt The amount of the token to convert.
     * @return usdoAmt The value of the token in USDO.
     */
    function convertFromUnderlying(address token, uint256 amt) public view returns (uint256 usdoAmt) {
        return _assetRegistry.convertFromUnderlying(token, amt);
    }

    function convertToUnderlying(address token, uint256 usdoAmt) public view returns (uint256 amt) {
        return _assetRegistry.convertToUnderlying(token, usdoAmt);
    }

    function updateTreasury(address treasury) external onlyRole(MAINTAINER_ROLE) {
        if (treasury == address(0)) revert USDOExpressZeroAddress();
        _treasury = treasury;
        emit UpdateTreasury(treasury);
    }

    function updateFeeTo(address feeTo) external onlyRole(MAINTAINER_ROLE) {
        if (feeTo == address(0)) revert USDOExpressZeroAddress();
        _feeTo = feeTo;
        emit UpdateFeeTo(feeTo);
    }

    function txsFee(uint256 amt, TxType txType) public view returns (uint256 fee) {
        uint256 feeRate;
        if (txType == TxType.MINT) {
            feeRate = _mintFeeRate;
        } else if (txType == TxType.REDEEM) {
            feeRate = _redeemFeeRate;
        } else if (txType == TxType.INSTANT_REDEEM) {
            feeRate = _instantRedeemFeeRate;
        }
        fee = (amt * feeRate) / _BPS_BASE;
    }

    /**
     * @notice Previews the instant mint amounts.
     * @param usdoAmt The amount of USDO requested for minting.
     * @return usdoAmtCurr The amount of USDO minted with the current bonus multiplier.
     * @return usdoAmtNext The amount of USDO minted with the next bonus multiplier.
     */
    function previewIssuance(uint256 usdoAmt) public view returns (uint256 usdoAmtCurr, uint256 usdoAmtNext) {
        (uint256 curr, uint256 next) = getBonusMultiplier();
        usdoAmtCurr = usdoAmt.mulDiv(curr, next);
        usdoAmtNext = usdoAmtCurr.mulDiv(next, curr);
    }

    function getBonusMultiplier() public view returns (uint256 curr, uint256 next) {
        curr = _usdo.bonusMultiplier();
        next = curr + _increment;
    }

    function previewMint(
        address underlying,
        uint256 amt
    ) public view returns (uint256 netAmt, uint256 fee, uint256 usdoAmtCurr, uint256 usdoAmtNext) {
        fee = txsFee(amt, TxType.MINT);
        netAmt = amt - fee;
        uint256 usdoAmt = convertFromUnderlying(underlying, netAmt);
        (usdoAmtCurr, usdoAmtNext) = previewIssuance(usdoAmt);
    }

    function previewRedeem(
        uint256 amt,
        bool isInstant
    ) public view returns (uint256 feeAmt, uint256 usdcAmt, uint256 extraFee) {
        TxType txType = isInstant ? TxType.INSTANT_REDEEM : TxType.REDEEM;
        uint256 feeInUsdo = txsFee(amt, txType);

        if (isInstant && address(_redemptionContract) != address(0)) {
            // For instant redemption, include fees from the redemption contract
            uint256 usdcNeeded = convertToUnderlying(_usdc, amt);

            // Get redemption preview (payout and redemption fee)
            (uint256 redemptionPayout, uint256 redemptionFee, ) = _redemptionContract.previewRedeem(usdcNeeded);

            // Calculate USDOExpress fee in USDC
            feeAmt = convertToUnderlying(_usdc, feeInUsdo);

            // Redemption contract fee (extraFee)
            extraFee = redemptionFee;

            // User receives: redemption payout - USDOExpress fee
            usdcAmt = redemptionPayout - feeAmt;
        } else {
            // For manual redemption, only USDOExpress fees apply
            feeAmt = convertToUnderlying(_usdc, feeInUsdo);
            usdcAmt = convertToUnderlying(_usdc, amt - feeInUsdo);
            extraFee = 0; // No redemption contract fees for manual redemption
        }
    }

    /**
     * @notice Set the redemption contract and token addresses.
     * @param redemptionContract Address of the redemption contract.
     */
    function setRedemption(address redemptionContract) external onlyRole(MAINTAINER_ROLE) {
        _redemptionContract = IRedemption(redemptionContract);
        emit SetRedemption(redemptionContract);
    }

    /**
     * @notice Retrieve the on-chain assets amount.
     * @param token The address of the token.
     * @return assetAmt Amount of onchain usdc.
     */
    function getTokenBalance(address token) public view returns (uint256 assetAmt) {
        return IERC20Upgradeable(token).balanceOf(address(this));
    }

    /**
     * @notice Update the mint status of the account.
     */
    function updateFirstDeposit(address account, bool flag) external onlyRole(MAINTAINER_ROLE) {
        _firstDeposit[account] = flag;
        emit UpdateFirstDeposit(account, flag);
    }

    /**
     * @notice Retrieve redemption queue information for a given index.
     * @param _index Index to retrieve data from.
     * @return sender The sender's address.
     * @return receiver The receiver's address.
     * @return usdoAmt The number of USDO.
     * @return id The ID associated with the redemption.
     */
    function getRedemptionQueueInfo(
        uint256 _index
    ) external view returns (address sender, address receiver, uint256 usdoAmt, bytes32 id) {
        if (_redemptionQueue.empty() || _index > _redemptionQueue.length() - 1) {
            return (address(0), address(0), 0, 0x0);
        }

        bytes memory data = bytes(_redemptionQueue.at(_index));
        (sender, receiver, usdoAmt, id) = _decodeData(data);
    }

    /**
     * @notice Retrieve redemption information for a specific user that is in the queue.
     * @param _user Address of the user.
     * @return usdoAmt Number of USDO associated with the user.
     */
    function getRedemptionUserInfo(address _user) external view returns (uint256 usdoAmt) {
        return _redemptionInfo[_user];
    }

    /**
     * @notice Retrieve the length of the redemption queue.
     * @return Length of the redemption queue.
     */
    function getRedemptionQueueLength() external view returns (uint256) {
        return _redemptionQueue.length();
    }

    /*//////////////////////////////////////////////////////////////
                    USDOExpressPausable functions
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Pauses minting
     */
    function pauseMint() external onlyRole(PAUSE_ROLE) {
        _pauseMint();
    }

    /**
     * @notice Unpauses minting
     */
    function unpauseMint() external onlyRole(PAUSE_ROLE) {
        _unpauseMint();
    }

    /**
     * @notice Pauses redeeming
     */
    function pauseRedeem() external onlyRole(PAUSE_ROLE) {
        _pauseRedeem();
    }

    /**
     * @notice Unpauses redeeming
     */
    function unpauseRedeem() external onlyRole(PAUSE_ROLE) {
        _unpauseRedeem();
    }

    /*//////////////////////////////////////////////////////////////
                    USDOMintRedeemLimiter functions
    //////////////////////////////////////////////////////////////*/
    /**
     * @notice Set the mint minimum in USDO equivalent.
     * @dev Amount should be in 18 decimals (USDO decimals) for accurate comparison across different underlying assets
     */
    function setMintMinimum(uint256 mintMinimum) external onlyRole(MAINTAINER_ROLE) {
        _setMintMinimum(mintMinimum);
    }

    /**
     * @notice Set the mint limit for a certain duration in seconds, etc 8400s.
     */
    function setMintDuration(uint256 mintDuration) external onlyRole(MAINTAINER_ROLE) {
        _setMintDuration(mintDuration);
    }

    /**
     * @notice Set the mint limit for a certain duration in seconds.
     */
    function setMintLimit(uint256 mintLimit) external onlyRole(MAINTAINER_ROLE) {
        _setMintLimit(mintLimit);
    }

    /**
     * @notice Set the redeem minimum in USDO.
     * @dev with 18 decimals
     */
    function setRedeemMinimum(uint256 redeemMinimum) external onlyRole(MAINTAINER_ROLE) {
        _setRedeemMinimum(redeemMinimum);
    }

    /**
     * @notice Set the redeem duration for a certain duration in seconds, etc 8400s.
     */
    function setRedeemDuration(uint256 redeemDuration) external onlyRole(MAINTAINER_ROLE) {
        _setRedeemDuration(redeemDuration);
    }

    /**
     * @notice Set the redeem limit for a certain duration in seconds.
     */
    function setRedeemLimit(uint256 redeemLimit) external onlyRole(MAINTAINER_ROLE) {
        _setRedeemLimit(redeemLimit);
    }

    /**
     * @notice Set the first deposit amount for the account.
     * @dev Amount should be in 18 decimals (USDO decimals) for accurate comparison across different underlying assets
     * @param amount The amount of the first deposit in USDO equivalent.
     */
    function setFirstDepositAmount(uint256 amount) external onlyRole(MAINTAINER_ROLE) {
        _setFirstDepositAmount(amount);
    }

    /**
     * @notice Grant KYC to the address.
     * @param _addresses The address to grant KYC.
     */
    function grantKycInBulk(address[] calldata _addresses) external onlyRole(WHITELIST_ROLE) {
        for (uint256 i = 0; i < _addresses.length; i++) {
            _kycList[_addresses[i]] = true;
        }
        emit USDOKycGranted(_addresses);
    }

    /**
     * @notice Revoke KYC to the address.
     * @param _addresses The address to revoke KYC.
     */
    function revokeKycInBulk(address[] calldata _addresses) external onlyRole(WHITELIST_ROLE) {
        for (uint256 i = 0; i < _addresses.length; i++) {
            _kycList[_addresses[i]] = false;
        }
        emit USDOKycRevoked(_addresses);
    }

    /**
     * @dev transfer underlying from vault to treasury, only operator can call this function
     * @param amt the amount of the token to transfer
     */
    function offRamp(uint256 amt) external onlyRole(OPERATOR_ROLE) {
        SafeERC20Upgradeable.safeTransfer(IERC20Upgradeable(_usdc), _treasury, amt);
        emit OffRamp(_treasury, amt);
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Decodes a given data bytes into its components.
     * @param _data Encoded data bytes.
     * @return sender Sender's address.
     * @return receiver Receiver's address.
     * @return usdoAmt Number of USDO.
     * @return prevId Previous ID.
     */
    function _decodeData(
        bytes memory _data
    ) internal pure returns (address sender, address receiver, uint256 usdoAmt, bytes32 prevId) {
        (sender, receiver, usdoAmt, prevId) = abi.decode(_data, (address, address, uint256, bytes32));
    }

    /**
     * @notice Allows a whitelisted user to perform an instant mint.
     * @param underlying The address of the token to mint USDO from.
     * @param to The address to mint the USDO to.
     * @param amt The supplied amount of the underlying token.
     * @param user The end user to mint the USDO to.
     */
    function _instantMintInternal(
        address underlying,
        address from,
        address to,
        address user,
        uint256 amt
    ) internal returns (uint256, uint256) {
        // Convert underlying amount to USDO decimals for comparison
        uint256 usdoEquivalent = convertFromUnderlying(underlying, amt);

        // if the user has not deposited before, the first deposit amount should be set
        // if the user has deposited before, the mint amount should be greater than the mint minimum
        // do noted: the first deposit amount will be greater than the mint minimum
        if (!_firstDeposit[user]) {
            if (usdoEquivalent < _firstDepositAmount)
                revert FirstDepositLessThanRequired(usdoEquivalent, _firstDepositAmount);
            _firstDeposit[user] = true;
        } else {
            if (usdoEquivalent < _mintMinimum) revert MintLessThanMinimum(usdoEquivalent, _mintMinimum);
        }

        (uint256 netAmt, uint256 fee, uint256 usdoAmtCurr, ) = previewMint(underlying, amt);
        _checkMintLimit(usdoAmtCurr);

        // 2. transfer netAmt to treasury, and fee to feeTo
        if (fee > 0) SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(underlying), from, _feeTo, fee);
        SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(underlying), from, address(_treasury), netAmt);

        _usdo.mint(to, usdoAmtCurr);
        return (usdoAmtCurr, fee);
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}
"
    },
    "contracts/interfaces/IAssetRegistry.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

/**
 * @title IAssetRegistry
 * @notice Simple interface for managing supported underlying assets
 */
interface IAssetRegistry {
    struct AssetConfig {
        address asset;
        bool isSupported;
        address priceFeed; // Optional: IPriceFeed contract for price conversion (like TBILL)
        uint256 maxStalePeriod; // Maximum staleness period for this asset's price feed (in seconds)
    }

    /**
     * @notice Add or update an asset configuration
     * @param config The asset configuration
     */
    function setAssetConfig(AssetConfig calldata config) external;

    /**
     * @notice Remove an asset from supported assets
     * @param asset The asset address
     */
    function removeAsset(address asset) external;

    /**
     * @notice Get asset configuration
     * @param asset The asset address
     * @return config The asset configuration
     */
    function getAssetConfig(address asset) external view returns (AssetConfig memory config);

    /**
     * @notice Check if asset is supported
     * @param asset The asset address
     * @return supported True if asset is supported
     */
    function isAssetSupported(address asset) external view returns (bool supported);

    /**
     * @notice Convert asset amount to USDO amount
     * @param asset The asset address
     * @param assetAmount The asset amount
     * @return usdoAmount The equivalent USDO amount
     */
    function convertFromUnderlying(address asset, uint256 assetAmount) external view returns (uint256 usdoAmount);

    /**
     * @notice Convert USDO amount to asset amount
     * @param asset The asset address
     * @param usdoAmount The USDO amount
     * @return assetAmount The equivalent asset amount
     */
    function convertToUnderlying(address asset, uint256 usdoAmount) external view returns (uint256 assetAmount);

    /**
     * @notice Get list of all supported assets
     * @return assets Array of supported asset addresses
     */
    function getSupportedAssets() external view returns (address[] memory assets);

    // Events
    event AssetAdded(address indexed asset, AssetConfig config);
    event AssetUpdated(address indexed asset, AssetConfig config);
    event AssetRemoved(address indexed asset);
    event MaxStalePeriodUpdated(uint256 newStalePeriod);
}
"
    },
    "contracts/interfaces/IRedemption.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.18;

interface IRedemption {
    function checkLiquidity() external view returns (uint256, uint256, uint256, uint256, uint256, uint256);

    function checkPaused() external view returns (bool);

    function redeem(uint256 amount) external returns (uint256 payout, uint256 fee, int256 price);

    function redeemFor(address user, uint256 amount) external returns (uint256 payout, uint256 fee, int256 price);

    function previewRedeem(uint256 amount) external view returns (uint256 payout, uint256 fee, int256 price);
}
"
    },
    "contracts/interfaces/ICUSDO.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

interface ICUSDO {
    function deposit(uint256 assets, address receiver) external returns (uint256);

    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256);

    function maxDeposit(address receiver) external view returns (uint256);

    function maxWithdraw(address owner) external view returns (uint256);

    function previewDeposit(uint256 assets) external view returns (uint256);
}
"
    },
    "contracts/interfaces/IUSDO.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

interface IUSDO {
    function bonusMultiplier() external view returns (uint256);

    function addBonusMultiplier(uint256 _bonusMultiplierIncrement) external;

    function approve(address spender, uint256 amount) external returns (bool);

    function mint(address to, uint256 amount) external;

    function burn(address from, uint256 amount) external;

    function pause() external;

    function unpause() external;

    function totalSupply() external view returns (uint256);
}
"
    },
    "contracts/extensions/DoubleQueueModified.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/structs/DoubleEndedQueue.sol)
pragma solidity =0.8.18;

/**
 * @dev A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of
 * the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and
 * FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes {clear}, given that
 * the existing queue contents are left in storage.
 *
 * The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be
 * used in storage, and not in memory.
 * ```
 * DoubleEndedQueue.Bytes32Deque queue;
 * ```
 *
 * _Available since v4.6._
 */
library DoubleQueueModified {
    /**
     * @dev An operation (e.g. {front}) couldn't be completed due to the queue being empty.
     */
    error Empty();

    /**
     * @dev An operation (e.g. {at}) couldn't be completed due to an index being out of bounds.
     */
    error OutOfBounds();

    /**
     * @dev Indices are signed integers because the queue can grow in any direction. They are 128 bits so begin and end
     * are packed in a single storage slot for efficient access. Since the items are added one at a time we can safely
     * assume that these 128-bit indices will not overflow, and use unchecked arithmetic.
     *
     * Struct members have an underscore prefix indicating that they are "private" and should not be read or written to
     * directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and
     * lead to unexpected behavior.
     *
     * Indices are in the range [begin, end) which means the first item is at data[begin] and the last item is at
     * data[end - 1].
     */
    struct BytesDeque {
        int128 _begin;
        int128 _end;
        mapping(int128 => bytes) _data;
    }

    /**
     * @dev Inserts an item at the end of the queue.
     */
    function pushBack(BytesDeque storage deque, bytes memory value) internal {
        int128 backIndex = deque._end;
        deque._data[backIndex] = value;
        unchecked {
            deque._end = backIndex + 1;
        }
    }

    /**
     * @dev Removes the item at the beginning of the queue and returns it.
     *
     * Reverts with `Empty` if the queue is empty.
     */
    function popFront(BytesDeque storage deque) internal returns (bytes memory value) {
        if (empty(deque)) revert Empty();
        int128 frontIndex = deque._begin;
        value = deque._data[frontIndex];
        delete deque._data[frontIndex];
        unchecked {
            deque._begin = frontIndex + 1;
        }
    }

    /**
     * @dev Returns the item at the beginning of the queue.
     *
     * Reverts with `Empty` if the queue is empty.
     */
    function front(BytesDeque storage deque) internal view returns (bytes memory value) {
        if (empty(deque)) revert Empty();
        int128 frontIndex = deque._begin;
        return deque._data[frontIndex];
    }

    /**
     * @dev Return the item at a position in the queue given by `index`, with the first item at 0 and last item at
     * `length(deque) - 1`.
     *
     * Reverts with `OutOfBounds` if the index is out of bounds.
     */
    function at(BytesDeque storage deque, uint256 index) internal view returns (bytes memory value) {
        // int256(deque._begin) is a safe upcast
        int128 idx = _toInt128(int256(deque._begin) + _toInt256(index));
        if (idx >= deque._end) revert OutOfBounds();
        return deque._data[idx];
    }

    function _toInt128(int256 value) internal pure returns (int128 downcasted) {
        downcasted = int128(value);
        require(downcasted == value, "SafeCast: value doesn't fit in 128 bits");
    }

    function _toInt256(uint256 value) internal pure returns (int256) {
        // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
        require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
        return int256(value);
    }

    /**
     * @dev Resets the queue back to being empty.
     *
     * NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses
     * out on potential gas refunds.
     */
    function clear(BytesDeque storage deque) internal {
        deque._begin = 0;
        deque._end = 0;
    }

    /**
     * @dev Returns the number of items in the queue.
     */
    function length(BytesDeque storage deque) internal view returns (uint256) {
        // The interface preserves the invariant that begin <= end so we assume this will not overflow.
        // We also assume there are at most int256.max items in the queue.
        unchecked {
            return uint256(int256(deque._end) - int256(deque._begin));
        }
    }

    /**
     * @dev Returns true if the queue is empty.
     */
    function empty(BytesDeque storage deque) internal view returns (bool) {
        return deque._end <= deque._begin;
    }
}
"
    },
    "contracts/extensions/USDOMintRedeemLimiterV2.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

struct USDOMintRedeemLimiterCfg {
    // mint
    uint256 mintMinimum;
    uint256 mintLimit;
    uint256 mintDuration;
    // redeem
    uint256 redeemMinimum;
    uint256 redeemLimit;
    uint256 redeemDuration;
    uint256 firstDepositAmount;
}

/**
 * @title USDOMintRedeemLimiter
 * @notice contract implementing time-based rate limiting for minting and redeeming.
 */
abstract contract USDOMintRedeemLimiter {
    // Total supply cap
    uint256 public _RESERVE2; // #previous: _totalSupplyCap

    // Mint rate limit
    uint256 public _mintMinimum;
    uint256 public _mintLimit;
    uint256 public _mintDuration;
    uint256 public _mintResetTime;
    uint256 public _mintedAmount;

    // Redeem rate limit
    uint256 public _redeemMinimum;
    uint256 public _redeemLimit;
    uint256 public _redeemDuration;
    uint256 public _redeemResetTime;
    uint256 public _redeemedAmount;

    // First deposit amount
    uint256 public _firstDepositAmount;

    // Events
    event MintMinimumUpdated(uint256 newMinimum);
    event MintLimitUpdated(uint256 newLimit);
    event MintDurationUpdated(uint256 newDuration);

    event RdeemMinimumUpdated(uint256 newMinimum);
    event RedeemLimitUpdated(uint256 newLimit);
    event RedeemDurationUpdated(uint256 newDuration);
    event FirstDepositAmount(uint256 amount);

    // Errors
    error RedeemLessThanMinimum(uint256 amount, uint256 minimum);
    error MintLimitExceeded();
    error RedeemLimitExceeded();

    /**
     * @notice Initializes mint and redeem rate limits.
     * @param mintMinimum    Min amount allowed to mint in one transaction (in USDO decimals - 18)
     * @param mintLimit      Max amount allowed to mint in one duration (in USDO decimals - 18)
     * @param mintDuration   Reset duration for minting (seconds)
     * @param redeemMinimum  Min amount allowed to redeem in one transaction (in USDO decimals - 18)
     * @param redeemLimit    Max amount allowed to redeem in one duration (in USDO decimals - 18)
     * @param redeemDuration Reset duration for redeeming (seconds)
     * @param firstDepositAmount The first deposit amount (in USDO decimals - 18)
     */

    function __USDOMintRedeemLimiter_init(
        uint256 mintMinimum,
        uint256 mintLimit,
        uint256 mintDuration,
        uint256 redeemMinimum,
        uint256 redeemLimit,
        uint256 redeemDuration,
        uint256 firstDepositAmount
    ) internal {
        //mint
        _mintMinimum = mintMinimum;
        _mintLimit = mintLimit;
        _mintDuration = mintDuration;

        //redeem
        _redeemMinimum = redeemMinimum;
        _redeemLimit = redeemLimit;
        _redeemDuration = redeemDuration;
        _firstDepositAmount = firstDepositAmount;

        _mintResetTime = block.timestamp;
        _redeemResetTime = block.timestamp;
    }

    /*//////////////////////////////////////////////////////////////
                          Mint Limit Functions
    //////////////////////////////////////////////////////////////*/
    /**
     * @dev Ensures mint amount doesn't exceed the rate limit.
     * @param amount Amount to mint.
     */
    function _checkMintLimit(uint256 amount) internal {
        if (block.timestamp >= _mintResetTime + _mintDuration) {
            _mintedAmount = 0;
            _mintResetTime = block.timestamp;
        }

        if (_mintedAmount + amount > _mintLimit) revert MintLimitExceeded();
        _mintedAmount += amount;
    }

    /**
     * @dev Updates the mint minimum.
     * @dev Amount should be in USDO decimals (18) for accurate comparison across different underlying assets
     * @param mintMinimum New mint minimum in USDO equivalent.
     */
    function _setMintMinimum(uint256 mintMinimum) internal {
        _mintMinimum = mintMinimum;
        emit MintMinimumUpdated(mintMinimum);
    }

    /**
     * @dev Updates the mint limit.
     * @param mintLimit New mint limit.
     */
    function _setMintLimit(uint256 mintLimit) internal {
        _mintLimit = mintLimit;
        emit MintLimitUpdated(mintLimit);
    }

    /**
     * @dev Updates the mint duration.
     * @param mintDuration New mint duration (seconds).
     */
    function _setMintDuration(uint256 mintDuration) internal {
        _mintDuration = mintDuration;
        emit MintDurationUpdated(mintDuration);
    }

    /*//////////////////////////////////////////////////////////////
                          Redeem Limit Functions
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Ensures redeem amount doesn't exceed the rate limit.
     * @param amount Amount to redeem.
     */
    function _checkRedeemLimit(uint256 amount) internal {
        if (amount < _redeemMinimum) revert RedeemLessThanMinimum(amount, _redeemMinimum);

        if (block.timestamp >= _redeemResetTime + _redeemDuration) {
            _redeemedAmount = 0;
            _redeemResetTime = block.timestamp;
        }

        if (_redeemedAmount + amount > _redeemLimit) revert RedeemLimitExceeded();
        _redeemedAmount += amount;
    }

    /**
     * @dev Updates the redeem minimum.
     * @param redeemMinimum New redeem minimum.
     */
    function _setRedeemMinimum(uint256 redeemMinimum) internal {
        _redeemMinimum = redeemMinimum;
        emit RdeemMinimumUpdated(redeemMinimum);
    }

    /**
     * @dev Updates the redeem limit.
     * @param redeemLimit New redeem limit.
     */
    function _setRedeemLimit(uint256 redeemLimit) internal {
        _redeemLimit = redeemLimit;
        emit RedeemLimitUpdated(redeemLimit);
    }

    /**
     * @dev Updates the redeem duration.
     * @param redeemDuration New redeem duration (seconds).
     */
    function _setRedeemDuration(uint256 redeemDuration) internal {
        _redeemDuration = redeemDuration;
        emit RedeemDurationUpdated(redeemDuration);
    }

    /// @notice Set the first deposit amount
    /// @dev Amount should be in USDO decimals (18) for accurate comparison across different underlying assets
    /// @param amount The first deposit amount in USDO equivalent
    function _setFirstDepositAmount(uint256 amount) internal {
        _firstDepositAmount = amount;
        emit FirstDepositAmount(amount);
    }

    uint256[10] private __gap;
}
"
    },
    "contracts/extensions/USDOExpressPausable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity =0.8.18;
import "@openzeppelin/contracts/utils/Context.sol";

abstract contract USDOExpressPausable {
    event PausedMint(address account);
    event PausedRedeem(address account);

    event UnpausedMint(address account);
    event UnpausedRedeem(address account);

    bool private _pausedMint;
    bool private _pausedRedeem;

    /*//////////////////////////////////////////////////////////////
                          Paused Mint
    //////////////////////////////////////////////////////////////*/

    modifier whenNotPausedMint() {
        require(!pausedMint(), "Pausable: Mint paused");
        _;
    }

    modifier whenPausedMint() {
        require(pausedMint(), "Pausable: Mint not paused");
        _;
    }

    function pausedMint() public view virtual returns (bool) {
        return _pausedMint;
    }

    function _pauseMint() internal virtual whenNotPausedMint {
        _pausedMint = true;
        emit PausedMint(msg.sender);
    }

    function _unpauseMint() internal virtual whenPausedMint {
        _pausedMint = false;
        emit UnpausedMint(msg.sender);
    }

    /*//////////////////////////////////////////////////////////////
                          Paused Redeem
    //////////////////////////////////////////////////////////////*/

    modifier whenNotPausedRedeem() {
        require(!pausedRedeem(), "Pausable: Redeem paused");
        _;
    }

    modifier whenPausedRedeem() {
        require(pausedRedeem(), "Pausable: Redeem not paused");
        _;
    }

    function pausedRedeem() public view virtual returns (bool) {
        return _pausedRedeem;
    }

    function _pauseRedeem() internal virtual whenNotPausedRedeem {
        _pausedRedeem = true;
        emit PausedRedeem(msg.sender);
    }

    function _unpauseRedeem() internal virtual whenPausedRedeem {
        _pausedRedeem = false;
        emit UnpausedRedeem(msg.sender);
    }
}
"
    },
    "node_modules/@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library MathUpgradeable {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

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

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return 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 up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev 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 {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // 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 prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

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

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

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, 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.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

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

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

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

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

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

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

    /**
     * @notice 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) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

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

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}
"
    },
    "node_modules/@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../extensions/IERC20PermitUpgradeable.sol";
import "../../../utils/AddressUpgradeable.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 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 SafeERC20Upgradeable {
    using AddressUpgradeable for address;

    /**
     * @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(IERC20Upgradeable token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from

Tags:
ERC20, ERC165, Proxy, Mintable, Burnable, Pausable, Liquidity, Upgradeable, Factory|addr:0xddea8966f5f33281333b19f7b071fb5b05118451|verified:true|block:23647160|tx:0x820604c7e1042427ea465dc97d5902bf0b97979b75c0d5fff8e03f3e3bd8d37f|first_check:1761328037

Submitted on: 2025-10-24 19:47:18

Comments

Log in to comment.

No comments yet.