swap amounts

This commit is contained in:
tim
2025-09-15 18:38:28 -04:00
parent f7e40eee06
commit 070717959e
6 changed files with 175 additions and 93 deletions

View File

@@ -108,7 +108,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
/// @notice Calculate the proportional deposit amounts required for a given LP token amount
/// @param lpTokenAmount The amount of LP tokens desired
/// @return depositAmounts Array of token amounts to deposit (rounded up)
function computeMintAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
uint256 n = tokens.length;
depositAmounts = new uint256[](n);
@@ -136,11 +136,11 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount
/// @param lpTokenAmount The amount of LP tokens to burn
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
function computeBurnAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
return _computeBurnAmounts(lpTokenAmount);
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
return _burnReceiveAmounts(lpTokenAmount);
}
function _computeBurnAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory withdrawAmounts) {
function _burnReceiveAmounts(uint256 lpTokenAmount) internal view returns (uint256[] memory withdrawAmounts) {
uint256 n = tokens.length;
withdrawAmounts = new uint256[](n);
@@ -188,7 +188,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
if (!isInitialDeposit) {
// Calculate required deposit amounts for the desired LP tokens
depositAmounts = computeMintAmounts(lpTokenAmount);
depositAmounts = mintDepositAmounts(lpTokenAmount);
// Transfer in all token amounts
for (uint i = 0; i < n; ) {
@@ -284,7 +284,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
}
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
uint256[] memory withdrawAmounts = _computeBurnAmounts(lpAmount);
uint256[] memory withdrawAmounts = _burnReceiveAmounts(lpAmount);
// Transfer underlying tokens out to receiver according to computed proportions
for (uint i = 0; i < n; ) {
@@ -334,6 +334,119 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
Swaps
---------------------- */
/// @notice Internal quote for exact-input swap that mirrors swap() rounding and fee application
/// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
/// amountInInternalUsed and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint)
function _quoteSwapExactIn(
uint256 i,
uint256 j,
uint256 maxAmountIn,
int128 limitPrice
)
internal
view
returns (
uint256 grossIn,
uint256 amountOutUint,
int128 amountInInternalUsed,
int128 amountOutInternal,
uint256 amountInUintNoFee
)
{
uint256 n = tokens.length;
require(i < n && j < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero");
require(lmsr.nAssets > 0, "swap: empty pool");
// Estimate max net input (fee on gross rounded up, then subtract)
(, uint256 netUintForSwap) = _computeFee(maxAmountIn);
// Convert to internal (floor)
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[i]);
require(deltaInternalI > int128(0), "swap: input too small after fee");
// Compute internal amounts using LMSR (exact-input with price limit)
(amountInInternalUsed, amountOutInternal) = lmsr.swapAmountsForExactInput(i, j, deltaInternalI, limitPrice);
// Convert actual used input internal -> uint (ceil)
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, bases[i]);
require(amountInUintNoFee > 0, "swap: input zero");
// Compute gross transfer including fee on the used input (ceil)
grossIn = amountInUintNoFee;
if (swapFeePpm > 0) {
grossIn += _ceilFee(amountInUintNoFee, swapFeePpm);
}
// Ensure within user max
require(grossIn <= maxAmountIn, "swap: transfer exceeds max");
// Compute output (floor)
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
require(amountOutUint > 0, "swap: output zero");
}
/// @notice Internal quote for swap-to-limit that mirrors swapToLimit() rounding and fee application
/// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
/// amountInInternal and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint)
function _quoteSwapToLimit(
uint256 i,
uint256 j,
int128 limitPrice
)
internal
view
returns (
uint256 grossIn,
uint256 amountOutUint,
int128 amountInInternal,
int128 amountOutInternal,
uint256 amountInUintNoFee
)
{
uint256 n = tokens.length;
require(i < n && j < n, "swapToLimit: idx");
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized");
// Compute internal maxima at the price limit
(amountInInternal, amountOutInternal) = lmsr.swapAmountsForPriceLimit(i, j, limitPrice);
// Convert input to uint (ceil) and output to uint (floor)
amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[i]);
require(amountInUintNoFee > 0, "swapToLimit: input zero");
grossIn = amountInUintNoFee;
if (swapFeePpm > 0) {
grossIn += _ceilFee(amountInUintNoFee, swapFeePpm);
}
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
require(amountOutUint > 0, "swapToLimit: output zero");
}
/// @notice External view to quote exact-in swap amounts (gross input incl. fee and output), matching swap() computations
function swapAmounts(
uint256 i,
uint256 j,
uint256 maxAmountIn,
int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut) {
(uint256 grossIn, uint256 outUint,,,) = _quoteSwapExactIn(i, j, maxAmountIn, limitPrice);
return (grossIn, outUint);
}
/// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations
function swapToLimitAmounts(
uint256 i,
uint256 j,
int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut) {
(uint256 grossIn, uint256 outUint,,,) = _quoteSwapToLimit(i, j, limitPrice);
return (grossIn, outUint);
}
/// @notice Swap input token i -> token j. Payer must approve token i.
/// @param payer address of the account that pays for the swap
/// @param receiver address that will receive the output tokens
@@ -361,47 +474,15 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this));
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
// Calculate fee (ceiling) and net amount
(, uint256 netUintForSwap) = _computeFee(maxAmountIn);
// Convert the net amount to internal (floor)
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[i]);
require(deltaInternalI > int128(0), "swap: input too small after fee");
// Make sure LMSR state exists
require(lmsr.nAssets > 0, "swap: empty pool");
// Compute swap amounts in internal space using exact-input logic (with limitPrice)
(int128 amountInInternalUsed, int128 amountOutInternal) = lmsr.swapAmountsForExactInput(
i,
j,
deltaInternalI,
limitPrice
);
// Convert actual used input internal -> uint (ceiling to protect the pool)
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, bases[i]);
// Total transfer amount includes fee calculated on the actual used input (ceiling)
uint256 totalTransferAmount = amountInUint;
if (swapFeePpm > 0) {
uint256 feeOnUsed = _ceilFee(amountInUint, swapFeePpm);
totalTransferAmount += feeOnUsed;
}
// Ensure we do not attempt to transfer more than the caller specified as maximum
require(totalTransferAmount > 0, 'swap: input zero');
require(totalTransferAmount <= maxAmountIn, "swap: transfer exceeds max");
// Compute amounts using the same path as views
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, ) =
_quoteSwapExactIn(i, j, maxAmountIn, limitPrice);
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount);
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this));
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
// Compute output uint amount (floor)
uint256 amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
require(amountOutUint > 0, "swap: output zero");
// Transfer output to receiver and verify exact decrease
_safeTransfer(tokens[j], receiver, amountOutUint);
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this));
@@ -411,8 +492,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
cachedUintBalances[i] = balIAfter;
cachedUintBalances[j] = balJAfter;
// Apply swap to LMSR state with the internal amounts actually used
// (fee is already accounted for in the reduced input amount)
// Apply swap to LMSR state with the internal amounts actually used
lmsr.applySwap(i, j, amountInInternalUsed, amountOutInternal);
emit Swap(payer, receiver, tokens[i], tokens[j], totalTransferAmount, amountOutUint);
@@ -437,37 +517,19 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
// Ensure LMSR state exists
require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized");
// Read previous balances for affected assets
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this));
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
// Compute maxima in internal space using library
(int128 amountInInternalMax, int128 amountOutInternal) = lmsr.swapAmountsForPriceLimit(i, j, limitPrice);
// Calculate how much input will be needed with fee included (ceiling to protect the pool)
uint256 amountInUsedUint = _internalToUintCeil(amountInInternalMax, bases[i]);
require(amountInUsedUint > 0, "swapToLimit: input zero");
// Total transfer amount is the input amount including what will be taken as fee (ceiling)
uint256 totalTransferAmount = amountInUsedUint;
if (swapFeePpm > 0) {
uint256 feeOnUsed = _ceilFee(amountInUsedUint, swapFeePpm);
totalTransferAmount += feeOnUsed;
}
// Compute amounts using the same path as views
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint) =
_quoteSwapToLimit(i, j, limitPrice);
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount);
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this));
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
// Compute output amount (floor)
uint256 amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
require(amountOutUint > 0, "swapToLimit: output zero");
// Transfer output to receiver and verify exact decrease
_safeTransfer(tokens[j], receiver, amountOutUint);
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this));
@@ -478,9 +540,9 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
cachedUintBalances[j] = balJAfter;
// Apply swap to LMSR state with the internal amounts
// (fee is already part of the reduced effective input)
lmsr.applySwap(i, j, amountInInternalMax, amountOutInternal);
// Maintain original event semantics (logs input without fee)
emit Swap(payer, receiver, tokens[i], tokens[j], amountInUsedUint, amountOutUint);
return (amountInUsedUint, amountOutUint);
@@ -685,7 +747,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
}
function computeFlashRepaymentAmounts(uint256[] memory loanAmounts) external view
function flashRepaymentAmounts(uint256[] memory loanAmounts) external view
returns (uint256[] memory repaymentAmounts) {
repaymentAmounts = new uint256[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {