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.
*/
Submitted on: 2025-10-28 14:36:33
Comments
Log in to comment.
No comments yet.