additive fees; burnSwapAmounts fix

This commit is contained in:
tim
2025-11-04 16:58:16 -04:00
parent 590acdd4dc
commit dc2e186331
10 changed files with 103 additions and 100 deletions

View File

@@ -269,7 +269,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @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 amountIn actual input amount used (excluding fee)
/// @return lpMinted LP tokens that would be minted
/// @return inFee fee amount charged
function swapMintAmounts(
@@ -279,7 +279,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
LMSRStabilized.State memory lmsrState,
uint256[] memory bases_,
uint256 totalSupply_
) public pure returns (uint256 amountInUsed, uint256 lpMinted, uint256 inFee) {
) public pure returns (uint256 amountIn, uint256 lpMinted, uint256 inFee) {
require(inputTokenIndex < bases_.length, "swapMintAmounts: idx");
require(maxAmountIn > 0, "swapMintAmounts: input zero");
require(lmsrState.qInternal.length > 0, "swapMintAmounts: uninit pool");
@@ -302,7 +302,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
inputTokenIndex, netInternalGuess);
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
amountInUsed = _internalToUintCeilPure(amountInInternalUsed, bases_[inputTokenIndex]);
uint256 amountInUsed = _internalToUintCeilPure(amountInInternalUsed, bases_[inputTokenIndex]);
require(amountInUsed > 0, "swapMintAmounts: input zero after internal conversion");
// Compute fee on the actual used input (ceiling)
@@ -310,8 +310,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
if (swapFeePpm > 0) {
inFee = (amountInUsed * swapFeePpm + 999999) / 1000000; // ceil fee
}
uint256 totalTransfer = amountInUsed + inFee;
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMintAmounts: transfer exceeds max");
amountIn = amountInUsed + inFee;
require(amountIn > 0 && amountIn <= maxAmountIn, "swapMintAmounts: transfer exceeds max");
// Compute old and new scaled size metrics to determine LP minted
int128 oldTotal = _computeSizeMetricPure(lmsrState.qInternal);
@@ -347,7 +347,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @param maxAmountIn maximum uint token input (inclusive of fee)
/// @param deadline optional deadline
/// @param swapFeePpm fee in parts-per-million for this pool
/// @return amountInUsed actual input used (uint256), lpMinted actual LP minted (uint256), inFee fee taken from the input (uint256)
/// @return amountIn actual input used (uint256), lpMinted actual LP minted (uint256), inFee fee taken from the input (uint256)
function swapMint(
address payer,
address receiver,
@@ -356,7 +356,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
uint256 deadline,
uint256 swapFeePpm,
uint256 protocolFeePpm
) external payable native killable nonReentrant returns (uint256 amountInUsed, uint256 lpMinted, uint256 inFee) {
) external payable native killable nonReentrant returns (uint256 amountIn, uint256 lpMinted, uint256 inFee) {
uint256 n = _tokens.length;
require(inputTokenIndex < n, "swapMint: idx");
require(maxAmountIn > 0, "swapMint: input zero");
@@ -374,27 +374,27 @@ contract PartyPoolMintImpl is PartyPoolBase {
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) = _lmsr.swapAmountsForMint(inputTokenIndex, netInternalGuess);
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, _bases[inputTokenIndex]);
require(amountInUint > 0, "swapMint: input zero after internal conversion");
uint256 amountInUsed = _internalToUintCeil(amountInInternalUsed, _bases[inputTokenIndex]);
require(amountInUsed > 0, "swapMint: input zero after internal conversion");
// Compute fee on the actual used input and total transfer amount (ceiling)
uint256 feeUintActual = _ceilFee(amountInUint, swapFeePpm);
uint256 totalTransfer = amountInUint + feeUintActual;
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
inFee = _ceilFee(amountInUsed, swapFeePpm);
amountIn = amountInUsed + inFee;
require(amountIn > 0 && amountIn <= maxAmountIn, "swapMint: transfer exceeds max");
// Transfer _tokens from payer (assume standard ERC20 without transfer fees) via helper
_receiveTokenFrom(payer, _tokens[inputTokenIndex], totalTransfer);
_receiveTokenFrom(payer, _tokens[inputTokenIndex], amountIn);
// Accrue protocol share (floor) from the fee on the input token
uint256 protoShare = 0;
if (protocolFeePpm > 0 && feeUintActual > 0) {
protoShare = (feeUintActual * protocolFeePpm) / 1_000_000;
if (protocolFeePpm > 0 && inFee > 0) {
protoShare = (inFee * protocolFeePpm) / 1_000_000;
if (protoShare > 0) {
_protocolFeesOwed[inputTokenIndex] += protoShare;
}
}
// Update cached effective balance directly: add totalTransfer minus protocol share
_cachedUintBalances[inputTokenIndex] += (totalTransfer - protoShare);
_cachedUintBalances[inputTokenIndex] += (amountIn - protoShare);
// Compute old and new scaled size metrics to determine LP minted
int128 oldTotal = _computeSizeMetric(_lmsr.qInternal);
@@ -403,23 +403,22 @@ contract PartyPoolMintImpl is PartyPoolBase {
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
uint256 actualLpToMint;
// Use natural ERC20 function since base contract inherits from ERC20
uint256 currentSupply = _totalSupply;
if (currentSupply == 0) {
// If somehow supply zero (shouldn't happen as _lmsr.nAssets>0), mint newScaled
actualLpToMint = newScaled;
lpMinted = newScaled;
} else {
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
if (delta > 0) {
// floor truncation rounds in favor of pool
actualLpToMint = (currentSupply * delta) / oldScaled;
lpMinted = (currentSupply * delta) / oldScaled;
} else {
actualLpToMint = 0;
lpMinted = 0;
}
}
require(actualLpToMint > 0, "swapMint: zero LP minted");
require(lpMinted > 0, "swapMint: zero LP minted");
// Update LMSR internal state: scale qInternal proportionally by newTotal/oldTotal
int128[] memory newQInternal = new int128[](n);
@@ -432,14 +431,11 @@ contract PartyPoolMintImpl is PartyPoolBase {
_lmsr.updateForProportionalChange(newQInternal);
// Use natural ERC20 function since base contract inherits from ERC20
_mint(receiver, actualLpToMint);
_mint(receiver, lpMinted);
emit IPartyPool.SwapMint(payer, receiver, _tokens[inputTokenIndex],
totalTransfer, actualLpToMint, feeUintActual-protoShare, protoShare);
amountIn, lpMinted, inFee -protoShare, protoShare);
amountInUsed = amountInUint;
lpMinted = actualLpToMint;
inFee = feeUintActual;
return (amountInUsed, lpMinted, inFee);
}
@@ -465,23 +461,17 @@ contract PartyPoolMintImpl is PartyPoolBase {
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
int128 alpha = ABDKMath64x64.divu(lpAmount, totalSupply_);
// Use LMSR view to compute single-asset payout and burned size-metric
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.kappa, lmsrState.qInternal,
(, int128 payoutInternal) = LMSRStabilized.swapAmountsForBurn(lmsrState.kappa, lmsrState.qInternal,
outputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool
amountOut = _internalToUintFloorPure(payoutInternal, bases_[outputTokenIndex]);
require(amountOut > 0, "burnSwapAmounts: output zero");
// Compute gross payout (no swap fee) to derive token-side fee = gross - net
int128 alphaGross = ABDKMath64x64.divu(lpAmount, totalSupply_); // gross fraction (no swap fee)
(int128 payoutGrossInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.kappa, lmsrState.qInternal,
outputTokenIndex, alphaGross);
uint256 payoutGrossUint = _internalToUintFloorPure(payoutGrossInternal, bases_[outputTokenIndex]);
outFee = (payoutGrossUint > amountOut) ? (payoutGrossUint - amountOut) : 0;
uint256 grossAmountOut = _internalToUintFloorPure(payoutInternal, bases_[outputTokenIndex]);
(outFee,) = _computeFee(grossAmountOut, swapFeePpm);
require(grossAmountOut > outFee, "burnSwapAmounts: output zero");
amountOut = grossAmountOut - outFee;
}
/// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `outputTokenIndex` and send to receiver.
@@ -494,7 +484,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @param outputTokenIndex index of target asset to receive
/// @param deadline optional deadline
/// @param swapFeePpm fee in parts-per-million for this pool (may be used for future fee logic)
/// @return amountOutUint uint amount of asset i sent to receiver
/// @return amountOut uint amount of asset i sent to receiver
/// @return outFee uint amount of asset i kept as an LP and protocol fee
function burnSwap(
address payer,
address receiver,
@@ -504,7 +495,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
bool unwrap,
uint256 swapFeePpm,
uint256 protocolFeePpm
) external nonReentrant killable returns (uint256 amountOutUint) {
) external nonReentrant killable returns (uint256 amountOut, uint256 outFee) {
uint256 n = _tokens.length;
require(outputTokenIndex < n, "burnSwap: idx");
require(lpAmount > 0, "burnSwap: zero lp");
@@ -514,35 +505,26 @@ contract PartyPoolMintImpl is PartyPoolBase {
require(supply > 0, "burnSwap: empty supply");
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
int128 alpha = ABDKMath64x64.divu(lpAmount, supply); // fraction of total supply to burn
// Use LMSR view to compute single-asset payout and burned size-metric
(int128 payoutInternal, ) = _lmsr.swapAmountsForBurn(outputTokenIndex, alpha);
(, int128 payoutInternal) = _lmsr.swapAmountsForBurn(outputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool
amountOutUint = _internalToUintFloor(payoutInternal, _bases[outputTokenIndex]);
require(amountOutUint > 0, "burnSwap: output zero");
// Compute gross payout (no swap fee) so we can determine token-side fee = gross - net
int128 alphaGross = ABDKMath64x64.divu(lpAmount, supply); // gross fraction (no swap fee)
(int128 payoutGrossInternal, ) = _lmsr.swapAmountsForBurn(outputTokenIndex, alphaGross);
uint256 payoutGrossUint = _internalToUintFloor(payoutGrossInternal, _bases[outputTokenIndex]);
uint256 feeTokenUint = (payoutGrossUint > amountOutUint) ? (payoutGrossUint - amountOutUint) : 0;
uint256 payoutGrossUint = _internalToUintFloorPure(payoutInternal, _bases[outputTokenIndex]);
(outFee,) = _computeFee(payoutGrossUint, swapFeePpm);
require(payoutGrossUint > outFee, "burnSwapAmounts: output zero");
amountOut = payoutGrossUint - outFee;
// Accrue protocol share (floor) from the token-side fee
uint256 protoShare = 0;
if (protocolFeePpm > 0 && feeTokenUint > 0) {
protoShare = (feeTokenUint * protocolFeePpm) / 1_000_000;
if (protocolFeePpm > 0 && outFee > 0) {
protoShare = (outFee * protocolFeePpm) / 1_000_000;
if (protoShare > 0) {
_protocolFeesOwed[outputTokenIndex] += protoShare;
}
}
// Transfer the payout to receiver via centralized helper
IERC20 outputToken = _tokens[outputTokenIndex];
_sendTokenTo(outputToken, receiver, amountOutUint, unwrap);
// Burn LP _tokens from payer (authorization via allowance)
if (msg.sender != payer) {
uint256 allowed = _allowances[payer][msg.sender];
@@ -550,13 +532,19 @@ contract PartyPoolMintImpl is PartyPoolBase {
}
_burn(payer, lpAmount);
// Transfer the payout to receiver via centralized helper
IERC20 outputToken = _tokens[outputTokenIndex];
_sendTokenTo(outputToken, receiver, amountOut, unwrap);
// Update cached balances using computed payout and protocol fee; no on-chain reads
int128[] memory newQInternal = new int128[](n);
for (uint256 idx = 0; idx < n; idx++) {
uint256 newBal = _cachedUintBalances[idx];
if (idx == outputTokenIndex) {
// Effective LP balance decreases by net payout and increased protocol owed
newBal = newBal - amountOutUint - protoShare;
newBal = newBal - amountOut - protoShare;
}
_cachedUintBalances[idx] = newBal;
newQInternal[idx] = _uintToInternalFloor(newBal, _bases[idx]);
@@ -573,10 +561,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
_lmsr.updateForProportionalChange(newQInternal);
}
emit IPartyPool.BurnSwap(payer, receiver, outputToken, lpAmount, amountOutUint,
feeTokenUint-protoShare, protoShare);
return amountOutUint;
emit IPartyPool.BurnSwap(payer, receiver, outputToken, lpAmount, amountOut,
outFee-protoShare, protoShare);
}
/// @notice Pure version of _uintToInternalFloor for use in view functions