LMSRStabilized pure refactor; swapMintAmounts

This commit is contained in:
tim
2025-10-01 17:08:02 -04:00
parent a6f6fd034c
commit 5a2e7039d1
4 changed files with 417 additions and 87 deletions

View File

@@ -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++; }
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}
}