LMSRStabilized pure refactor; swapMintAmounts
This commit is contained in:
@@ -64,8 +64,15 @@ library LMSRStabilized {
|
||||
|
||||
/// @notice Cost C(q) = b * (M + ln(Z))
|
||||
function cost(State storage s) internal view returns (int128) {
|
||||
int128 b = _computeB(s);
|
||||
(int128 M, int128 Z) = _computeMAndZ(b, s.qInternal);
|
||||
return cost(s.kappa, s.qInternal);
|
||||
}
|
||||
|
||||
/// @notice Pure version: Cost C(q) = b * (M + ln(Z))
|
||||
function cost(int128 kappa, int128[] memory qInternal) internal pure returns (int128) {
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
(int128 M, int128 Z) = _computeMAndZ(b, qInternal);
|
||||
int128 lnZ = _ln(Z);
|
||||
int128 inner = M.add(lnZ);
|
||||
int128 c = b.mul(inner);
|
||||
@@ -102,23 +109,58 @@ library LMSRStabilized {
|
||||
int128 a,
|
||||
int128 limitPrice
|
||||
) internal view returns (int128 amountIn, int128 amountOut) {
|
||||
require(i < s.nAssets && j < s.nAssets, "LMSR: idx");
|
||||
return swapAmountsForExactInput(s.nAssets, s.kappa, s.qInternal, i, j, a, limitPrice);
|
||||
}
|
||||
|
||||
/// @notice Pure version: Closed-form asset-i -> asset-j amountOut in 64.64 fixed-point format (fee-free kernel)
|
||||
/// Uses the closed-form two-asset LMSR formula (no fees in kernel):
|
||||
/// y = b * ln(1 + r0 * (1 - exp(-a / b)))
|
||||
/// where r0 = e_i / e_j.
|
||||
///
|
||||
/// This variant accepts an additional `limitPrice` (64.64) which represents the
|
||||
/// maximum acceptable marginal price (p_i / p_j). If the marginal price would
|
||||
/// exceed `limitPrice` before the requested `a` is fully consumed, the input
|
||||
/// `a` is truncated to the value that makes the marginal price equal `limitPrice`.
|
||||
///
|
||||
/// NOTE: Kernel is fee-free; fees should be handled by the wrapper/token layer.
|
||||
///
|
||||
/// @param nAssets Number of assets in the pool
|
||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||
/// @param i Index of input asset
|
||||
/// @param j Index of output asset
|
||||
/// @param a Amount of input asset (in int128 format, 64.64 fixed-point)
|
||||
/// @param limitPrice Maximum acceptable price ratio (64.64). If <= current price, this call reverts.
|
||||
/// @return amountIn Actual amount of input asset used (may be less than `a` if limited by price)
|
||||
/// @return amountOut Amount of output asset j in 64.64 fixed-point format
|
||||
function swapAmountsForExactInput(
|
||||
uint256 nAssets,
|
||||
int128 kappa,
|
||||
int128[] memory qInternal,
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
int128 a,
|
||||
int128 limitPrice
|
||||
) internal pure returns (int128 amountIn, int128 amountOut) {
|
||||
require(i < nAssets && j < nAssets, "LMSR: idx");
|
||||
|
||||
// Initialize amountIn to full amount (will be adjusted if limit price is hit)
|
||||
amountIn = a;
|
||||
|
||||
// Compute b and ensure positivity before deriving invB
|
||||
int128 b = _computeB(s);
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
|
||||
// Precompute reciprocal of b to avoid repeated divisions
|
||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||
|
||||
// Guard: output asset must have non-zero effective weight to avoid degenerate/div-by-zero-like conditions
|
||||
require(s.qInternal[j] > int128(0), "LMSR: e_j==0");
|
||||
require(qInternal[j] > int128(0), "LMSR: e_j==0");
|
||||
|
||||
// Compute r0 = exp((q_i - q_j) / b) directly using invB
|
||||
int128 r0 = _exp(s.qInternal[i].sub(s.qInternal[j]).mul(invB));
|
||||
int128 r0 = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
||||
require(r0 > int128(0), "LMSR: r0<=0"); // equivalent to e_j > 0 check
|
||||
|
||||
// If a positive limitPrice is given, determine whether the full `a` would
|
||||
@@ -203,7 +245,7 @@ library LMSRStabilized {
|
||||
// If inner <= 0 then cap output to the current balance q_j (cannot withdraw more than q_j)
|
||||
if (inner <= int128(0)) {
|
||||
console2.log("WARNING: inner <= 0, capping output to balance q_j");
|
||||
int128 qj64 = s.qInternal[j];
|
||||
int128 qj64 = qInternal[j];
|
||||
console2.log("Capped output (64.64):");
|
||||
console2.logInt(qj64);
|
||||
return (amountIn, qj64);
|
||||
@@ -249,21 +291,48 @@ library LMSRStabilized {
|
||||
uint256 j,
|
||||
int128 limitPrice
|
||||
) internal view returns (int128 amountIn, int128 amountOut) {
|
||||
require(i < s.nAssets && j < s.nAssets, "LMSR: idx");
|
||||
return swapAmountsForPriceLimit(s.nAssets, s.kappa, s.qInternal, i, j, limitPrice);
|
||||
}
|
||||
|
||||
/// @notice Pure version: Maximum input/output pair possible when swapping from asset i to asset j
|
||||
/// given a maximum acceptable price ratio (p_i/p_j).
|
||||
/// Returns the input amount that would drive the marginal price to the limit (amountIn)
|
||||
/// and the corresponding output amount (amountOut). If the output would exceed the
|
||||
/// j-balance, amountOut is capped and amountIn is solved for the capped output.
|
||||
///
|
||||
/// @param nAssets Number of assets in the pool
|
||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||
/// @param i Index of input asset
|
||||
/// @param j Index of output asset
|
||||
/// @param limitPrice Maximum acceptable price ratio (64.64)
|
||||
/// @return amountIn Maximum input amount in 64.64 fixed-point format that reaches the price limit
|
||||
/// @return amountOut Corresponding maximum output amount in 64.64 fixed-point format
|
||||
function swapAmountsForPriceLimit(
|
||||
uint256 nAssets,
|
||||
int128 kappa,
|
||||
int128[] memory qInternal,
|
||||
uint256 i,
|
||||
uint256 j,
|
||||
int128 limitPrice
|
||||
) internal pure returns (int128 amountIn, int128 amountOut) {
|
||||
require(i < nAssets && j < nAssets, "LMSR: idx");
|
||||
require(limitPrice > int128(0), "LMSR: limitPrice <= 0");
|
||||
|
||||
// Compute b and ensure positivity before deriving invB
|
||||
int128 b = _computeB(s);
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
|
||||
// Precompute reciprocal of b to avoid repeated divisions
|
||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||
|
||||
// Guard: output asset must have non-zero effective weight to avoid degenerate/div-by-zero-like conditions
|
||||
require(s.qInternal[j] > int128(0), "LMSR: e_j==0");
|
||||
require(qInternal[j] > int128(0), "LMSR: e_j==0");
|
||||
|
||||
// Compute r0 = exp((q_i - q_j) / b) directly using invB
|
||||
int128 r0 = _exp(s.qInternal[i].sub(s.qInternal[j]).mul(invB));
|
||||
int128 r0 = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
||||
|
||||
console2.log("\n=== Max Input/Output Calculation ===");
|
||||
console2.log("Limit price (64x64):");
|
||||
@@ -316,7 +385,7 @@ library LMSRStabilized {
|
||||
console2.logInt(maxOutput);
|
||||
|
||||
// Current balance of asset j (in 64.64)
|
||||
int128 qj64 = s.qInternal[j];
|
||||
int128 qj64 = qInternal[j];
|
||||
console2.log("Current j balance (64.64):");
|
||||
console2.logInt(qj64);
|
||||
|
||||
@@ -366,20 +435,45 @@ library LMSRStabilized {
|
||||
uint256 i,
|
||||
int128 a
|
||||
) internal view returns (int128 amountIn, int128 amountOut) {
|
||||
require(i < s.nAssets, "LMSR: idx");
|
||||
return swapAmountsForMint(s.nAssets, s.kappa, s.qInternal, i, a);
|
||||
}
|
||||
|
||||
/// @notice Pure version: Compute LP-size increase when minting from a single-token input using bisection only.
|
||||
/// @dev Solve for α >= 0 such that:
|
||||
/// a = α*q_i + sum_{j != i} x_j(α)
|
||||
/// where x_j(α) is the input to swap i->j that yields y_j = α*q_j and
|
||||
/// x_j = b * ln( r0_j / (r0_j + 1 - exp(y_j / b)) ), r0_j = exp((q_i - q_j)/b).
|
||||
/// Bisection is used (no Newton) to keep implementation compact and gas-friendly.
|
||||
/// @param nAssets Number of assets in the pool
|
||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||
/// @param i Index of input asset
|
||||
/// @param a Amount of input asset (in int128 format, 64.64 fixed-point)
|
||||
/// @return amountIn Actual amount of input consumed
|
||||
/// @return amountOut LP size-metric increase (alpha * S)
|
||||
function swapAmountsForMint(
|
||||
uint256 nAssets,
|
||||
int128 kappa,
|
||||
int128[] memory qInternal,
|
||||
uint256 i,
|
||||
int128 a
|
||||
) internal pure returns (int128 amountIn, int128 amountOut) {
|
||||
require(i < nAssets, "LMSR: idx");
|
||||
require(a > int128(0), "LMSR: amount <= 0");
|
||||
|
||||
int128 b = _computeB(s);
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||
int128 S = _computeSizeMetric(s.qInternal);
|
||||
int128 S = sizeMetric;
|
||||
|
||||
uint256 n = s.nAssets;
|
||||
uint256 n = nAssets;
|
||||
|
||||
// Precompute r0_j = exp((q_i - q_j) / b) for all j to avoid recomputing during search.
|
||||
int128[] memory r0 = new int128[](n);
|
||||
for (uint256 j = 0; j < n; ) {
|
||||
r0[j] = _exp(s.qInternal[i].sub(s.qInternal[j]).mul(invB));
|
||||
r0[j] = _exp(qInternal[i].sub(qInternal[j]).mul(invB));
|
||||
unchecked { j++; }
|
||||
}
|
||||
|
||||
@@ -415,7 +509,7 @@ library LMSRStabilized {
|
||||
// loop j != i
|
||||
for (uint256 j = 0; j < n; ) {
|
||||
if (j != i) {
|
||||
int128 yj = alpha.mul(s.qInternal[j]); // target output y_j = alpha * q_j
|
||||
int128 yj = alpha.mul(qInternal[j]); // target output y_j = alpha * q_j
|
||||
if (yj > int128(0)) {
|
||||
int128 expArg = yj.mul(invB);
|
||||
// Guard exp arg
|
||||
@@ -433,7 +527,7 @@ library LMSRStabilized {
|
||||
unchecked { j++; }
|
||||
}
|
||||
|
||||
int128 aReq = fail ? int128(type(int128).max) : alpha.mul(s.qInternal[i]).add(sumX);
|
||||
int128 aReq = fail ? int128(type(int128).max) : alpha.mul(qInternal[i]).add(sumX);
|
||||
|
||||
if (aReq >= a || high >= alphaCap) {
|
||||
break;
|
||||
@@ -455,7 +549,7 @@ library LMSRStabilized {
|
||||
|
||||
for (uint256 j = 0; j < n; ) {
|
||||
if (j != i) {
|
||||
int128 yj = alpha.mul(s.qInternal[j]);
|
||||
int128 yj = alpha.mul(qInternal[j]);
|
||||
if (yj > int128(0)) {
|
||||
int128 expArg = yj.mul(invB);
|
||||
if (expArg > EXP_LIMIT) { fail = true; break; }
|
||||
@@ -472,7 +566,7 @@ library LMSRStabilized {
|
||||
unchecked { j++; }
|
||||
}
|
||||
|
||||
int128 aReq = fail ? int128(type(int128).max) : alpha.mul(s.qInternal[i]).add(sumX);
|
||||
int128 aReq = fail ? int128(type(int128).max) : alpha.mul(qInternal[i]).add(sumX);
|
||||
|
||||
if (aReq > a) {
|
||||
// mid requires more input than provided -> decrease alpha
|
||||
@@ -502,7 +596,7 @@ library LMSRStabilized {
|
||||
bool failFinal = false;
|
||||
for (uint256 j = 0; j < n; ) {
|
||||
if (j != i) {
|
||||
int128 yj = alphaFinal.mul(s.qInternal[j]);
|
||||
int128 yj = alphaFinal.mul(qInternal[j]);
|
||||
if (yj > int128(0)) {
|
||||
int128 expArg = yj.mul(invB);
|
||||
if (expArg > EXP_LIMIT) { failFinal = true; break; }
|
||||
@@ -524,7 +618,7 @@ library LMSRStabilized {
|
||||
return (int128(0), int128(0));
|
||||
}
|
||||
|
||||
int128 aRequired = alphaFinal.mul(s.qInternal[i]).add(sumXFinal);
|
||||
int128 aRequired = alphaFinal.mul(qInternal[i]).add(sumXFinal);
|
||||
|
||||
// amountIn is actual consumed input (may be <= provided a)
|
||||
amountIn = aRequired;
|
||||
@@ -553,28 +647,56 @@ library LMSRStabilized {
|
||||
uint256 i,
|
||||
int128 alpha
|
||||
) internal view returns (int128 amountOut, int128 amountIn) {
|
||||
require(i < s.nAssets, "LMSR: idx");
|
||||
return swapAmountsForBurn(s.nAssets, s.kappa, s.qInternal, i, alpha);
|
||||
}
|
||||
|
||||
/// @notice Pure version: Compute single-asset payout when burning a proportional share alpha of the pool.
|
||||
/// @dev Simulate q_after = (1 - alpha) * q, return the amount of asset `i` the burner
|
||||
/// would receive after swapping each other asset's withdrawn portion into `i`.
|
||||
/// For each j != i:
|
||||
/// - wrapper holds a_j = alpha * q_j
|
||||
/// - swap j->i with closed-form exact-input formula using the current q_local
|
||||
/// - cap output to q_local[i] when necessary (solve inverse for input used)
|
||||
/// Treat any per-asset rhs<=0 as "this asset contributes zero" (do not revert).
|
||||
/// Revert only if the final single-asset payout is zero.
|
||||
/// @param nAssets Number of assets in the pool
|
||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||
/// @param i Index of output asset
|
||||
/// @param alpha Proportional share to burn (0 < alpha <= 1)
|
||||
/// @return amountOut Amount of asset i received (in 64.64 fixed-point)
|
||||
/// @return amountIn LP size-metric redeemed (alpha * S)
|
||||
function swapAmountsForBurn(
|
||||
uint256 nAssets,
|
||||
int128 kappa,
|
||||
int128[] memory qInternal,
|
||||
uint256 i,
|
||||
int128 alpha
|
||||
) internal pure returns (int128 amountOut, int128 amountIn) {
|
||||
require(i < nAssets, "LMSR: idx");
|
||||
require(alpha > int128(0) && alpha <= ONE, "LMSR: alpha");
|
||||
|
||||
int128 b = _computeB(s);
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||
|
||||
uint256 n = s.nAssets;
|
||||
uint256 n = nAssets;
|
||||
|
||||
// Size metric and burned size (amountIn returned)
|
||||
int128 S = _computeSizeMetric(s.qInternal);
|
||||
int128 S = sizeMetric;
|
||||
amountIn = alpha.mul(S); // total size-metric redeemed
|
||||
|
||||
// Build q_local := q_after_burn = (1 - alpha) * q
|
||||
int128[] memory qLocal = new int128[](n);
|
||||
for (uint256 j = 0; j < n; ) {
|
||||
qLocal[j] = s.qInternal[j].mul(ONE.sub(alpha));
|
||||
qLocal[j] = qInternal[j].mul(ONE.sub(alpha));
|
||||
unchecked { j++; }
|
||||
}
|
||||
|
||||
// Start totalOut with direct portion of asset i redeemed
|
||||
int128 totalOut = alpha.mul(s.qInternal[i]);
|
||||
int128 totalOut = alpha.mul(qInternal[i]);
|
||||
|
||||
// Track whether any non-zero contribution was produced
|
||||
bool anyNonZero = (totalOut > int128(0));
|
||||
@@ -582,7 +704,7 @@ library LMSRStabilized {
|
||||
// For each asset j != i, swap the withdrawn a_j := alpha * q_j into i
|
||||
for (uint256 j = 0; j < n; ) {
|
||||
if (j != i) {
|
||||
int128 aj = alpha.mul(s.qInternal[j]); // wrapper-held withdrawn amount of j
|
||||
int128 aj = alpha.mul(qInternal[j]); // wrapper-held withdrawn amount of j
|
||||
if (aj > int128(0)) {
|
||||
// expArg = aj / b
|
||||
int128 expArg = aj.mul(invB);
|
||||
@@ -722,15 +844,26 @@ library LMSRStabilized {
|
||||
|
||||
/// @notice Price-share of asset i: exp(z_i) / Z (64.64)
|
||||
function priceShare(State storage s, uint256 i) internal view returns (int128) {
|
||||
int128 b = _computeB(s);
|
||||
uint len = s.qInternal.length;
|
||||
return priceShare(s.kappa, s.qInternal, i);
|
||||
}
|
||||
|
||||
/// @notice Pure version: Price-share of asset i: exp(z_i) / Z (64.64)
|
||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||
/// @param i Index of asset
|
||||
/// @return Price share in 64.64 fixed-point format
|
||||
function priceShare(int128 kappa, int128[] memory qInternal, uint256 i) internal pure returns (int128) {
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
uint len = qInternal.length;
|
||||
require(len > 0, "LMSR: no assets");
|
||||
|
||||
// Precompute reciprocal of b and perform a single pass that tracks M, Z, and e_i
|
||||
int128 invB = ABDKMath64x64.div(ONE, b);
|
||||
|
||||
// Initialize from the first element
|
||||
int128 M = s.qInternal[0].mul(invB);
|
||||
int128 M = qInternal[0].mul(invB);
|
||||
int128 Z = ONE; // exp(0)
|
||||
int128 e_i_acc;
|
||||
bool setEi;
|
||||
@@ -741,7 +874,7 @@ library LMSRStabilized {
|
||||
}
|
||||
|
||||
for (uint idx = 1; idx < len; ) {
|
||||
int128 yi = s.qInternal[idx].mul(invB);
|
||||
int128 yi = qInternal[idx].mul(invB);
|
||||
if (yi <= M) {
|
||||
// Add contribution under current center
|
||||
int128 term = _exp(yi.sub(M));
|
||||
@@ -770,7 +903,7 @@ library LMSRStabilized {
|
||||
if (!setEi) {
|
||||
// Only possible when len == 1 and i != 0, guarded by caller invariants typically
|
||||
// Fallback: compute directly (kept for completeness)
|
||||
int128 yi = s.qInternal[i].mul(invB);
|
||||
int128 yi = qInternal[i].mul(invB);
|
||||
e_i_acc = _exp(yi.sub(M));
|
||||
}
|
||||
|
||||
@@ -780,27 +913,54 @@ library LMSRStabilized {
|
||||
/// @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);
|
||||
return price(s.nAssets, s.kappa, s.qInternal, baseTokenIndex, quoteTokenIndex);
|
||||
}
|
||||
|
||||
/// @notice Pure version: 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.
|
||||
/// @param nAssets Number of assets in the pool
|
||||
/// @param kappa Liquidity parameter κ (64.64 fixed point)
|
||||
/// @param qInternal Cached internal balances in 64.64 fixed-point format
|
||||
/// @param baseTokenIndex Index of base token
|
||||
/// @param quoteTokenIndex Index of quote token
|
||||
/// @return Price in 64.64 fixed-point format
|
||||
function price(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal pure returns (int128) {
|
||||
require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "LMSR: idx");
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
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));
|
||||
return _exp(qInternal[quoteTokenIndex].sub(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");
|
||||
return poolPrice(s.nAssets, 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)
|
||||
/// @param nAssets Number of assets in the pool
|
||||
/// @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(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) {
|
||||
require(quoteTokenIndex < nAssets, "LMSR: idx");
|
||||
// Compute b and ensure positivity
|
||||
int128 b = _computeB(s);
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
int128 b = kappa.mul(sizeMetric);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
|
||||
// Compute total size metric S = sum q_i
|
||||
int128 S = _computeSizeMetric(s.qInternal);
|
||||
int128 S = sizeMetric;
|
||||
require(S > int128(0), "LMSR: size zero");
|
||||
|
||||
// Precompute reciprocal of b
|
||||
@@ -808,12 +968,12 @@ library LMSRStabilized {
|
||||
|
||||
// Accumulate weighted exponentials: sum_j q_j * exp((q_j - q_quote) / b)
|
||||
int128 acc = int128(0);
|
||||
uint256 n = s.nAssets;
|
||||
uint256 n = 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));
|
||||
int128 factor = _exp(qInternal[j].sub(qInternal[quoteTokenIndex]).mul(invB));
|
||||
// term = q_j * factor
|
||||
int128 term = s.qInternal[j].mul(factor);
|
||||
int128 term = qInternal[j].mul(factor);
|
||||
acc = acc.add(term);
|
||||
unchecked { j++; }
|
||||
}
|
||||
|
||||
@@ -128,37 +128,14 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
||||
/// @inheritdoc IPartyPool
|
||||
function initialMint(address receiver, uint256 lpTokens) external nonReentrant
|
||||
returns (uint256 lpMinted) {
|
||||
uint256 n = tokens.length;
|
||||
|
||||
// Check if this is initial deposit - revert if not
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
require(isInitialDeposit, "initialMint: pool already initialized");
|
||||
|
||||
// Update cached balances for all assets
|
||||
int128[] memory newQInternal = new int128[](n);
|
||||
uint256[] memory depositAmounts = new uint256[](n);
|
||||
for (uint i = 0; i < n; ) {
|
||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||
cachedUintBalances[i] = bal;
|
||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||
depositAmounts[i] = bal;
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Initialize the stabilized LMSR state with provided kappa
|
||||
lmsr.init(newQInternal, KAPPA);
|
||||
|
||||
// Compute actual LP tokens to mint based on size metric (scaled)
|
||||
if( lpTokens != 0 )
|
||||
lpMinted = lpTokens;
|
||||
else {
|
||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||
lpMinted = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
}
|
||||
|
||||
require(lpMinted > 0, "initialMint: zero LP amount");
|
||||
_mint(receiver, lpMinted);
|
||||
emit Mint(address(0), receiver, depositAmounts, lpMinted);
|
||||
bytes memory data = abi.encodeWithSignature(
|
||||
"initialMint(address,uint256,int128)",
|
||||
receiver,
|
||||
lpTokens,
|
||||
KAPPA
|
||||
);
|
||||
bytes memory result = Address.functionDelegateCall(address(MINT_IMPL), data);
|
||||
return abi.decode(result, (uint256));
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
@@ -460,11 +437,29 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
||||
return netUint + fee;
|
||||
}
|
||||
|
||||
// --- New events for single-token mint/burn flows ---
|
||||
// Note: events intentionally avoid exposing internal ΔS and avoid duplicating LP mint/burn data
|
||||
// which is already present in the standard Mint/Burn events.
|
||||
function swapMintAmounts(uint256 inputTokenIndex, uint256 maxAmountIn) external view
|
||||
returns (uint256 amountInUsed, uint256 fee, uint256 lpMinted) {
|
||||
return SWAP_MINT_IMPL.swapMintAmounts(
|
||||
inputTokenIndex,
|
||||
maxAmountIn,
|
||||
SWAP_FEE_PPM,
|
||||
lmsr,
|
||||
bases,
|
||||
totalSupply()
|
||||
);
|
||||
}
|
||||
|
||||
// todo swapMintAmounts and burnSwapAmounts
|
||||
function burnSwapAmounts(uint256 lpAmount, uint256 inputTokenIndex) external view
|
||||
returns (uint256 amountOut) {
|
||||
return SWAP_MINT_IMPL.burnSwapAmounts(
|
||||
lpAmount,
|
||||
inputTokenIndex,
|
||||
SWAP_FEE_PPM,
|
||||
lmsr,
|
||||
bases,
|
||||
totalSupply()
|
||||
);
|
||||
}
|
||||
|
||||
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
||||
/// @dev This function forwards the call to the swapMint implementation via delegatecall
|
||||
|
||||
@@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "./PartyPoolBase.sol";
|
||||
import "./LMSRStabilized.sol";
|
||||
import {PartyPool} from "./PartyPool.sol";
|
||||
|
||||
/// @title PartyPoolMintImpl - Implementation contract for mint and burn functions
|
||||
/// @notice This contract contains the mint and burn implementation that will be called via delegatecall
|
||||
@@ -21,14 +22,41 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
||||
|
||||
constructor() PartyPoolBase('','') {}
|
||||
|
||||
/// @notice Proportional mint for existing pool.
|
||||
/// @dev Payer must approve the required token amounts before calling.
|
||||
/// Can only be called when pool is already initialized (totalSupply() > 0 and lmsr.nAssets > 0).
|
||||
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
|
||||
/// @param payer address that provides the input tokens
|
||||
/// @param receiver address that receives the LP tokens
|
||||
/// @param lpTokenAmount desired amount of LP tokens to mint
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external
|
||||
returns (uint256 lpMinted) {
|
||||
uint256 n = tokens.length;
|
||||
|
||||
// Check if this is initial deposit - revert if not
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
require(isInitialDeposit, "initialMint: pool already initialized");
|
||||
|
||||
// Update cached balances for all assets
|
||||
int128[] memory newQInternal = new int128[](n);
|
||||
uint256[] memory depositAmounts = new uint256[](n);
|
||||
for (uint i = 0; i < n; ) {
|
||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||
cachedUintBalances[i] = bal;
|
||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||
depositAmounts[i] = bal;
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Initialize the stabilized LMSR state with provided kappa
|
||||
lmsr.init(newQInternal, KAPPA);
|
||||
|
||||
// Compute actual LP tokens to mint based on size metric (scaled)
|
||||
if( lpTokens != 0 )
|
||||
lpMinted = lpTokens;
|
||||
else {
|
||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||
lpMinted = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
}
|
||||
|
||||
require(lpMinted > 0, "initialMint: zero LP amount");
|
||||
_mint(receiver, lpMinted);
|
||||
emit Mint(address(0), receiver, depositAmounts, lpMinted);
|
||||
}
|
||||
|
||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external returns (uint256 lpMinted) {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
||||
uint256 n = tokens.length;
|
||||
|
||||
@@ -125,6 +125,117 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
||||
return actualLpToMint;
|
||||
}
|
||||
|
||||
/// @notice Calculate the amounts for a swap mint operation
|
||||
/// @dev This is a pure view function that computes swap mint amounts from provided state
|
||||
/// @param inputTokenIndex index of the input token
|
||||
/// @param maxAmountIn maximum amount of token to deposit (inclusive of fee)
|
||||
/// @param swapFeePpm fee in parts-per-million
|
||||
/// @param lmsrState current LMSR state
|
||||
/// @param bases_ scaling bases for each token
|
||||
/// @param totalSupply_ current total LP token supply
|
||||
/// @return amountInUsed actual input amount used (excluding fee)
|
||||
/// @return fee fee amount charged
|
||||
/// @return lpMinted LP tokens that would be minted
|
||||
function swapMintAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
uint256 swapFeePpm,
|
||||
LMSRStabilized.State memory lmsrState,
|
||||
uint256[] memory bases_,
|
||||
uint256 totalSupply_
|
||||
) public pure returns (uint256 amountInUsed, uint256 fee, uint256 lpMinted) {
|
||||
require(inputTokenIndex < bases_.length, "swapMintAmounts: idx");
|
||||
require(maxAmountIn > 0, "swapMintAmounts: input zero");
|
||||
require(lmsrState.nAssets > 0, "swapMintAmounts: uninit pool");
|
||||
|
||||
// Compute fee on gross maxAmountIn to get an initial net estimate
|
||||
uint256 feeGuess = 0;
|
||||
uint256 netUintGuess = maxAmountIn;
|
||||
if (swapFeePpm > 0) {
|
||||
feeGuess = (maxAmountIn * swapFeePpm + 999999) / 1000000; // ceil fee
|
||||
netUintGuess = maxAmountIn - feeGuess;
|
||||
}
|
||||
|
||||
// Convert the net guess to internal (floor)
|
||||
int128 netInternalGuess = _uintToInternalFloorPure(netUintGuess, bases_[inputTokenIndex]);
|
||||
require(netInternalGuess > int128(0), "swapMintAmounts: input too small after fee");
|
||||
|
||||
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
|
||||
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) =
|
||||
LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
||||
inputTokenIndex, netInternalGuess);
|
||||
|
||||
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
|
||||
amountInUsed = _internalToUintCeilPure(amountInInternalUsed, bases_[inputTokenIndex]);
|
||||
require(amountInUsed > 0, "swapMintAmounts: input zero after internal conversion");
|
||||
|
||||
// Compute fee on the actual used input (ceiling)
|
||||
fee = 0;
|
||||
if (swapFeePpm > 0) {
|
||||
fee = (amountInUsed * swapFeePpm + 999999) / 1000000; // ceil fee
|
||||
}
|
||||
uint256 totalTransfer = amountInUsed + fee;
|
||||
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMintAmounts: transfer exceeds max");
|
||||
|
||||
// Compute old and new scaled size metrics to determine LP minted
|
||||
int128 oldTotal = _computeSizeMetricPure(lmsrState.qInternal);
|
||||
require(oldTotal > int128(0), "swapMintAmounts: zero total");
|
||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||
|
||||
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
|
||||
if (totalSupply_ == 0) {
|
||||
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
||||
lpMinted = newScaled;
|
||||
} else {
|
||||
require(oldScaled > 0, "swapMintAmounts: oldScaled zero");
|
||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||
if (delta > 0) {
|
||||
// floor truncation rounds in favor of pool
|
||||
lpMinted = (totalSupply_ * delta) / oldScaled;
|
||||
} else {
|
||||
lpMinted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
require(lpMinted > 0, "swapMintAmounts: zero LP minted");
|
||||
}
|
||||
|
||||
/// @notice Calculate the amounts for a burn swap operation
|
||||
/// @dev This is a pure view function that computes burn swap amounts from provided state
|
||||
/// @param lpAmount amount of LP tokens to burn
|
||||
/// @param inputTokenIndex index of target asset to receive
|
||||
/// @param swapFeePpm fee in parts-per-million
|
||||
/// @param lmsrState current LMSR state
|
||||
/// @param bases_ scaling bases for each token
|
||||
/// @param totalSupply_ current total LP token supply
|
||||
/// @return amountOut amount of target asset that would be received
|
||||
function burnSwapAmounts(
|
||||
uint256 lpAmount,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 swapFeePpm,
|
||||
LMSRStabilized.State memory lmsrState,
|
||||
uint256[] memory bases_,
|
||||
uint256 totalSupply_
|
||||
) public pure returns (uint256 amountOut) {
|
||||
require(inputTokenIndex < bases_.length, "burnSwapAmounts: idx");
|
||||
require(lpAmount > 0, "burnSwapAmounts: zero lp");
|
||||
require(totalSupply_ > 0, "burnSwapAmounts: empty supply");
|
||||
|
||||
// alpha = lpAmount / supply as Q64.64
|
||||
int128 alpha = ABDKMath64x64.divu(lpAmount, totalSupply_) // fraction of total supply to burn
|
||||
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
|
||||
|
||||
// Use LMSR view to compute single-asset payout and burned size-metric
|
||||
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
|
||||
inputTokenIndex, alpha);
|
||||
|
||||
// Convert payoutInternal -> uint (floor) to favor pool
|
||||
amountOut = _internalToUintFloorPure(payoutInternal, bases_[inputTokenIndex]);
|
||||
require(amountOut > 0, "burnSwapAmounts: output zero");
|
||||
}
|
||||
|
||||
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver.
|
||||
/// @dev The function burns LP tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state.
|
||||
/// @param payer who burns LP tokens
|
||||
@@ -198,4 +309,40 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
||||
emit Burn(payer, receiver, new uint256[](n), lpAmount);
|
||||
return amountOutUint;
|
||||
}
|
||||
|
||||
/// @notice Pure version of _uintToInternalFloor for use in view functions
|
||||
function _uintToInternalFloorPure(uint256 amount, uint256 base) internal pure returns (int128) {
|
||||
// amount / base as Q64.64, floored
|
||||
return ABDKMath64x64.divu(amount, base);
|
||||
}
|
||||
|
||||
/// @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
|
||||
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
|
||||
}
|
||||
return floored;
|
||||
}
|
||||
|
||||
/// @notice Pure version of _internalToUintFloor for use in view functions
|
||||
function _internalToUintFloorPure(int128 amount, uint256 base) internal pure returns (uint256) {
|
||||
// Convert Q64.64 to uint with floor: floor(amount * base)
|
||||
return ABDKMath64x64.mulu(amount, base);
|
||||
}
|
||||
|
||||
/// @notice Pure version of _computeSizeMetric for use in view functions
|
||||
function _computeSizeMetricPure(int128[] memory qInternal) internal pure returns (int128) {
|
||||
int128 sum = int128(0);
|
||||
for (uint256 i = 0; i < qInternal.length; i++) {
|
||||
sum = sum.add(qInternal[i]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user