swapMintAmounts

This commit is contained in:
tim
2025-09-29 17:17:23 -04:00
parent 8e69bfac5c
commit e5b2577ba9
3 changed files with 135 additions and 57 deletions

View File

@@ -188,6 +188,15 @@ interface IPartyPool is IERC20Metadata {
uint256 deadline uint256 deadline
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee); ) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee);
/// @notice External view to quote swapMint amounts, matching swapMint() computations
/// @param inputTokenIndex index of input token to deposit
/// @param maxAmountIn maximum gross input allowed (inclusive of fee)
/// @return totalTransfer gross input amount to transfer (includes fee), amountIn net input amount used for minting, fee fee amount taken, lpMinted LP tokens that would be minted
function swapMintAmounts(
uint256 inputTokenIndex,
uint256 maxAmountIn
) external view returns (uint256 totalTransfer, uint256 amountIn, uint256 fee, uint256 lpMinted);
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP. /// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
/// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal. /// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal.
/// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance. /// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance.
@@ -205,6 +214,15 @@ interface IPartyPool is IERC20Metadata {
uint256 deadline uint256 deadline
) external returns (uint256 lpMinted); ) external returns (uint256 lpMinted);
/// @notice External view to quote burnSwap amounts, matching burnSwap() computations
/// @param lpAmount amount of LP tokens to burn
/// @param inputTokenIndex index of target asset to receive
/// @return amountOut output amount user would receive after fees
function burnSwapAmounts(
uint256 lpAmount,
uint256 inputTokenIndex
) external view returns (uint256 amountOut);
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. /// @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. /// @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 /// @param payer who burns LP tokens

View File

@@ -170,6 +170,14 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
return s.swapToLimitAmounts(inputTokenIndex, outputTokenIndex, limitPrice, swapFeePpm); return s.swapToLimitAmounts(inputTokenIndex, outputTokenIndex, limitPrice, swapFeePpm);
} }
/// @inheritdoc IPartyPool
function swapMintAmounts(
uint256 inputTokenIndex,
uint256 maxAmountIn
) external view returns (uint256 totalTransfer, uint256 amountIn, uint256 fee, uint256 lpMinted) {
return s.swapMintAmounts(inputTokenIndex, maxAmountIn, swapFeePpm, totalSupply());
}
/// @notice Swap input token i -> token j. Payer must approve token i. /// @notice Swap input token i -> token j. Payer must approve token i.
/// @dev This function transfers the exact gross input (including fee) from payer and sends the computed output to receiver. /// @dev This function transfers the exact gross input (including fee) from payer and sends the computed output to receiver.
/// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks. /// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks.
@@ -228,6 +236,14 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
_mint(receiver, lpMinted); _mint(receiver, lpMinted);
} }
/// @inheritdoc IPartyPool
function burnSwapAmounts(
uint256 lpAmount,
uint256 inputTokenIndex
) external view returns (uint256 amountOut) {
return s.burnSwapAmounts(lpAmount, inputTokenIndex, swapFeePpm, totalSupply());
}
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. /// @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. /// @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 /// @param payer who burns LP tokens

View File

@@ -321,6 +321,64 @@ library PoolLib {
return (grossIn, outUint, feeUint); return (grossIn, outUint, feeUint);
} }
/// @notice Get amounts for swapMint operation
function swapMintAmounts(
State storage state,
uint256 inputTokenIndex,
uint256 maxAmountIn,
uint256 swapFeePpm,
uint256 totalSupply
) internal view returns (uint256 totalTransfer, uint256 amountIn, uint256 fee, uint256 lpMinted) {
uint256 n = state.tokens.length;
require(inputTokenIndex < n, "swapMintAmounts: idx");
require(maxAmountIn > 0, "swapMintAmounts: input zero");
require(state.lmsr.nAssets > 0, "swapMintAmounts: uninit pool");
// Compute fee on gross maxAmountIn to get initial net estimate
(, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm);
// Convert the net guess to internal (floor)
int128 netInternalGuess = _uintToInternalFloor(netUintGuess, state.bases[inputTokenIndex]);
require(netInternalGuess > int128(0), "swapMintAmounts: input too small after fee");
// Use LMSR view to determine actual internal consumed and size-increase
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) = state.lmsr.swapAmountsForMint(inputTokenIndex, netInternalGuess);
// Convert to uint (ceil) to determine actual transfer
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]);
require(amountInUint > 0, "swapMintAmounts: input zero after internal conversion");
// Compute fee on actual used input and total transfer amount
uint256 feeUintActual = _ceilFee(amountInUint, swapFeePpm);
uint256 totalTransferAmount = amountInUint + feeUintActual;
require(totalTransferAmount > 0 && totalTransferAmount <= maxAmountIn, "swapMintAmounts: transfer exceeds max");
// Compute old and new scaled size metrics
int128 oldTotal = _computeSizeMetric(state.lmsr.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);
uint256 actualLpToMint;
if (totalSupply == 0) {
actualLpToMint = newScaled;
} else {
require(oldScaled > 0, "swapMintAmounts: oldScaled zero");
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
if (delta > 0) {
actualLpToMint = (totalSupply * delta) / oldScaled;
} else {
actualLpToMint = 0;
}
}
require(actualLpToMint > 0, "swapMintAmounts: zero LP minted");
return (totalTransferAmount, amountInUint, feeUintActual, actualLpToMint);
}
/// @notice Execute exact input swap /// @notice Execute exact input swap
function swap( function swap(
State storage state, State storage state,
@@ -415,6 +473,39 @@ library PoolLib {
return (amountInUsedUint, amountOutUint, feeUint); return (amountInUsedUint, amountOutUint, feeUint);
} }
/// @notice Get amounts for burnSwap operation
function burnSwapAmounts(
State storage state,
uint256 lpAmount,
uint256 inputTokenIndex,
uint256 swapFeePpm,
uint256 totalSupply
) internal view returns (uint256 amountOut) {
uint256 n = state.tokens.length;
require(inputTokenIndex < n, "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);
// Use LMSR view to compute single-asset payout
(int128 payoutInternal, ) = state.lmsr.swapAmountsForBurn(inputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool
uint256 amountOutUint = _internalToUintFloor(payoutInternal, state.bases[inputTokenIndex]);
require(amountOutUint > 0, "burnSwapAmounts: output zero");
// Apply swap fee to the output
if (swapFeePpm > 0) {
uint256 feeUint = _ceilFee(amountOutUint, swapFeePpm);
require(amountOutUint > feeUint, "burnSwapAmounts: fee exceeds output");
amountOutUint -= feeUint;
}
return amountOutUint;
}
/// @notice Single-token mint (swapMint) /// @notice Single-token mint (swapMint)
function swapMint( function swapMint(
State storage state, State storage state,
@@ -432,24 +523,10 @@ library PoolLib {
require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline"); require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline");
require(state.lmsr.nAssets > 0, "swapMint: uninit pool"); require(state.lmsr.nAssets > 0, "swapMint: uninit pool");
// Compute fee on gross maxAmountIn to get initial net estimate // Calculate amounts using view function
(, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm); (uint256 totalTransfer, uint256 amountInUint, uint256 feeUintActual, uint256 actualLpToMint) = swapMintAmounts(
state, inputTokenIndex, maxAmountIn, swapFeePpm, totalSupply
// Convert the net guess to internal (floor) );
int128 netInternalGuess = _uintToInternalFloor(netUintGuess, state.bases[inputTokenIndex]);
require(netInternalGuess > int128(0), "swapMint: input too small after fee");
// Use LMSR view to determine actual internal consumed and size-increase
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) = state.lmsr.swapAmountsForMint(inputTokenIndex, netInternalGuess);
// Convert to uint (ceil) to determine actual transfer
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, state.bases[inputTokenIndex]);
require(amountInUint > 0, "swapMint: input zero after internal conversion");
// Compute fee on actual used input and total transfer amount
uint256 feeUintActual = _ceilFee(amountInUint, swapFeePpm);
uint256 totalTransfer = amountInUint + feeUintActual;
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
// Record pre-balance and transfer tokens // Record pre-balance and transfer tokens
uint256 prevBalI = IERC20(state.tokens[inputTokenIndex]).balanceOf(address(this)); uint256 prevBalI = IERC20(state.tokens[inputTokenIndex]).balanceOf(address(this));
@@ -460,30 +537,11 @@ library PoolLib {
// Update cached uint balances // Update cached uint balances
state.cachedUintBalances[inputTokenIndex] = balIAfter; state.cachedUintBalances[inputTokenIndex] = balIAfter;
// Compute old and new scaled size metrics
int128 oldTotal = _computeSizeMetric(state.lmsr.qInternal);
require(oldTotal > int128(0), "swapMint: zero total");
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
uint256 actualLpToMint;
if (totalSupply == 0) {
actualLpToMint = newScaled;
} else {
require(oldScaled > 0, "swapMint: oldScaled zero");
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
if (delta > 0) {
actualLpToMint = (totalSupply * delta) / oldScaled;
} else {
actualLpToMint = 0;
}
}
require(actualLpToMint > 0, "swapMint: zero LP minted");
// Update LMSR internal state // Update LMSR internal state
int128 oldTotal = _computeSizeMetric(state.lmsr.qInternal);
(, int128 sizeIncreaseInternal) = state.lmsr.swapAmountsForMint(inputTokenIndex, _uintToInternalFloor(amountInUint, state.bases[inputTokenIndex]));
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
int128[] memory newQInternal = new int128[](n); int128[] memory newQInternal = new int128[](n);
for (uint256 idx = 0; idx < n; idx++) { for (uint256 idx = 0; idx < n; idx++) {
newQInternal[idx] = state.lmsr.qInternal[idx].mul(newTotal).div(oldTotal); newQInternal[idx] = state.lmsr.qInternal[idx].mul(newTotal).div(oldTotal);
@@ -516,22 +574,8 @@ library PoolLib {
require(totalSupply > 0, "burnSwap: empty supply"); require(totalSupply > 0, "burnSwap: empty supply");
require(payerBalance >= lpAmount, "burnSwap: insufficient LP"); require(payerBalance >= lpAmount, "burnSwap: insufficient LP");
// alpha = lpAmount / supply as Q64.64 // Calculate amounts using view function
int128 alpha = ABDKMath64x64.divu(lpAmount, totalSupply); amountOutUint = burnSwapAmounts(state, lpAmount, inputTokenIndex, swapFeePpm, totalSupply);
// Use LMSR view to compute single-asset payout
(int128 payoutInternal, ) = state.lmsr.swapAmountsForBurn(inputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool
amountOutUint = _internalToUintFloor(payoutInternal, state.bases[inputTokenIndex]);
require(amountOutUint > 0, "burnSwap: output zero");
// Apply swap fee to the output
if (swapFeePpm > 0) {
uint256 feeUint = _ceilFee(amountOutUint, swapFeePpm);
require(amountOutUint > feeUint, "burnSwap: fee exceeds output");
amountOutUint -= feeUint;
}
// Transfer the payout to receiver // Transfer the payout to receiver
state.tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint); state.tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint);