diff --git a/src/IPartyPool.sol b/src/IPartyPool.sol index 7d27eda..dd0b664 100644 --- a/src/IPartyPool.sol +++ b/src/IPartyPool.sol @@ -224,6 +224,22 @@ interface IPartyPool is IERC20Metadata { uint256 deadline ) external returns (uint256 amountOutUint); + /// @notice Marginal price of `base` denominated in `quote` as Q64.64. + /// @dev Returns the LMSR marginal price p_quote / p_base in ABDK 64.64 fixed-point format. + /// Useful for off-chain quoting; raw 64.64 value is returned (no scaling to token units). + /// @param baseTokenIndex index of the base asset (e.g., ETH) + /// @param quoteTokenIndex index of the quote asset (e.g., USD) + /// @return price Q64.64 value equal to quote per base (p_quote / p_base) + function price(uint256 baseTokenIndex, uint256 quoteTokenIndex) external view returns (int128); + + /// @notice Price of one LP token denominated in `quote` as Q64.64. + /// @dev Computes LMSR poolPrice (quote per unit internal qTotal) and scales it to LP units: + /// returns price_per_LP = poolPrice_quote * (totalSupply() / qTotal) in ABDK 64.64 format. + /// The returned value is raw Q64.64 and represents quote units per one LP token unit. + /// @param quoteTokenIndex index of the quote asset in which to denominate the LP price + /// @return price Q64.64 value equal to quote per LP token unit + function poolPrice(uint256 quoteTokenIndex) external view returns (int128); + /// @notice Compute repayment amounts (principal + flash fee) for a proposed flash loan. /// @param loanAmounts array of per-token loan amounts; must match the pool's token ordering. /// @return repaymentAmounts array where repaymentAmounts[i] = loanAmounts[i] + ceil(loanAmounts[i] * flashFeePpm) diff --git a/src/LMSRStabilized.sol b/src/LMSRStabilized.sol index ac6c925..bd77280 100644 --- a/src/LMSRStabilized.sol +++ b/src/LMSRStabilized.sol @@ -778,6 +778,51 @@ library LMSRStabilized { return e_i_acc.div(Z); } + /// @notice Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64 + /// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0. + function price(State storage s, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal view returns (int128) { + require(baseTokenIndex < s.nAssets && quoteTokenIndex < s.nAssets, "LMSR: idx"); + int128 b = _computeB(s); + require(b > int128(0), "LMSR: b<=0"); + + // Use reciprocal of b to avoid repeated divisions + int128 invB = ABDKMath64x64.div(ONE, b); + + // Marginal price p_quote / p_base = exp((q_quote - q_base) / b) + return _exp(s.qInternal[quoteTokenIndex].sub(s.qInternal[baseTokenIndex]).mul(invB)); + } + + /// @notice Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64) + /// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b) + function poolPrice(State storage s, uint256 quoteTokenIndex) internal view returns (int128) { + require(quoteTokenIndex < s.nAssets, "LMSR: idx"); + // Compute b and ensure positivity + int128 b = _computeB(s); + require(b > int128(0), "LMSR: b<=0"); + + // Compute total size metric S = sum q_i + int128 S = _computeSizeMetric(s.qInternal); + require(S > int128(0), "LMSR: size zero"); + + // Precompute reciprocal of b + int128 invB = ABDKMath64x64.div(ONE, b); + + // Accumulate weighted exponentials: sum_j q_j * exp((q_j - q_quote) / b) + int128 acc = int128(0); + uint256 n = s.nAssets; + for (uint256 j = 0; j < n; ) { + // factor = exp((q_j - q_quote) / b) + int128 factor = _exp(s.qInternal[j].sub(s.qInternal[quoteTokenIndex]).mul(invB)); + // term = q_j * factor + int128 term = s.qInternal[j].mul(factor); + acc = acc.add(term); + unchecked { j++; } + } + + // pool price in units of quote = (1 / S) * acc + return acc.div(S); + } + /* -------------------- Slippage -> b computation & resize-triggered rescale -------------------- */ diff --git a/src/PartyPool.sol b/src/PartyPool.sol index ee903fe..1f09775 100644 --- a/src/PartyPool.sol +++ b/src/PartyPool.sol @@ -927,6 +927,42 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard { return floorValue; } + /// @notice Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64 + /// @dev Returns the LMSR marginal price directly (raw 64.64) for use by off-chain quoting logic. + function price(uint256 baseTokenIndex, uint256 quoteTokenIndex) external view returns (int128) { + uint256 n = tokens.length; + require(baseTokenIndex < n && quoteTokenIndex < n, "price: idx"); + require(lmsr.nAssets > 0, "price: uninit"); + return lmsr.price(baseTokenIndex, quoteTokenIndex); + } + + /// @notice Price of one LP token denominated in `quote` asset as Q64.64 + /// @dev Computes LMSR poolPrice (quote per unit qTotal) and scales it by totalSupply() / qTotal + /// to return price per LP token unit in quote asset (raw 64.64). + function poolPrice(uint256 quoteTokenIndex) external view returns (int128) { + uint256 n = tokens.length; + require(quoteTokenIndex < n, "poolPrice: idx"); + require(lmsr.nAssets > 0, "poolPrice: uninit"); + + // price per unit of qTotal (Q64.64) from LMSR + int128 pricePerQ = lmsr.poolPrice(quoteTokenIndex); + + // total internal q (qTotal) as Q64.64 + int128 qTotal = _computeSizeMetric(lmsr.qInternal); + require(qTotal > int128(0), "poolPrice: qTotal zero"); + + // totalSupply as Q64.64 + uint256 supply = totalSupply(); + require(supply > 0, "poolPrice: zero supply"); + int128 supplyQ64 = ABDKMath64x64.fromUInt(supply); + + // factor = totalSupply / qTotal (Q64.64) + int128 factor = supplyQ64.div(qTotal); + + // price per LP token = pricePerQ * factor (Q64.64) + return pricePerQ.mul(factor); + } + /// @notice Helper to compute size metric (sum of all asset quantities) from internal balances /// @dev Returns the sum of all provided qInternal_ entries as a Q64.64 value. function _computeSizeMetric(int128[] memory qInternal_) private pure returns (int128) {