return fee taken

This commit is contained in:
tim
2025-09-15 18:51:47 -04:00
parent 070717959e
commit fef6d007d8
3 changed files with 37 additions and 25 deletions

View File

@@ -93,7 +93,7 @@ interface IPartyPool is IERC20Metadata {
uint256 j, uint256 j,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut); ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee);
function swap( function swap(
address payer, address payer,
@@ -103,14 +103,14 @@ interface IPartyPool is IERC20Metadata {
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external returns (uint256 amountIn, uint256 amountOut); ) external returns (uint256 amountIn, uint256 amountOut, uint256 fee);
/// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations /// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations
function swapToLimitAmounts( function swapToLimitAmounts(
uint256 i, uint256 i,
uint256 j, uint256 j,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut); ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee);
function swapToLimit( function swapToLimit(
address payer, address payer,
@@ -119,7 +119,7 @@ interface IPartyPool is IERC20Metadata {
uint256 j, uint256 j,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external returns (uint256 amountInUsed, uint256 amountOut); ) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee);
/// @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.
/// @param payer who transfers the input token /// @param payer who transfers the input token

View File

@@ -336,7 +336,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
/// @notice Internal quote for exact-input swap that mirrors swap() rounding and fee application /// @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), /// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
/// amountInInternalUsed and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint) /// amountInInternalUsed and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint),
/// feeUint fee taken from the gross input (uint)
function _quoteSwapExactIn( function _quoteSwapExactIn(
uint256 i, uint256 i,
uint256 j, uint256 j,
@@ -350,7 +351,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 amountOutUint, uint256 amountOutUint,
int128 amountInInternalUsed, int128 amountInInternalUsed,
int128 amountOutInternal, int128 amountOutInternal,
uint256 amountInUintNoFee uint256 amountInUintNoFee,
uint256 feeUint
) )
{ {
uint256 n = tokens.length; uint256 n = tokens.length;
@@ -373,9 +375,11 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
require(amountInUintNoFee > 0, "swap: input zero"); require(amountInUintNoFee > 0, "swap: input zero");
// Compute gross transfer including fee on the used input (ceil) // Compute gross transfer including fee on the used input (ceil)
feeUint = 0;
grossIn = amountInUintNoFee; grossIn = amountInUintNoFee;
if (swapFeePpm > 0) { if (swapFeePpm > 0) {
grossIn += _ceilFee(amountInUintNoFee, swapFeePpm); feeUint = _ceilFee(amountInUintNoFee, swapFeePpm);
grossIn += feeUint;
} }
// Ensure within user max // Ensure within user max
@@ -388,7 +392,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
/// @notice Internal quote for swap-to-limit that mirrors swapToLimit() rounding and fee application /// @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), /// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
/// amountInInternal and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint) /// amountInInternal and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint),
/// feeUint fee taken from the gross input (uint)
function _quoteSwapToLimit( function _quoteSwapToLimit(
uint256 i, uint256 i,
uint256 j, uint256 j,
@@ -401,7 +406,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 amountOutUint, uint256 amountOutUint,
int128 amountInInternal, int128 amountInInternal,
int128 amountOutInternal, int128 amountOutInternal,
uint256 amountInUintNoFee uint256 amountInUintNoFee,
uint256 feeUint
) )
{ {
uint256 n = tokens.length; uint256 n = tokens.length;
@@ -416,9 +422,11 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[i]); amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[i]);
require(amountInUintNoFee > 0, "swapToLimit: input zero"); require(amountInUintNoFee > 0, "swapToLimit: input zero");
feeUint = 0;
grossIn = amountInUintNoFee; grossIn = amountInUintNoFee;
if (swapFeePpm > 0) { if (swapFeePpm > 0) {
grossIn += _ceilFee(amountInUintNoFee, swapFeePpm); feeUint = _ceilFee(amountInUintNoFee, swapFeePpm);
grossIn += feeUint;
} }
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]); amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]);
@@ -431,9 +439,9 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 j, uint256 j,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut) { ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
(uint256 grossIn, uint256 outUint,,,) = _quoteSwapExactIn(i, j, maxAmountIn, limitPrice); (uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(i, j, maxAmountIn, limitPrice);
return (grossIn, outUint); return (grossIn, outUint, feeUint);
} }
/// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations /// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations
@@ -441,9 +449,9 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 i, uint256 i,
uint256 j, uint256 j,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut) { ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
(uint256 grossIn, uint256 outUint,,,) = _quoteSwapToLimit(i, j, limitPrice); (uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(i, j, limitPrice);
return (grossIn, outUint); return (grossIn, outUint, feeUint);
} }
@@ -455,7 +463,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
/// @param maxAmountIn maximum amount of token i (uint256) to transfer in (inclusive of fees) /// @param maxAmountIn maximum amount of token i (uint256) to transfer in (inclusive of fees)
/// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore. /// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore.
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore. /// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
/// @return amountIn actual input used (uint256), amountOut actual output sent (uint256) /// @return amountIn actual input used (uint256), amountOut actual output sent (uint256), fee fee taken from the input (uint256)
function swap( function swap(
address payer, address payer,
address receiver, address receiver,
@@ -464,7 +472,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external nonReentrant returns (uint256 amountIn, uint256 amountOut) { ) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n && j < n, "swap: idx"); require(i < n && j < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero"); require(maxAmountIn > 0, "swap: input zero");
@@ -475,7 +483,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this)); uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
// Compute amounts using the same path as views // Compute amounts using the same path as views
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, ) = (uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
_quoteSwapExactIn(i, j, maxAmountIn, limitPrice); _quoteSwapExactIn(i, j, maxAmountIn, limitPrice);
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer) // Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
@@ -497,7 +505,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
emit Swap(payer, receiver, tokens[i], tokens[j], totalTransferAmount, amountOutUint); emit Swap(payer, receiver, tokens[i], tokens[j], totalTransferAmount, amountOutUint);
return (totalTransferAmount, amountOutUint); return (totalTransferAmount, amountOutUint, feeUint);
} }
/// @notice Swap up to the price limit; computes max input to reach limit then performs swap. /// @notice Swap up to the price limit; computes max input to reach limit then performs swap.
@@ -511,7 +519,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 j, uint256 j,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external returns (uint256 amountInUsed, uint256 amountOut) { ) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n && j < n, "swapToLimit: idx"); require(i < n && j < n, "swapToLimit: idx");
require(limitPrice > int128(0), "swapToLimit: limit <= 0"); require(limitPrice > int128(0), "swapToLimit: limit <= 0");
@@ -522,7 +530,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this)); uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this));
// Compute amounts using the same path as views // Compute amounts using the same path as views
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint) = (uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
_quoteSwapToLimit(i, j, limitPrice); _quoteSwapToLimit(i, j, limitPrice);
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer) // Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
@@ -545,7 +553,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
// Maintain original event semantics (logs input without fee) // Maintain original event semantics (logs input without fee)
emit Swap(payer, receiver, tokens[i], tokens[j], amountInUsedUint, amountOutUint); emit Swap(payer, receiver, tokens[i], tokens[j], amountInUsedUint, amountOutUint);
return (amountInUsedUint, amountOutUint); return (amountInUsedUint, amountOutUint, feeUint);
} }
/// @notice Ceiling fee helper: computes ceil(x * feePpm / 1_000_000) /// @notice Ceiling fee helper: computes ceil(x * feePpm / 1_000_000)

View File

@@ -453,12 +453,14 @@ contract PartyPoolTest is Test {
// Execute swap: token0 -> token1 // Execute swap: token0 -> token1
vm.prank(alice); vm.prank(alice);
(uint256 amountInUsed, uint256 amountOut) = pool.swap(alice, bob, 0, 1, maxIn, 0, 0); (uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swap(alice, bob, 0, 1, maxIn, 0, 0);
// Amounts should be positive and not exceed provided max // Amounts should be positive and not exceed provided max
assertTrue(amountInUsed > 0, "expected some input used"); assertTrue(amountInUsed > 0, "expected some input used");
assertTrue(amountOut > 0, "expected some output returned"); assertTrue(amountOut > 0, "expected some output returned");
assertTrue(amountInUsed <= maxIn, "used input must not exceed max"); assertTrue(amountInUsed <= maxIn, "used input must not exceed max");
// Fee should be <= amountInUsed
assertTrue(fee <= amountInUsed, "fee must not exceed total input");
// Alice's balance decreased by exactly amountInUsed // Alice's balance decreased by exactly amountInUsed
assertEq(token0.balanceOf(alice), balAliceBefore - amountInUsed); assertEq(token0.balanceOf(alice), balAliceBefore - amountInUsed);
@@ -492,10 +494,12 @@ contract PartyPoolTest is Test {
token0.approve(address(pool), type(uint256).max); token0.approve(address(pool), type(uint256).max);
vm.prank(alice); vm.prank(alice);
(uint256 amountInUsed, uint256 amountOut) = pool.swapToLimit(alice, bob, 0, 1, limitPrice, 0); (uint256 amountInUsed, uint256 amountOut, uint256 fee) = pool.swapToLimit(alice, bob, 0, 1, limitPrice, 0);
assertTrue(amountInUsed > 0, "expected some input used for swapToLimit"); assertTrue(amountInUsed > 0, "expected some input used for swapToLimit");
assertTrue(amountOut > 0, "expected some output for swapToLimit"); assertTrue(amountOut > 0, "expected some output for swapToLimit");
// Fee should be <= amountInUsed (gross includes fee)
assertTrue(fee <= amountInUsed, "fee must not exceed total input for swapToLimit");
// Verify bob got the output // Verify bob got the output
assertEq(token1.balanceOf(bob) >= amountOut, true); assertEq(token1.balanceOf(bob) >= amountOut, true);