EOSpectraPTFeedHybrid

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": {
    "src/EOSpectraPTFeedHybrid.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { IEOSpectraPTFeedHybrid } from "./interfaces/IEOSpectraPTFeedHybrid.sol";
import { IEOSpectraPTFeed } from "./interfaces/IEOSpectraPTFeed.sol";
import { EOSpectraPTFeed } from "./EOSpectraPTFeed.sol";
import { CurveOracleLib } from "@spectra-core/src/libraries/CurveOracleLib.sol";
import { ICurveNGPool } from "@spectra-core/src/interfaces/ICurveNGPool.sol";
import { IStableSwapNG } from "@spectra-core/src/interfaces/IStableSwapNG.sol";
import { DecimalConverter } from "./libraries/DecimalsConverter.sol";

/**
 * @title EOSpectraPTFeedHybrid
 * @author EO
 * @notice Spectra PT Hybrid feed oracle, based on MIN(1, MAX(LinearDiscount, TWAP PTtoAsset))
 */
contract EOSpectraPTFeedHybrid is EOSpectraPTFeed, IEOSpectraPTFeedHybrid {
    using CurveOracleLib for address;
    using DecimalConverter for uint256;

    /**
     * @notice The minimum duration of the TWAP
     */
    uint32 internal constant _MIN_TWAP_DURATION = 300;

    /**
     * @notice The Curve pool address
     */
    address public pool;

    /**
     * @notice The function to get the PT to Asset TWAP rate, is dynamic based on the pool type
     */
    function() view returns (uint256) internal _getPTToAssetTWAPRate;

    /**
     * @notice The gap for the upgradeable contract.
     */
    uint256[50] private __gap;

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

    /**
     * @notice Initializes the contract with the required parameters
     * @param pool_ The address of the Spectra pool
     * @param description_ The description of the price feed
     * @param initialImpliedAPY_ The initial implied APY
     * @param decimals_ The decimals of the price feed, if 0 - use the underlying decimals
     */
    function initialize(
        address pool_,
        string memory description_,
        uint256 initialImpliedAPY_,
        uint8 decimals_
    )
        external
        initializer
    {
        if (pool_ == address(0)) revert PoolAddressIsZero();

        pool = pool_;
        _setTWAPRateMethod();

        // Derive PT token from the Curve pool and initialize the base feed with LINEAR discount
        address ptTokenAddress = ICurveNGPool(pool_).coins(1);
        _initializeSpectraPTFeed(
            ptTokenAddress, description_, initialImpliedAPY_, IEOSpectraPTFeed.DiscountType.LINEAR, decimals_
        );
    }

    /**
     * @notice External wrapper to enable try/catch around TWAP retrieval
     * @dev This simply forwards to the internally-selected TWAP method
     * @return The rate of the PT to asset TWAP, with configured decimals
     */
    function getPTToAssetTWAPRate() external view returns (uint256) {
        return _getPTToAssetTWAPRate();
    }

    /**
     * @notice External wrapper for Linear Discount rate
     * @return the Linear Discount rate
     */
    function getLinearDiscountRate() external view returns (uint256) {
        return _getLinearDiscountRate();
    }

    /**
     * @notice Returns latest round data
     * @dev Returns zero for roundId, startedAt and answeredInRound.
     * @return roundId The round id, returns 0
     * @return answer The answer, returns the rate according to the Hybrid, MIN(1, MAX(LinearDiscount, TWAP PTtoAsset))
     * @return startedAt The started at, returns 0
     * @return updatedAt The updated at, returns current block timestamp
     * @return answeredInRound The answered in round, returns 0
     */
    function latestRoundData() public view override returns (uint80, int256, uint256, uint256, uint80) {
        uint256 rateLinearDiscount = _getLinearDiscountRate();

        // Degradation policy: if TWAP fails but IBT-aware linear succeeds → return the IBT-aware linear
        uint256 rateTWAP = 0;
        bool twapFailed = false;
        try this.getPTToAssetTWAPRate() returns (uint256 rateTWAP_) {
            rateTWAP = rateTWAP_;
        } catch {
            twapFailed = true;
        }

        uint256 rate;
        if (twapFailed) {
            rate = rateLinearDiscount;
        } else {
            rate = (rateTWAP > rateLinearDiscount) ? rateTWAP : rateLinearDiscount;
        }

        uint256 one = 10 ** uint256(decimals);
        if (rate > one) rate = one;

        return (0, int256(rate), 0, block.timestamp, 0);
    }

    /**
     * @notice Internal wrapper for setting the TWAP rate method
     */
    function _setTWAPRateMethod() internal {
        if (_isCryptoswapPoolType()) {
            _getPTToAssetTWAPRate = _getPTToAssetTWAPRateCryptoswap;
        } else if (_isStableswapPoolType()) {
            _getPTToAssetTWAPRate = _getPTToAssetTWAPRateStableswap;
        } else {
            revert PoolTypeNotSupported();
        }
    }

    /**
     * @notice Internal wrapper for checking if the pool is a Curve Cryptoswap pool
     * @return True if the pool is a Curve Cryptoswap pool, false otherwise
     */
    function _isCryptoswapPoolType() internal view returns (bool) {
        try ICurveNGPool(pool).price_oracle() returns (uint256) {
            return true;
        } catch {
            return false;
        }
    }

    /**
     * @notice Internal wrapper for checking if the pool is a Curve StableSwap NG pool
     * @return True if the pool is a Curve StableSwap NG pool, false otherwise
     */
    function _isStableswapPoolType() internal view returns (bool) {
        try IStableSwapNG(pool).price_oracle(0) returns (uint256) {
            return true;
        } catch {
            return false;
        }
    }

    /**
     * @notice Internal wrapper for TWAP PT to Asset rate on a Curve Cryptoswap pool
     * @return The rate of the PT to asset TWAP, with configured decimals
     */
    function _getPTToAssetTWAPRateCryptoswap() internal view returns (uint256) {
        return pool.getPTToAssetRate().convertDecimals(underlyingDecimals, decimals);
    }

    /**
     * @notice Internal wrapper for TWAP PT to Asset rate on a Curve StableSwap NG pool
     * @return The rate of the PT to asset TWAP, with configured decimals
     */
    function _getPTToAssetTWAPRateStableswap() internal view returns (uint256) {
        return pool.getPTToAssetRateSNG().convertDecimals(underlyingDecimals, decimals);
    }

    /**
     * @notice Internal wrapper for Linear Discount rate
     * @return The rate of the Linear Discount, with configured decimals
     */
    function _getLinearDiscountRate() internal view returns (uint256) {
        // Use the base feed's discount model (LINEAR) price
        return _getPriceWithDiscount().convertDecimals(underlyingDecimals, decimals);
    }
}
"
    },
    "src/interfaces/IEOSpectraPTFeedHybrid.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title IEOSpectraPTFeedHybrid
 * @author EO
 * @notice Interface for Spectra PT Hybrid feed oracle
 */
interface IEOSpectraPTFeedHybrid {
    /**
     * @notice Error thrown when the pool address is zero.
     */
    error PoolAddressIsZero();

    /**
     * @notice Error thrown when the pool type is not supported.
     */
    error PoolTypeNotSupported();

    function initialize(
        address pool_,
        string memory description_,
        uint256 initialImpliedAPY_,
        uint8 decimals_
    )
        external;
}
"
    },
    "src/interfaces/IEOSpectraPTFeed.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title IEOSpectraPTFeed
 * @author EO
 * @notice Interface for Spectra PT feed oracle, based on selected discount type.
 */
interface IEOSpectraPTFeed {
    /**
     * @notice The type of the discount
     * @dev LINEAR: 0
     */
    enum DiscountType {
        LINEAR
    }

    /**
     * @notice Error thrown when the PT token address is zero.
     */
    error PTTokenAddressIsZero();

    /**
     * @notice Error thrown when the discount type is invalid.
     */
    error InvalidDiscountType();

    /**
     * @notice Error thrown when the price is zero.
     */
    error PriceMustBeGreaterThanZero();

    function initialize(
        address ptToken_,
        string memory description_,
        uint256 initialImpliedAPY_,
        DiscountType discountType_,
        uint8 decimals_
    )
        external;
}
"
    },
    "src/EOSpectraPTFeed.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { ISpectraPT } from "./interfaces/spectra/ISpectraPT.sol";
import { EOBase } from "./EOBase.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { IEOSpectraPTFeed } from "./interfaces/IEOSpectraPTFeed.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import { DecimalConverter } from "./libraries/DecimalsConverter.sol";

/**
 * @title EOSpectraPTFeed
 * @author EO
 * @notice Spectra PT feed oracle, based on selected discount type.
 */
contract EOSpectraPTFeed is IEOSpectraPTFeed, EOBase, Initializable {
    using DecimalConverter for uint256;

    /**
     * @notice The number of seconds in a year.
     */
    uint256 internal constant _SECONDS_PER_YEAR = 365 days;

    /**
     * @notice The constant value of 1.
     */
    uint256 internal constant _ONE = 1e18;

    /**
     * @notice The unit precision of the PT token.
     */
    uint256 internal _ptScale;

    /**
     * @notice The decimals precision of the underlying token.
     */
    uint8 public underlyingDecimals;

    /**
     * @notice The decimals precision of the price feed.
     */
    uint8 public decimals;

    /**
     * @notice The address of the PT token.
     */
    address public ptToken;

    /**
     * @notice The maturity of the discount.
     */
    uint256 public maturity;

    /**
     * @notice The initial implied APY, 100% = 1e18
     */
    uint256 public initialImpliedAPY;

    /**
     * @notice The type of the discount.
     * @dev LINEAR(0)
     */
    DiscountType public discountType;

    /**
     * @notice The description of the price feed.
     */
    string public description;

    /**
     * @notice The initialization timestamp of this feed.
     */
    uint256 public startTime;

    /**
     * @notice The gap for the upgradeable contract.
     */
    uint256[50] private _gap;

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

    /**
     * @notice Initialize the price feed.
     * @param ptToken_ The address of the PT token.
     * @param description_ The description of the price feed.
     * @param initialImpliedAPY_ The initial implied APY.
     * @param discountType_ The type of the discount.
     * @param decimals_ The decimals of the price feed, if 0 - use the underlying decimals
     */
    function initialize(
        address ptToken_,
        string memory description_,
        uint256 initialImpliedAPY_,
        DiscountType discountType_,
        uint8 decimals_
    )
        external
        initializer
    {
        _initializeSpectraPTFeed(ptToken_, description_, initialImpliedAPY_, discountType_, decimals_);
    }

    /**
     * @notice Returns latest round data in configured decimals
     * @dev Returns zero for roundId, startedAt and answeredInRound.
     * @return roundId The round id, returns 0
     * @return answer The answer, returns the rate according to the discountType
     * @return startedAt The started at, returns 0
     * @return updatedAt The updated at, returns current block timestamp
     * @return answeredInRound The answered in round, returns 0
     */
    function latestRoundData() public view virtual override returns (uint80, int256, uint256, uint256, uint80) {
        uint256 priceWithDiscount = _getPriceWithDiscount();
        uint256 price = priceWithDiscount.convertDecimals(underlyingDecimals, decimals);

        return (0, int256(price), 0, block.timestamp, 0);
    }

    /**
     * @notice Internal initializer to support multiple inheritance initialization
     * @dev Should be called only from within another initializer
     */
    function _initializeSpectraPTFeed(
        address ptToken_,
        string memory description_,
        uint256 initialImpliedAPY_,
        DiscountType discountType_,
        uint8 decimals_
    )
        internal
        onlyInitializing
    {
        if (ptToken_ == address(0)) revert PTTokenAddressIsZero();

        _ptScale = 10 ** ISpectraPT(ptToken_).decimals();

        underlyingDecimals = IERC20Metadata(ISpectraPT(ptToken_).underlying()).decimals();
        ptToken = ptToken_;
        maturity = ISpectraPT(ptToken_).maturity();
        discountType = discountType_;
        initialImpliedAPY = initialImpliedAPY_;
        description = description_;
        decimals = decimals_ == 0 ? underlyingDecimals : decimals_;
        startTime = block.timestamp;

        uint256 price = _getPriceWithDiscount();

        // the check is necessary to avoid deployment of the feed with a price being 0
        // slither-disable-next-line incorrect-equality
        if (price == 0) revert PriceMustBeGreaterThanZero();
    }

    /**
     * @notice Get the discount for a given future PT value.
     * @return The price with discount applied, with decimals equal to underlying decimals
     */
    function _getPriceWithDiscount() internal view returns (uint256) {
        uint256 futurePTValue = ISpectraPT(ptToken).convertToUnderlying(_ptScale);

        if (discountType == DiscountType.LINEAR) {
            uint256 currentTimestamp = block.timestamp;

            if (currentTimestamp >= maturity) {
                return futurePTValue;
            }

            uint256 duration = maturity - startTime;
            uint256 termAdjustedInitialImpliedAPY = (initialImpliedAPY * duration) / _SECONDS_PER_YEAR;

            uint256 anchor = (futurePTValue * _ONE) / (_ONE + termAdjustedInitialImpliedAPY);
            uint256 drift = ((futurePTValue - anchor) * (currentTimestamp - startTime)) / duration;
            uint256 price = anchor + drift;
            return price;
        } else {
            revert InvalidDiscountType();
        }
    }
}
"
    },
    "lib/spectra-core/src/libraries/CurveOracleLib.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.25;

import {Math} from "openzeppelin-math/Math.sol";
import {IERC20} from "openzeppelin-contracts/interfaces/IERC20.sol";
import {IERC4626} from "openzeppelin-contracts/interfaces/IERC4626.sol";
import {ICurveNGPool} from "../interfaces/ICurveNGPool.sol";
import {IStableSwapNG} from "../interfaces/IStableSwapNG.sol";
import {IPrincipalToken} from "../interfaces/IPrincipalToken.sol";

/**
 * @dev Utilities for computing prices of Spectra PTs, YTs and LP tokens in Curve CryptoSwap pools.
 */

library CurveOracleLib {
    using Math for uint256;

    error PoolLiquidityError();

    uint256 public constant CURVE_UNIT = 1e18;

    /**
     * This function returns the TWAP rate PT/Asset on a Curve Cryptoswap pool, but takes into account the current rate of IBT
     * This accounts for special cases where underlying asset becomes insolvent and has decreasing exchangeRate
     * @param pool Address of the Curve Pool to get rate from
     * @return PT/Underlying exchange rate
     */
    function getPTToAssetRate(address pool) internal view returns (uint256) {
        uint256 ptToIBTRate = getPTToIBTRate(pool);
        IERC4626 ibt = IERC4626(ICurveNGPool(pool).coins(0));
        return ibt.previewRedeem(ptToIBTRate);
    }

    /**
     * This function returns the TWAP rate PT/Asset on a Curve StableSwap NG pool, but takes into account the current rate of IBT
     * This accounts for special cases where underlying asset becomes insolvent and has decreasing exchangeRate
     * @param pool Address of the Curve Pool to get rate from
     * @return PT/Underlying exchange rate
     */
    function getPTToAssetRateSNG(address pool) public view returns (uint256) {
        uint256 ptToIBTRate = getPTToIBTRateSNG(pool);
        IERC4626 ibt = IERC4626(ICurveNGPool(pool).coins(0));
        return ibt.previewRedeem(ptToIBTRate);
    }

    /**
     * @dev This function returns the TWAP rate PT/IBT on a Curve Cryptoswap pool
     * This accounts for special cases where underlying asset becomes insolvent and has decreasing exchangeRate
     * @param pool Address of the Curve Pool to get rate from
     * @return PT/IBT exchange rate
     */
    function getPTToIBTRate(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(ICurveNGPool(pool).coins(1));
        uint256 maturity = pt.maturity();
        if (maturity <= block.timestamp) {
            return pt.previewRedeemForIBT(pt.getIBTUnit());
        } else {
            return pt.getIBTUnit().mulDiv(ICurveNGPool(pool).price_oracle(), CURVE_UNIT);
        }
    }

    /**
     * @dev This function returns the TWAP rate PT/IBT on a Curve StableSwap NG pool
     * This accounts for special cases where underlying asset becomes insolvent and has decreasing exchangeRate
     * @param pool Address of the Curve Pool to get rate from
     * @return PT/IBT exchange rate
     */
    function getPTToIBTRateSNG(address pool) public view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(ICurveNGPool(pool).coins(1));
        uint256 maturity = pt.maturity();
        if (maturity <= block.timestamp) {
            return pt.previewRedeemForIBT(pt.getIBTUnit());
        } else {
            uint256[] memory storedRates = IStableSwapNG(pool).stored_rates();
            return
                pt.getIBTUnit().mulDiv(storedRates[1], storedRates[0]).mulDiv(
                    IStableSwapNG(pool).price_oracle(0),
                    CURVE_UNIT
                );
        }
    }

    /**
     * This function returns the TWAP rate YT/Asset on a Curve Cryptoswap pool
     * @param pool Curve Pool to get rate from
     * @return YT/Underlying exchange rate
     */
    function getYTToAssetRate(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(ICurveNGPool(pool).coins(1));
        uint256 ptToAssetRateCore = pt.previewRedeem(pt.getIBTUnit());
        uint256 ptToAssetRateOracle = getPTToAssetRate(pool);
        if (ptToAssetRateOracle > ptToAssetRateCore) {
            revert PoolLiquidityError();
        }
        return (ptToAssetRateCore - ptToAssetRateOracle);
    }

    /**
     * This function returns the TWAP rate YT/Asset on a Curve StableSwap NG pool
     * @param pool Curve Pool to get rate from
     * @return YT/Underlying exchange rate
     */
    function getYTToAssetRateSNG(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(IStableSwapNG(pool).coins(1));
        uint256 ptToAssetRateCore = pt.previewRedeem(pt.getIBTUnit());
        uint256 ptToAssetRateOracle = getPTToAssetRateSNG(pool);
        if (ptToAssetRateOracle > ptToAssetRateCore) {
            revert PoolLiquidityError();
        }
        return (ptToAssetRateCore - ptToAssetRateOracle);
    }

    /**
     * @dev This function returns the TWAP rate YT/IBT on a Curve Cryptoswap pool
     * @param pool Curve Pool to get rate from
     * @return YT/IBT exchange rate
     */
    function getYTToIBTRate(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(ICurveNGPool(pool).coins(1));
        uint256 ptToIBTRateCore = pt.previewRedeemForIBT(pt.getIBTUnit());
        uint256 ptToIBTRateOracle = getPTToIBTRate(pool);
        if (ptToIBTRateOracle > ptToIBTRateCore) {
            revert PoolLiquidityError();
        }
        return ptToIBTRateCore - ptToIBTRateOracle;
    }

    /**
     * @dev This function returns the TWAP rate YT/IBT on a Curve StableSwap NG pool
     * @param pool Curve Pool to get rate from
     * @return YT/IBT exchange rate
     */
    function getYTToIBTRateSNG(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(IStableSwapNG(pool).coins(1));
        uint256 ptToIBTRateCore = pt.previewRedeemForIBT(pt.getIBTUnit());
        uint256 ptToIBTRateOracle = getPTToIBTRateSNG(pool);
        if (ptToIBTRateOracle > ptToIBTRateCore) {
            revert PoolLiquidityError();
        }
        return ptToIBTRateCore - ptToIBTRateOracle;
    }

    /**
     * This function returns the TWAP rate LP/Asset on a Curve Cryptoswap , and takes into account the current rate of IBT
     * @param pool Address of the Curve Pool to get rate from
     * @return LP/Underlying exchange rate
     */
    function getLPTToAssetRate(address pool) internal view returns (uint256) {
        uint256 lptToIBTRate = getLPTToIBTRate(pool);
        IERC4626 ibt = IERC4626(ICurveNGPool(pool).coins(0));
        return ibt.previewRedeem(lptToIBTRate);
    }

    /**
     * @dev This function returns the TWAP rate LP/IBT on a Curve CryptoSwap pool
     * @param pool Address of the Curve Pool to get rate from
     * @return LP/IBT exchange rate
     */
    function getLPTToIBTRate(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(ICurveNGPool(pool).coins(1));
        uint256 maturity = pt.maturity();
        uint256 balIBT = ICurveNGPool(pool).balances(0);
        uint256 balPT = ICurveNGPool(pool).balances(1);
        uint256 supplyLPT = IERC20(pool).totalSupply();
        if (maturity <= block.timestamp) {
            return
                pt.previewRedeemForIBT(balPT.mulDiv(CURVE_UNIT, supplyLPT)) +
                balIBT.mulDiv(CURVE_UNIT, supplyLPT);
        } else {
            uint256 ptToIBTRate = getPTToIBTRate(pool);
            return
                ((balPT.mulDiv(ptToIBTRate, pt.getIBTUnit())) + balIBT).mulDiv(
                    CURVE_UNIT,
                    supplyLPT
                );
        }
    }

    /**
     * This function returns the TWAP rate LP/Asset on a Curve StableSwap NG pool, and takes into account the current rate of IBT
     * @param pool Address of the Curve Pool to get rate from
     * @return LP/Underlying exchange rate
     */
    function getLPTToAssetRateSNG(address pool) internal view returns (uint256) {
        uint256 lptToIBTRate = getLPTToIBTRateSNG(pool);
        IERC4626 ibt = IERC4626(IStableSwapNG(pool).coins(0));
        return ibt.previewRedeem(lptToIBTRate);
    }

    /**
     * @dev This function returns the TWAP rate LP/IBT on a Curve StableSwap NG pool
     * @param pool Address of the Curve Pool to get rate from
     * @return LP/IBT exchange rate
     */
    function getLPTToIBTRateSNG(address pool) internal view returns (uint256) {
        IPrincipalToken pt = IPrincipalToken(IStableSwapNG(pool).coins(1));
        uint256 maturity = pt.maturity();
        uint256 balIBT = IStableSwapNG(pool).balances(0);
        uint256 balPT = IStableSwapNG(pool).balances(1);
        uint256 supplyLPT = IERC20(pool).totalSupply();
        if (maturity <= block.timestamp) {
            return
                pt.previewRedeemForIBT(balPT.mulDiv(CURVE_UNIT, supplyLPT)) +
                balIBT.mulDiv(CURVE_UNIT, supplyLPT);
        } else {
            uint256 ptToIBTRate = getPTToIBTRateSNG(pool);
            return
                ((balPT.mulDiv(ptToIBTRate, pt.getIBTUnit())) + balIBT).mulDiv(
                    CURVE_UNIT,
                    supplyLPT
                );
        }
    }
}
"
    },
    "lib/spectra-core/src/interfaces/ICurveNGPool.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.20;

import {IERC20Metadata} from "openzeppelin-contracts/token/ERC20/extensions/IERC20Metadata.sol";

/**
 * @dev Interface for Curve TwoCrypto-NG pool
 */
interface ICurveNGPool is IERC20Metadata {
    function coins(uint256 index) external view returns (address);

    function balances(uint256 index) external view returns (uint256);

    function A() external view returns (uint256);

    function gamma() external view returns (uint256);

    function D() external view returns (uint256);

    function token() external view returns (address);

    function price_scale() external view returns (uint256);

    function price_oracle() external view returns (uint256);

    function future_A_gamma_time() external view returns (uint256);

    function future_A_gamma() external view returns (uint256);

    function initial_A_gamma_time() external view returns (uint256);

    function initial_A_gamma() external view returns (uint256);

    function fee_gamma() external view returns (uint256);

    function mid_fee() external view returns (uint256);

    function out_fee() external view returns (uint256);

    function allowed_extra_profit() external view returns (uint256);

    function adjustment_step() external view returns (uint256);

    function admin_fee() external view returns (uint256);

    function ma_time() external view returns (uint256);

    function get_virtual_price() external view returns (uint256);

    function fee() external view returns (uint256);

    function get_dy(uint256 i, uint256 j, uint256 dx) external view returns (uint256);

    function get_dx(uint256 i, uint256 j, uint256 dy) external view returns (uint256);

    function last_prices() external view returns (uint256);

    function calc_token_amount(
        uint256[2] calldata amounts,
        bool deposit
    ) external view returns (uint256);

    function calc_withdraw_one_coin(
        uint256 _token_amount,
        uint256 i
    ) external view returns (uint256);

    function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256);

    function exchange(
        uint256 i,
        uint256 j,
        uint256 dx,
        uint256 min_dy,
        address receiver
    ) external returns (uint256);

    function add_liquidity(
        uint256[2] calldata amounts,
        uint256 min_mint_amount
    ) external returns (uint256);

    function add_liquidity(
        uint256[2] calldata amounts,
        uint256 min_mint_amount,
        address receiver
    ) external returns (uint256);

    function remove_liquidity(uint256 amount, uint256[2] calldata min_amounts) external;

    function remove_liquidity(
        uint256 amount,
        uint256[2] calldata min_amounts,
        address receiver
    ) external;

    function remove_liquidity_one_coin(
        uint256 token_amount,
        uint256 i,
        uint256 min_amount
    ) external;

    function remove_liquidity_one_coin(
        uint256 token_amount,
        uint256 i,
        uint256 min_amount,
        address receiver
    ) external;
}
"
    },
    "lib/spectra-core/src/interfaces/IStableSwapNG.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

interface IStableSwapNG {
    function A() external view returns (uint256);
    function A_precise() external view returns (uint256);
    function DOMAIN_SEPARATOR() external view returns (bytes32);
    function D_ma_time() external view returns (uint256);
    function D_oracle() external view returns (uint256);
    function N_COINS() external view returns (uint256);
    function add_liquidity(
        uint256[] memory _amounts,
        uint256 _min_mint_amount,
        address _receiver
    ) external returns (uint256);
    function admin_balances(uint256 arg0) external view returns (uint256);
    function admin_fee() external view returns (uint256);
    function allowance(address arg0, address arg1) external view returns (uint256);
    function approve(address _spender, uint256 _value) external returns (bool);
    function balanceOf(address arg0) external view returns (uint256);
    function balances(uint256 i) external view returns (uint256);
    function calc_token_amount(
        uint256[] memory _amounts,
        bool _is_deposit
    ) external view returns (uint256);
    function calc_withdraw_one_coin(uint256 _burn_amount, int128 i) external view returns (uint256);
    function coins(uint256 arg0) external view returns (address);
    function decimals() external view returns (uint8);
    function dynamic_fee(int128 i, int128 j) external view returns (uint256);
    function ema_price(uint256 i) external view returns (uint256);
    function exchange(int128 i, int128 j, uint256 _dx, uint256 _min_dy) external returns (uint256);
    function exchange(
        int128 i,
        int128 j,
        uint256 _dx,
        uint256 _min_dy,
        address _receiver
    ) external returns (uint256);
    function exchange_received(
        int128 i,
        int128 j,
        uint256 _dx,
        uint256 _min_dy
    ) external returns (uint256);
    function exchange_received(
        int128 i,
        int128 j,
        uint256 _dx,
        uint256 _min_dy,
        address _receiver
    ) external returns (uint256);
    function fee() external view returns (uint256);
    function future_A() external view returns (uint256);
    function future_A_time() external view returns (uint256);
    function get_balances() external view returns (uint256[] memory);
    function get_dx(int128 i, int128 j, uint256 dy) external view returns (uint256);
    function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256);
    function get_p(uint256 i) external view returns (uint256);
    function get_virtual_price() external view returns (uint256);
    function initial_A() external view returns (uint256);
    function initial_A_time() external view returns (uint256);
    function last_price(uint256 i) external view returns (uint256);
    function ma_exp_time() external view returns (uint256);
    function ma_last_time() external view returns (uint256);
    function name() external view returns (string memory);
    function nonces(address arg0) external view returns (uint256);
    function offpeg_fee_multiplier() external view returns (uint256);
    function permit(
        address _owner,
        address _spender,
        uint256 _value,
        uint256 _deadline,
        uint8 _v,
        bytes32 _r,
        bytes32 _s
    ) external returns (bool);
    function price_oracle(uint256 i) external view returns (uint256);
    function ramp_A(uint256 _future_A, uint256 _future_time) external;
    function remove_liquidity(
        uint256 _burn_amount,
        uint256[] memory _min_amounts
    ) external returns (uint256[] memory);
    function remove_liquidity(
        uint256 _burn_amount,
        uint256[] memory _min_amounts,
        address _receiver
    ) external returns (uint256[] memory);
    function remove_liquidity(
        uint256 _burn_amount,
        uint256[] memory _min_amounts,
        address _receiver,
        bool _claim_admin_fees
    ) external returns (uint256[] memory);
    function remove_liquidity_imbalance(
        uint256[] memory _amounts,
        uint256 _max_burn_amount
    ) external returns (uint256);
    function remove_liquidity_imbalance(
        uint256[] memory _amounts,
        uint256 _max_burn_amount,
        address _receiver
    ) external returns (uint256);
    function remove_liquidity_one_coin(
        uint256 _burn_amount,
        int128 i,
        uint256 _min_received
    ) external returns (uint256);
    function remove_liquidity_one_coin(
        uint256 _burn_amount,
        int128 i,
        uint256 _min_received,
        address _receiver
    ) external returns (uint256);
    function salt() external view returns (bytes32);
    function set_ma_exp_time(uint256 _ma_exp_time, uint256 _D_ma_time) external;
    function set_new_fee(uint256 _new_fee, uint256 _new_offpeg_fee_multiplier) external;
    function stop_ramp_A() external;
    function stored_rates() external view returns (uint256[] memory);
    function symbol() external view returns (string memory);
    function totalSupply() external view returns (uint256);
    function transfer(address _to, uint256 _value) external returns (bool);
    function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
    function version() external view returns (string memory);
    function withdraw_admin_fees() external;
}
"
    },
    "src/libraries/DecimalsConverter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
 * @title DecimalConverter
 * @author EO
 * @notice Library to convert amount from/to exact decimals
 */
library DecimalConverter {
    /**
     * @notice Converts a value from fromDecimals decimals to toDecimals decimals
     * @param amount The value to convert
     * @param fromDecimals Original number of decimals
     * @param toDecimals Target number of decimals
     * @return The converted value
     */
    function convertDecimals(uint256 amount, uint8 fromDecimals, uint8 toDecimals) internal pure returns (uint256) {
        if (fromDecimals == toDecimals) {
            return amount;
        } else if (fromDecimals < toDecimals) {
            return amount * (10 ** (toDecimals - fromDecimals));
        } else {
            return amount / (10 ** (fromDecimals - toDecimals));
        }
    }

    /**
     * @notice Converts to 18 decimals from a given decimal
     * @param amount The value to convert
     * @param fromDecimals Original number of decimals
     * @return The converted value
     */
    function to18Decimals(uint256 amount, uint8 fromDecimals) internal pure returns (uint256) {
        return convertDecimals(amount, fromDecimals, 18);
    }

    /**
     * @notice Returns the multipliers for the conversion
     * @param fromDecimals Original number of decimals
     * @param toDecimals Target number of decimals
     * @return numerator The numerator multiplier
     * @return denominator The denominator multiplier
     */
    function getMultipliers(
        uint8 fromDecimals,
        uint8 toDecimals
    )
        internal
        pure
        returns (uint256 numerator, uint256 denominator)
    {
        if (fromDecimals == toDecimals) {
            return (1, 1);
        } else if (fromDecimals < toDecimals) {
            return (10 ** (toDecimals - fromDecimals), 1);
        } else {
            return (1, 10 ** (fromDecimals - toDecimals));
        }
    }
}
"
    },
    "src/interfaces/spectra/ISpectraPT.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

interface ISpectraPT {
    function maturity() external view returns (uint256);

    function decimals() external view returns (uint8);

    function convertToUnderlying(uint256 amount) external view returns (uint256);

    function underlying() external view returns (address);
}
"
    },
    "src/EOBase.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

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

/**
 * @title EOBase
 * @author EO
 * @notice Base contract for EO contracts
 */
abstract contract EOBase is AggregatorV2V3Interface {
    /**
     * @notice Returns the latest answer
     * @return The latest answer
     */
    function latestAnswer() external view returns (int256) {
        (, int256 answer,,,) = latestRoundData();
        return answer;
    }

    /**
     * @notice Returns the latest update timestamp
     * @return The latest update timestamp
     */
    function latestTimestamp() external view returns (uint256) {
        (,,, uint256 updatedAt,) = latestRoundData();
        return updatedAt;
    }

    /**
     * @notice Returns the latest round ID
     * @return The latest round ID
     */
    function latestRound() external view returns (uint256) {
        (uint80 roundId,,,,) = latestRoundData();
        return uint256(roundId);
    }

    /**
     * @notice Returns the answer of the latest round
     * @param roundId The round ID (ignored)
     * @return The answer of the latest round
     */
    function getAnswer(uint256 roundId) external view returns (int256) {
        (, int256 answer,,,) = getRoundData(uint80(roundId));
        return answer;
    }

    /**
     * @notice Returns the update timestamp of the latest round
     * @param roundId The round ID (ignored)
     * @return The update timestamp of the latest round
     */
    function getTimestamp(uint256 roundId) external view returns (uint256) {
        (,,, uint256 updatedAt,) = getRoundData(uint80(roundId));
        return updatedAt;
    }

    /**
     * @notice Returns the minimum updated at, default to updatedAt in latestRoundData
     * @dev This method is valuable for complex feeds, where other feeds rates are involved into computation
     * and since updatedAt in latestRoundData is the latest updatedAt among the feeds, it may not reflect the data
     * freshness, so this method shows the earliest updatedAt among the feeds, and reflects the data freshness
     * @return The minimum updated at among the feeds used to compute the final rate
     */
    function minUpdatedAt() external view virtual returns (uint256) {
        (,,, uint256 updatedAt,) = latestRoundData();
        return updatedAt;
    }

    /**
     * @notice Returns the latest round data
     * @return roundId The latest round ID, optional
     * @return answer The latest answer
     * @return startedAt The timestamp when the round started, optional
     * @return updatedAt The timestamp of the latest update
     * @return answeredInRound The round ID in which the answer was computed, optional
     */
    function latestRoundData()
        public
        view
        virtual
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);

    /**
     * @notice Returns the latest round data
     * @param roundId The round ID, is ignored
     * @return roundId The round ID of   the latest round data
     * @return answer The answer of the latest round data
     * @return startedAt The started at of the latest round data
     * @return updatedAt The updated at of the latest round data
     * @return answeredInRound The round ID in which the answer was computed of the latest round data
     */
    function getRoundData(uint80)
        public
        view
        returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
    {
        return latestRoundData();
    }
}
"
    },
    "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reinitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
     *
     * NOTE: Consider following the ERC-7201 formula to derive storage locations.
     */
    function _initializableStorageSlot() internal pure virtual returns (bytes32) {
        return INITIALIZABLE_STORAGE;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        bytes32 slot = _initializableStorageSlot();
        assembly {
            $.slot := slot
        }
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

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);
}
"
    },
    "lib/spectra-core/lib/openzeppelin-contracts/contracts/utils/math/Math.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @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 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.
            return a / b;
        }

        // (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 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                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.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

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

            uint256 twos = denominator & (0 - denominator);
            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 (unsignedRoundsUp(rounding) && 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
     * towards zero.
     *
     * 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    

Tags:
ERC20, Proxy, Mintable, Pausable, Yield, Upgradeable, Factory, Oracle|addr:0x04b73039c24e9f9df1584c09e6fdfe1514564b30|verified:true|block:23675960|tx:0xc0b468a3db88b1a52d0fa11d45c80e6329ba35b407d1baf1f55369390770ea03|first_check:1761658591

Submitted on: 2025-10-28 14:36:33

Comments

Log in to comment.

No comments yet.