poolPrice() bugfix; burn() and mint() precision bugfixes

This commit is contained in:
tim
2025-12-01 15:42:12 -04:00
parent ea54059337
commit 4e56f54f27
6 changed files with 120 additions and 73 deletions

View File

@@ -776,19 +776,19 @@ library LMSRStabilized {
return _exp(qInternal[baseTokenIndex].sub(qInternal[quoteTokenIndex]).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) {
return poolPrice(s.kappa, s.qInternal, quoteTokenIndex);
/// @notice Total pool value denominated in `quote` asset (Q64.64, internal quote units)
/// @dev Computes: poolValue_quote = sum_j q_j * exp((q_j - q_quote) / b)
function poolValue(State storage s, uint256 quoteTokenIndex) internal view returns (int128) {
return poolValue(s.kappa, s.qInternal, quoteTokenIndex);
}
/// @notice Pure version: 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)
/// @notice Pure version: Total pool value denominated in `quote` asset (Q64.64, internal quote units)
/// @dev Computes: poolValue_quote = sum_j q_j * exp((q_j - q_quote) / b)
/// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param quoteTokenIndex Index of quote token
/// @return Pool price in 64.64 fixed-point format
function poolPrice(int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) {
/// @return Total pool value in 64.64 fixed-point format (internal quote units)
function poolValue(int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) {
// Compute b and ensure positivity
int128 sizeMetric = _computeSizeMetric(qInternal);
require(sizeMetric > int128(0), "LMSR: size metric zero");
@@ -814,8 +814,7 @@ library LMSRStabilized {
unchecked { j++; }
}
// pool price in units of quote = (1 / S) * acc
return acc.div(S);
return acc;
}
/* --------------------

View File

@@ -67,8 +67,12 @@ contract PartyInfo is PartyPoolHelpers, IPartyInfo {
require(nAssets > 0, "poolPrice: uninit");
require(quoteTokenIndex < nAssets, "poolPrice: idx");
// price per unit of qTotal (Q64.64) from LMSR
return LMSRStabilized.poolPrice( pool.kappa(), lmsr.qInternal, quoteTokenIndex);
// LMSR total value of pool in terms of quote token
int128 value = LMSRStabilized.poolValue(pool.kappa(), lmsr.qInternal, quoteTokenIndex);
uint256 qd = pool.denominators()[quoteTokenIndex];
uint256 supply = pool.totalSupply();
return value.mul(ABDKMath64x64.divu(qd * 10**18, supply));
}

View File

@@ -224,13 +224,13 @@ contract PartyPoolMintImpl is PartyPoolBase {
return depositAmounts; // Return zeros, initial deposit handled differently
}
// lpTokenAmount / totalLpSupply = depositAmount / currentBalance
// Therefore: depositAmount = (lpTokenAmount * currentBalance) / totalLpSupply
// We round up to protect the pool
// Compute mint ratio in Q64.64: ratio = lpTokenAmount / totalSupply
int128 ratio = ABDKMath64x64.divu(lpTokenAmount, totalSupply);
// depositAmount_i = ceil(ratio * currentBalance_i)
for (uint i = 0; i < numAssets; i++) {
uint256 currentBalance = cachedUintBalances[i];
// Calculate with rounding up: (a * b + c - 1) / c
depositAmounts[i] = (lpTokenAmount * currentBalance + totalSupply - 1) / totalSupply;
depositAmounts[i] = _internalToUintCeilPure(ratio, currentBalance);
}
return depositAmounts;
@@ -247,10 +247,10 @@ contract PartyPoolMintImpl is PartyPoolBase {
return withdrawAmounts; // Return zeros, nothing to withdraw
}
// withdrawAmount = floor(lpTokenAmount * currentBalance / totalLpSupply)
int128 ratio = ABDKMath64x64.divu(lpTokenAmount, totalSupply);
for (uint i = 0; i < numAssets; i++) {
uint256 currentBalance = cachedUintBalances[i];
withdrawAmounts[i] = (lpTokenAmount * currentBalance) / totalSupply;
withdrawAmounts[i] = ratio.mulu(currentBalance);
}
return withdrawAmounts;
@@ -536,7 +536,6 @@ contract PartyPoolMintImpl is PartyPoolBase {
IERC20 outputToken = _tokens[outputTokenIndex];
_sendTokenTo(outputToken, receiver, amountOut, unwrap);
// Update cached balances using computed payout and protocol fee; no on-chain reads
int128[] memory newQInternal = new int128[](n);
@@ -574,15 +573,24 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @notice Pure version of _internalToUintCeil for use in view functions
function _internalToUintCeilPure(int128 amount, uint256 base) internal pure returns (uint256) {
// Convert Q64.64 to uint with ceiling: ceil(amount * base)
// Use mulu which floors, then add remainder check for ceiling
// Fast path: compute floor using mulu, then detect fractional remainder via low 64-bit check
uint256 floored = ABDKMath64x64.mulu(amount, base);
// Check if there's a fractional part by computing amount * base - floored
int128 baseQ64 = ABDKMath64x64.fromUInt(base);
int128 flooredQ64 = ABDKMath64x64.fromUInt(floored);
int128 product = amount.mul(baseQ64);
if (product > flooredQ64) {
return floored + 1; // Ceiling
// Extract fractional 64 bits of `amount`; if zero, product is already an integer after scaling
uint64 frac = uint64(uint128(amount));
if (frac == 0) {
return floored;
}
unchecked {
// Remainder exists iff (frac * (base mod 2^64)) mod 2^64 != 0
uint64 baseL = uint64(base);
uint128 low = uint128(frac) * uint128(baseL);
if (uint64(low) != 0) {
return floored + 1; // Ceiling
}
}
return floored;
}