swapMintAmounts
This commit is contained in:
@@ -188,6 +188,15 @@ interface IPartyPool is IERC20Metadata {
|
||||
uint256 deadline
|
||||
) 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.
|
||||
/// @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.
|
||||
@@ -205,6 +214,15 @@ interface IPartyPool is IERC20Metadata {
|
||||
uint256 deadline
|
||||
) 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.
|
||||
/// @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
|
||||
|
||||
@@ -170,6 +170,14 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
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.
|
||||
/// @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.
|
||||
@@ -228,6 +236,14 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
_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.
|
||||
/// @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
|
||||
|
||||
158
src/PoolLib.sol
158
src/PoolLib.sol
@@ -321,6 +321,64 @@ library PoolLib {
|
||||
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
|
||||
function swap(
|
||||
State storage state,
|
||||
@@ -415,6 +473,39 @@ library PoolLib {
|
||||
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)
|
||||
function swapMint(
|
||||
State storage state,
|
||||
@@ -432,24 +523,10 @@ library PoolLib {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline");
|
||||
require(state.lmsr.nAssets > 0, "swapMint: 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), "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");
|
||||
// Calculate amounts using view function
|
||||
(uint256 totalTransfer, uint256 amountInUint, uint256 feeUintActual, uint256 actualLpToMint) = swapMintAmounts(
|
||||
state, inputTokenIndex, maxAmountIn, swapFeePpm, totalSupply
|
||||
);
|
||||
|
||||
// Record pre-balance and transfer tokens
|
||||
uint256 prevBalI = IERC20(state.tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
@@ -460,30 +537,11 @@ library PoolLib {
|
||||
// Update cached uint balances
|
||||
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
|
||||
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);
|
||||
for (uint256 idx = 0; idx < n; idx++) {
|
||||
newQInternal[idx] = state.lmsr.qInternal[idx].mul(newTotal).div(oldTotal);
|
||||
@@ -516,22 +574,8 @@ library PoolLib {
|
||||
require(totalSupply > 0, "burnSwap: empty supply");
|
||||
require(payerBalance >= lpAmount, "burnSwap: insufficient LP");
|
||||
|
||||
// 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
|
||||
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;
|
||||
}
|
||||
// Calculate amounts using view function
|
||||
amountOutUint = burnSwapAmounts(state, lpAmount, inputTokenIndex, swapFeePpm, totalSupply);
|
||||
|
||||
// Transfer the payout to receiver
|
||||
state.tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint);
|
||||
|
||||
Reference in New Issue
Block a user