doc improvements

This commit is contained in:
tim
2025-09-19 12:04:15 -04:00
parent 3eba6412a6
commit 9fe0179e6a
2 changed files with 236 additions and 116 deletions

View File

@@ -4,9 +4,18 @@ pragma solidity ^0.8.30;
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token /// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
/// @notice Uses LMSRStabilized library; stores per-token uint bases to convert to/from 64.64 fixed point. /// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
/// - Caches qInternal[] (int128 64.64) and cachedUintBalances[] to minimize balanceOf() calls. /// The pool issues an ERC20 LP token representing proportional ownership.
/// - swap and swapToLimit mimic core lib; mint/burn call updateForProportionalChange() and manage LP tokens. /// It supports:
/// - Proportional minting and burning of LP tokens,
/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap),
/// - Exact-input swaps and swaps-to-price-limits,
/// - Flash loans via a callback interface.
///
/// @dev The contract stores per-token uint "bases" used to scale token units into the internal Q64.64
/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls.
/// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool
/// (i.e., floor outputs to users, ceil inputs/fees where appropriate).
interface IPartyPool is IERC20Metadata { interface IPartyPool is IERC20Metadata {
// All int128's are ABDKMath64x64 format // All int128's are ABDKMath64x64 format
@@ -47,25 +56,51 @@ interface IPartyPool is IERC20Metadata {
// Immutable pool configuration (public getters) // Immutable pool configuration (public getters)
/// @notice Token addresses comprising the pool. Effectively immutable after construction.
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
function tokens(uint256) external view returns (address); // get single token function tokens(uint256) external view returns (address); // get single token
/// @notice Returns the number of tokens (n) in the pool.
function numTokens() external view returns (uint256); function numTokens() external view returns (uint256);
/// @notice Returns the list of all token addresses in the pool (copy).
function allTokens() external view returns (address[] memory); function allTokens() external view returns (address[] memory);
/// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation.
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
function denominators() external view returns (uint256[] memory); function denominators() external view returns (uint256[] memory);
/// @notice Trade fraction (Q64.64) representing a reference trade size as fraction of one asset's inventory.
/// @dev Used by the LMSR stabilization logic to compute target slippage.
function tradeFrac() external view returns (int128); // ABDK 64x64 function tradeFrac() external view returns (int128); // ABDK 64x64
/// @notice Target slippage (Q64.64) applied for the reference trade size specified by tradeFrac.
function targetSlippage() external view returns (int128); // ABDK 64x64 function targetSlippage() external view returns (int128); // ABDK 64x64
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
function swapFeePpm() external view returns (uint256); function swapFeePpm() external view returns (uint256);
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
function flashFeePpm() external view returns (uint256);
/// @notice Mapping from token address => (index+1). A zero value indicates the token is not in the pool.
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
function tokenAddressToIndexPlusOne(address) external view returns (uint); function tokenAddressToIndexPlusOne(address) external view returns (uint);
// Initialization / Mint / Burn (LP token managed) // Initialization / Mint / Burn (LP token managed)
/// @notice Calculate the proportional deposit amounts required for a given LP token amount /// @notice Calculate the proportional deposit amounts required for a given LP token amount
/// @dev Returns the minimum token amounts (rounded up) that must be supplied to receive lpTokenAmount
/// LP tokens at current pool proportions. If the pool is empty (initial deposit) returns zeros
/// because the initial deposit is handled by transferring tokens then calling mint().
/// @param lpTokenAmount The amount of LP tokens desired /// @param lpTokenAmount The amount of LP tokens desired
/// @return depositAmounts Array of token amounts to deposit (rounded up) /// @return depositAmounts Array of token amounts to deposit (rounded up)
function mintDepositAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory depositAmounts); function mintDepositAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory depositAmounts);
/// @notice Proportional mint (or initial supply if first call). /// @notice Proportional mint (or initial supply if first call).
/// For initial supply: assumes tokens have already been transferred to the pool /// @dev - For initial supply: assumes tokens have already been transferred to the pool prior to calling.
/// For subsequent mints: payer must approve tokens beforehand, receiver gets the LP tokens /// - For subsequent mints: payer must approve the required token amounts before calling.
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
/// @param payer address that provides the input tokens (ignored for initial deposit) /// @param payer address that provides the input tokens (ignored for initial deposit)
/// @param receiver address that receives the LP tokens /// @param receiver address that receives the LP tokens
/// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit) /// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit)
@@ -73,12 +108,15 @@ interface IPartyPool is IERC20Metadata {
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external; function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external;
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount /// @notice Calculate the proportional withdrawal amounts for a given LP token amount
/// @dev Returns the maximum token amounts (rounded down) that will be withdrawn when burning lpTokenAmount.
/// If the pool is uninitialized or supply is zero, returns zeros.
/// @param lpTokenAmount The amount of LP tokens to burn /// @param lpTokenAmount The amount of LP tokens to burn
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down) /// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts); function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts);
/// @notice Burn LP tokens and withdraw the proportional basket to receiver. /// @notice Burn LP tokens and withdraw the proportional basket to receiver.
/// Payer must own the LP tokens; withdraw amounts are computed from current proportions. /// @dev Payer must own or approve the LP tokens being burned. The function updates LMSR state
/// proportionally to reflect the reduced pool size after the withdrawal.
/// @param payer address that provides the LP tokens to burn /// @param payer address that provides the LP tokens to burn
/// @param receiver address that receives the withdrawn tokens /// @param receiver address that receives the withdrawn tokens
/// @param lpAmount amount of LP tokens to burn (proportional withdrawal) /// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
@@ -89,73 +127,114 @@ interface IPartyPool is IERC20Metadata {
// Swaps // Swaps
/// @notice External view to quote exact-in swap amounts (gross input incl. fee and output), matching swap() computations /// @notice External view to quote exact-in swap amounts (gross input incl. fee and output), matching swap() computations
/// @param inputTokenIndex index of input token
/// @param outputTokenIndex index of output token
/// @param maxAmountIn maximum gross input allowed (inclusive of fee)
/// @param limitPrice maximum acceptable marginal price (pass 0 to ignore)
/// @return amountIn gross input amount to transfer (includes fee), amountOut output amount user would receive, fee fee amount taken
function swapAmounts( function swapAmounts(
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee); ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee);
/// @notice Swap input token inputTokenIndex -> token outputTokenIndex. Payer must approve token inputTokenIndex.
/// @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.
/// @param payer address of the account that pays for the swap
/// @param receiver address that will receive the output tokens
/// @param inputTokenIndex index of input asset
/// @param outputTokenIndex index of output asset
/// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to transfer in (inclusive of fees)
/// @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.
/// @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,
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external returns (uint256 amountIn, uint256 amountOut, uint256 fee); ) 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
/// @param inputTokenIndex index of input token
/// @param outputTokenIndex index of output token
/// @param limitPrice target marginal price to reach (must be > 0)
/// @return amountIn gross input amount to transfer (includes fee), amountOut output amount user would receive, fee fee amount taken
function swapToLimitAmounts( function swapToLimitAmounts(
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee); ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee);
/// @notice Swap up to the price limit; computes max input to reach limit then performs swap.
/// @dev If balances prevent fully reaching the limit, the function caps and returns actuals.
/// The payer must transfer the exact gross input computed by the view.
/// @param payer address of the account that pays for the swap
/// @param receiver address that will receive the output tokens
/// @param inputTokenIndex index of input asset
/// @param outputTokenIndex index of output asset
/// @param limitPrice target marginal price to reach (must be > 0)
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
/// @return amountInUsed actual input used excluding fee (uint256), amountOut actual output sent (uint256), fee fee taken from the input (uint256)
function swapToLimit( function swapToLimit(
address payer, address payer,
address receiver, address receiver,
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee); ) 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.
/// @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.
/// @param payer who transfers the input token /// @param payer who transfers the input token
/// @param receiver who receives the minted LP tokens /// @param receiver who receives the minted LP tokens
/// @param i index of the input token /// @param inputTokenIndex index of the input token
/// @param maxAmountIn maximum uint token input (inclusive of fee) /// @param maxAmountIn maximum uint token input (inclusive of fee)
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return lpMinted actual LP minted (uint) /// @return lpMinted actual LP minted (uint)
function swapMint( function swapMint(
address payer, address payer,
address receiver, address receiver,
uint256 i, uint256 inputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
uint256 deadline uint256 deadline
) external returns (uint256 lpMinted); ) external returns (uint256 lpMinted);
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `i` 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.
/// @param payer who burns LP tokens /// @param payer who burns LP tokens
/// @param receiver who receives the single asset /// @param receiver who receives the single asset
/// @param lpAmount amount of LP tokens to burn /// @param lpAmount amount of LP tokens to burn
/// @param i index of target asset to receive /// @param inputTokenIndex index of target asset to receive
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return amountOutUint uint amount of asset i sent to receiver /// @return amountOutUint uint amount of asset inputTokenIndex sent to receiver
function burnSwap( function burnSwap(
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
uint256 i, uint256 inputTokenIndex,
uint256 deadline uint256 deadline
) external returns (uint256 amountOutUint); ) external returns (uint256 amountOutUint);
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback /// @notice Compute repayment amounts (principal + flash fee) for a proposed flash loan.
/// @dev The caller of this method receives a callback in the form of IPartyFlashCallback#partyFlashCallback /// @param loanAmounts array of per-token loan amounts; must match the pool's token ordering.
/// @return repaymentAmounts array where repaymentAmounts[i] = loanAmounts[i] + ceil(loanAmounts[i] * flashFeePpm)
function flashRepaymentAmounts(uint256[] memory loanAmounts) external view
returns (uint256[] memory repaymentAmounts);
/// @notice Receive token amounts and require them to be repaid plus a fee inside a callback.
/// @dev The caller must implement IPartyFlashCallback#partyFlashCallback which receives (amounts, repaymentAmounts, data).
/// This function verifies that, after the callback returns, the pool's balances have increased by at least the fees
/// for each borrowed token. Reverts if repayment (including fee) did not occur.
/// @param recipient The address which will receive the token amounts /// @param recipient The address which will receive the token amounts
/// @param amounts The amount of each token to send /// @param amounts The amount of each token to send (array length must equal pool size)
/// @param data Any data to be passed through to the callback /// @param data Any data to be passed through to the callback
function flash( function flash(
address recipient, address recipient,

View File

@@ -13,9 +13,18 @@ import "./IPartyPool.sol";
import "./IPartyFlashCallback.sol"; import "./IPartyFlashCallback.sol";
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token /// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
/// @notice Uses LMSRStabilized library; stores per-token uint bases to convert to/from 64.64 fixed point. /// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
/// - Caches qInternal[] (int128 64.64) and cachedUintBalances[] to minimize balanceOf() calls. /// The pool issues an ERC20 LP token representing proportional ownership.
/// - swap and swapToLimit mimic core lib; mint/burn call updateForProportionalChange() and manage LP tokens. /// It supports:
/// - Proportional minting and burning of LP tokens,
/// - Single-token mint (swapMint) and single-asset withdrawal (burnSwap),
/// - Exact-input swaps and swaps-to-price-limits,
/// - Flash loans via a callback interface.
///
/// @dev The contract stores per-token uint "bases" used to scale token units into the internal Q64.64
/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls.
/// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool
/// (i.e., floor outputs to users, ceil inputs/fees where appropriate).
contract PartyPool is IPartyPool, ERC20, ReentrancyGuard { contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
using ABDKMath64x64 for int128; using ABDKMath64x64 for int128;
using LMSRStabilized for LMSRStabilized.State; using LMSRStabilized for LMSRStabilized.State;
@@ -26,20 +35,31 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
// Immutable pool configuration // Immutable pool configuration
// //
/// @notice Token addresses comprising the pool. Effectively immutable after construction.
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
address[] public tokens; // effectively immutable since there is no interface to change the tokens address[] public tokens; // effectively immutable since there is no interface to change the tokens
/// @inheritdoc IPartyPool
function numTokens() external view returns (uint256) { return tokens.length; } function numTokens() external view returns (uint256) { return tokens.length; }
function allTokens() external view returns (address[] memory) { return tokens; }
/// @inheritdoc IPartyPool
function allTokens() external view returns (address[] memory) { return tokens; }
// NOTE that the slippage target is only exactly achieved in completely balanced pools where all assets are // NOTE that the slippage target is only exactly achieved in completely balanced pools where all assets are
// priced the same. This target is actually a minimum slippage that the pool imposes on traders, and the actual // priced the same. This target is actually a minimum slippage that the pool imposes on traders, and the actual
// slippage cost can be multiples bigger in practice due to pool inventory imbalances. // slippage cost can be multiples bigger in practice due to pool inventory imbalances.
/// @notice Trade fraction (Q64.64) representing a reference trade size as fraction of one asset's inventory.
/// @dev Used by the LMSR stabilization logic to compute target slippage.
int128 public immutable tradeFrac; // slippage target trade size as a fraction of one asset's inventory int128 public immutable tradeFrac; // slippage target trade size as a fraction of one asset's inventory
/// @notice Target slippage (Q64.64) applied for the reference trade size specified by tradeFrac.
int128 public immutable targetSlippage; // target slippage applied to that trade size int128 public immutable targetSlippage; // target slippage applied to that trade size
// fee in parts-per-million (ppm), taken from inputs before swaps /// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
uint256 public immutable swapFeePpm; uint256 public immutable swapFeePpm;
// flash loan fee in parts-per-million (ppm) /// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
uint256 public immutable flashFeePpm; uint256 public immutable flashFeePpm;
// //
@@ -47,16 +67,27 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
// //
LMSRStabilized.State internal lmsr; LMSRStabilized.State internal lmsr;
/// @notice If true and there are exactly two assets, an optimized 2-asset stable-pair path is used for some computations.
bool immutable private _stablePair; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled bool immutable private _stablePair; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled
// Cached on-chain balances (uint) and internal 64.64 representation // Cached on-chain balances (uint) and internal 64.64 representation
// balance / base = internal // balance / base = internal
uint256[] internal cachedUintBalances; uint256[] internal cachedUintBalances;
/// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation.
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
uint256[] internal bases; // per-token uint base used to scale token amounts <-> internal uint256[] internal bases; // per-token uint base used to scale token amounts <-> internal
/// @inheritdoc IPartyPool
function denominators() external view returns (uint256[] memory) { return bases; } function denominators() external view returns (uint256[] memory) { return bases; }
/// @notice Mapping from token address => (index+1). A zero value indicates the token is not in the pool.
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
mapping(address=>uint) public tokenAddressToIndexPlusOne; // Uses index+1 so a result of 0 indicates a failed lookup mapping(address=>uint) public tokenAddressToIndexPlusOne; // Uses index+1 so a result of 0 indicates a failed lookup
/// @notice Scale factor used when converting LMSR Q64.64 totals to LP token units (uint).
/// @dev LP tokens are minted in units equal to ABDK.mulu(lastTotalQ64x64, LP_SCALE).
uint256 public constant LP_SCALE = 1e18; // Scale used to convert LMSR lastTotal (Q64.64) into LP token units (uint) uint256 public constant LP_SCALE = 1e18; // Scale used to convert LMSR lastTotal (Q64.64) into LP token units (uint)
/// @param name_ LP token name /// @param name_ LP token name
@@ -111,9 +142,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
Initialization / Mint / Burn (LP token managed) Initialization / Mint / Burn (LP token managed)
---------------------- */ ---------------------- */
/// @notice Calculate the proportional deposit amounts required for a given LP token amount /// @inheritdoc IPartyPool
/// @param lpTokenAmount The amount of LP tokens desired
/// @return depositAmounts Array of token amounts to deposit (rounded up)
function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) { function mintDepositAmounts(uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
uint256 n = tokens.length; uint256 n = tokens.length;
depositAmounts = new uint256[](n); depositAmounts = new uint256[](n);
@@ -139,9 +168,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
return depositAmounts; return depositAmounts;
} }
/// @notice Calculate the proportional withdrawal amounts for a given LP token amount /// @inheritdoc IPartyPool
/// @param lpTokenAmount The amount of LP tokens to burn
/// @return withdrawAmounts Array of token amounts to withdraw (rounded down)
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) { function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
return _burnReceiveAmounts(lpTokenAmount); return _burnReceiveAmounts(lpTokenAmount);
} }
@@ -168,8 +195,9 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
} }
/// @notice Proportional mint (or initial supply if first call). /// @notice Proportional mint (or initial supply if first call).
/// For initial supply: assumes tokens have already been transferred to the pool /// @dev - For initial supply: assumes tokens have already been transferred to the pool prior to calling.
/// For subsequent mints: payer must approve tokens beforehand, receiver gets the LP tokens /// - For subsequent mints: payer must approve the required token amounts before calling.
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
/// @param payer address that provides the input tokens (ignored for initial deposit) /// @param payer address that provides the input tokens (ignored for initial deposit)
/// @param receiver address that receives the LP tokens /// @param receiver address that receives the LP tokens
/// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit) /// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit)
@@ -267,7 +295,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
} }
/// @notice Burn LP tokens and withdraw the proportional basket to receiver. /// @notice Burn LP tokens and withdraw the proportional basket to receiver.
/// Payer must own the LP tokens; withdraw amounts are computed from current proportions. /// @dev Payer must own or approve the LP tokens being burned. The function updates LMSR state
/// proportionally to reflect the reduced pool size after the withdrawal.
/// @param payer address that provides the LP tokens to burn /// @param payer address that provides the LP tokens to burn
/// @param receiver address that receives the withdrawn tokens /// @param receiver address that receives the withdrawn tokens
/// @param lpAmount amount of LP tokens to burn (proportional withdrawal) /// @param lpAmount amount of LP tokens to burn (proportional withdrawal)
@@ -341,12 +370,13 @@ 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
/// @dev Returns amounts consistent with swap() semantics: grossIn includes fees (ceil), amountOut is floored.
/// @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) /// feeUint fee taken from the gross input (uint)
function _quoteSwapExactIn( function _quoteSwapExactIn(
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice int128 limitPrice
) )
@@ -362,7 +392,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
) )
{ {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n && j < n, "swap: idx"); require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero"); require(maxAmountIn > 0, "swap: input zero");
require(lmsr.nAssets > 0, "swap: empty pool"); require(lmsr.nAssets > 0, "swap: empty pool");
@@ -370,18 +400,18 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
(, uint256 netUintForSwap) = _computeFee(maxAmountIn); (, uint256 netUintForSwap) = _computeFee(maxAmountIn);
// Convert to internal (floor) // Convert to internal (floor)
int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[i]); int128 deltaInternalI = _uintToInternalFloor(netUintForSwap, bases[inputTokenIndex]);
require(deltaInternalI > int128(0), "swap: input too small after fee"); require(deltaInternalI > int128(0), "swap: input too small after fee");
// Compute internal amounts using LMSR (exact-input with price limit) // Compute internal amounts using LMSR (exact-input with price limit)
// if _stablePair is true, use the optimized path // if _stablePair is true, use the optimized path
console2.log('stablepair optimization?', _stablePair); console2.log('stablepair optimization?', _stablePair);
(amountInInternalUsed, amountOutInternal) = (amountInInternalUsed, amountOutInternal) =
_stablePair ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(lmsr, i, j, deltaInternalI, limitPrice) _stablePair ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice)
: lmsr.swapAmountsForExactInput(i, j, deltaInternalI, limitPrice); : lmsr.swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
// Convert actual used input internal -> uint (ceil) // Convert actual used input internal -> uint (ceil)
amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, bases[i]); amountInUintNoFee = _internalToUintCeil(amountInInternalUsed, bases[inputTokenIndex]);
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)
@@ -396,17 +426,18 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
require(grossIn <= maxAmountIn, "swap: transfer exceeds max"); require(grossIn <= maxAmountIn, "swap: transfer exceeds max");
// Compute output (floor) // Compute output (floor)
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]); amountOutUint = _internalToUintFloor(amountOutInternal, bases[outputTokenIndex]);
require(amountOutUint > 0, "swap: output zero"); require(amountOutUint > 0, "swap: output zero");
} }
/// @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
/// @dev Computes the input required to reach limitPrice and the resulting output; all rounding matches swapToLimit.
/// @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) /// feeUint fee taken from the gross input (uint)
function _quoteSwapToLimit( function _quoteSwapToLimit(
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
int128 limitPrice int128 limitPrice
) )
internal internal
@@ -421,15 +452,15 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
) )
{ {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n && j < n, "swapToLimit: idx"); require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
require(limitPrice > int128(0), "swapToLimit: limit <= 0"); require(limitPrice > int128(0), "swapToLimit: limit <= 0");
require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized"); require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized");
// Compute internal maxima at the price limit // Compute internal maxima at the price limit
(amountInInternal, amountOutInternal) = lmsr.swapAmountsForPriceLimit(i, j, limitPrice); (amountInInternal, amountOutInternal) = lmsr.swapAmountsForPriceLimit(inputTokenIndex, outputTokenIndex, limitPrice);
// Convert input to uint (ceil) and output to uint (floor) // Convert input to uint (ceil) and output to uint (floor)
amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[i]); amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[inputTokenIndex]);
require(amountInUintNoFee > 0, "swapToLimit: input zero"); require(amountInUintNoFee > 0, "swapToLimit: input zero");
feeUint = 0; feeUint = 0;
@@ -439,37 +470,39 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
grossIn += feeUint; grossIn += feeUint;
} }
amountOutUint = _internalToUintFloor(amountOutInternal, bases[j]); amountOutUint = _internalToUintFloor(amountOutInternal, bases[outputTokenIndex]);
require(amountOutUint > 0, "swapToLimit: output zero"); require(amountOutUint > 0, "swapToLimit: output zero");
} }
/// @notice External view to quote exact-in swap amounts (gross input incl. fee and output), matching swap() computations /// @inheritdoc IPartyPool
function swapAmounts( function swapAmounts(
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(i, j, maxAmountIn, limitPrice); (uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
return (grossIn, outUint, feeUint); return (grossIn, outUint, feeUint);
} }
/// @notice External view to quote swap-to-limit amounts (gross input incl. fee and output), matching swapToLimit() computations /// @inheritdoc IPartyPool
function swapToLimitAmounts( function swapToLimitAmounts(
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(i, j, limitPrice); (uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
return (grossIn, outUint, feeUint); return (grossIn, outUint, feeUint);
} }
/// @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.
/// Non-standard tokens (fee-on-transfer, rebasers) are rejected via balance checks.
/// @param payer address of the account that pays for the swap /// @param payer address of the account that pays for the swap
/// @param receiver address that will receive the output tokens /// @param receiver address that will receive the output tokens
/// @param i index of input asset /// @param inputTokenIndex index of input asset
/// @param j index of output asset /// @param outputTokenIndex index of output asset
/// @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.
@@ -477,96 +510,97 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
function swap( function swap(
address payer, address payer,
address receiver, address receiver,
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) { ) 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(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
require(maxAmountIn > 0, "swap: input zero"); require(maxAmountIn > 0, "swap: input zero");
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded"); require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
// Read previous balances for affected assets // Read previous balances for affected assets
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this)); uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this)); uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).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 feeUint) = (uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
_quoteSwapExactIn(i, j, maxAmountIn, limitPrice); _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, 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)
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount); _safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransferAmount);
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this)); uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn"); require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
// Transfer output to receiver and verify exact decrease // Transfer output to receiver and verify exact decrease
_safeTransfer(tokens[j], receiver, amountOutUint); _safeTransfer(tokens[outputTokenIndex], receiver, amountOutUint);
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this)); uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut"); require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut");
// Update cached uint balances for i and j using actual balances // Update cached uint balances for i and j using actual balances
cachedUintBalances[i] = balIAfter; cachedUintBalances[inputTokenIndex] = balIAfter;
cachedUintBalances[j] = balJAfter; cachedUintBalances[outputTokenIndex] = balJAfter;
// Apply swap to LMSR state with the internal amounts actually used // Apply swap to LMSR state with the internal amounts actually used
lmsr.applySwap(i, j, amountInInternalUsed, amountOutInternal); lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalUsed, amountOutInternal);
emit Swap(payer, receiver, tokens[i], tokens[j], totalTransferAmount, amountOutUint); emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], totalTransferAmount, amountOutUint);
return (totalTransferAmount, amountOutUint, feeUint); 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.
/// If the pool can't fill entirely because of balances, it caps appropriately and returns actuals. /// @dev If balances prevent fully reaching the limit, the function caps and returns actuals.
/// Payer must approve token i for the exact computed input amount. /// The payer must transfer the exact gross input computed by the view.
/// @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.
function swapToLimit( function swapToLimit(
address payer, address payer,
address receiver, address receiver,
uint256 i, uint256 inputTokenIndex,
uint256 j, uint256 outputTokenIndex,
int128 limitPrice, int128 limitPrice,
uint256 deadline uint256 deadline
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) { ) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n && j < n, "swapToLimit: idx"); require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
require(limitPrice > int128(0), "swapToLimit: limit <= 0"); require(limitPrice > int128(0), "swapToLimit: limit <= 0");
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded"); require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
// Read previous balances for affected assets // Read previous balances for affected assets
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this)); uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
uint256 prevBalJ = IERC20(tokens[j]).balanceOf(address(this)); uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).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 feeUint) = (uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
_quoteSwapToLimit(i, j, limitPrice); _quoteSwapToLimit(inputTokenIndex, outputTokenIndex, 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)
_safeTransferFrom(tokens[i], payer, address(this), totalTransferAmount); _safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransferAmount);
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this)); uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn"); require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
// Transfer output to receiver and verify exact decrease // Transfer output to receiver and verify exact decrease
_safeTransfer(tokens[j], receiver, amountOutUint); _safeTransfer(tokens[outputTokenIndex], receiver, amountOutUint);
uint256 balJAfter = IERC20(tokens[j]).balanceOf(address(this)); uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
require(balJAfter == prevBalJ - amountOutUint, "swapToLimit: non-standard tokenOut"); require(balJAfter == prevBalJ - amountOutUint, "swapToLimit: non-standard tokenOut");
// Update caches to actual balances // Update caches to actual balances
cachedUintBalances[i] = balIAfter; cachedUintBalances[inputTokenIndex] = balIAfter;
cachedUintBalances[j] = balJAfter; cachedUintBalances[outputTokenIndex] = balJAfter;
// Apply swap to LMSR state with the internal amounts // Apply swap to LMSR state with the internal amounts
lmsr.applySwap(i, j, amountInInternalMax, amountOutInternal); lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
// 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[inputTokenIndex], tokens[outputTokenIndex], amountInUsedUint, amountOutUint);
return (amountInUsedUint, amountOutUint, feeUint); 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)
/// @dev Internal helper; public-facing functions use this to ensure fees round up in favor of pool.
function _ceilFee(uint256 x, uint256 feePpm) internal pure returns (uint256) { function _ceilFee(uint256 x, uint256 feePpm) internal pure returns (uint256) {
if (feePpm == 0) return 0; if (feePpm == 0) return 0;
// ceil division: (num + denom - 1) / denom // ceil division: (num + denom - 1) / denom
@@ -595,21 +629,23 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
// which is already present in the standard Mint/Burn events. // which is already present in the standard Mint/Burn events.
/// @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.
/// The function emits SwapMint (gross, net, fee) and also emits Mint for LP issuance.
/// @param payer who transfers the input token /// @param payer who transfers the input token
/// @param receiver who receives the minted LP tokens /// @param receiver who receives the minted LP tokens
/// @param i index of the input token /// @param inputTokenIndex index of the input token
/// @param maxAmountIn maximum uint token input (inclusive of fee) /// @param maxAmountIn maximum uint token input (inclusive of fee)
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return lpMinted actual LP minted (uint) /// @return lpMinted actual LP minted (uint)
function swapMint( function swapMint(
address payer, address payer,
address receiver, address receiver,
uint256 i, uint256 inputTokenIndex,
uint256 maxAmountIn, uint256 maxAmountIn,
uint256 deadline uint256 deadline
) external nonReentrant returns (uint256 lpMinted) { ) external nonReentrant returns (uint256 lpMinted) {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n, "swapMint: idx"); require(inputTokenIndex < n, "swapMint: idx");
require(maxAmountIn > 0, "swapMint: input zero"); require(maxAmountIn > 0, "swapMint: input zero");
require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline"); require(deadline == 0 || block.timestamp <= deadline, "swapMint: deadline");
@@ -620,14 +656,14 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
(, uint256 netUintGuess) = _computeFee(maxAmountIn); (, uint256 netUintGuess) = _computeFee(maxAmountIn);
// Convert the net guess to internal (floor) // Convert the net guess to internal (floor)
int128 netInternalGuess = _uintToInternalFloor(netUintGuess, bases[i]); int128 netInternalGuess = _uintToInternalFloor(netUintGuess, bases[inputTokenIndex]);
require(netInternalGuess > int128(0), "swapMint: input too small after fee"); require(netInternalGuess > int128(0), "swapMint: input too small after fee");
// Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint // Use LMSR view to determine actual internal consumed and size-increase (ΔS) for mint
(int128 amountInInternalUsed, int128 sizeIncreaseInternal) = lmsr.swapAmountsForMint(i, netInternalGuess); (int128 amountInInternalUsed, int128 sizeIncreaseInternal) = lmsr.swapAmountsForMint(inputTokenIndex, netInternalGuess);
// amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer // amountInInternalUsed may be <= netInternalGuess. Convert to uint (ceil) to determine actual transfer
uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, bases[i]); uint256 amountInUint = _internalToUintCeil(amountInInternalUsed, bases[inputTokenIndex]);
require(amountInUint > 0, "swapMint: input zero after internal conversion"); require(amountInUint > 0, "swapMint: input zero after internal conversion");
// Compute fee on the actual used input and total transfer amount (ceiling) // Compute fee on the actual used input and total transfer amount (ceiling)
@@ -636,13 +672,13 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max"); require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
// Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer) // Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer)
uint256 prevBalI = IERC20(tokens[i]).balanceOf(address(this)); uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
_safeTransferFrom(tokens[i], payer, address(this), totalTransfer); _safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransfer);
uint256 balIAfter = IERC20(tokens[i]).balanceOf(address(this)); uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn"); require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn");
// Update cached uint balances for token i (only i changed externally) // Update cached uint balances for token inputTokenIndex (only inputTokenIndex changed externally)
cachedUintBalances[i] = balIAfter; cachedUintBalances[inputTokenIndex] = balIAfter;
// Compute old and new scaled size metrics to determine LP minted // Compute old and new scaled size metrics to determine LP minted
int128 oldTotal = _computeSizeMetric(lmsr.qInternal); int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
@@ -679,13 +715,13 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
// Update cached internal and kappa via updateForProportionalChange // Update cached internal and kappa via updateForProportionalChange
lmsr.updateForProportionalChange(newQInternal); lmsr.updateForProportionalChange(newQInternal);
// Note: we updated cachedUintBalances[i] above via reading balance; other token uint balances did not // Note: we updated cachedUintBalances[inputTokenIndex] above via reading balance; other token uint balances did not
// change externally (they were not transferred in). We keep cachedUintBalances for others unchanged. // change externally (they were not transferred in). We keep cachedUintBalances for others unchanged.
// Mint LP tokens to receiver // Mint LP tokens to receiver
_mint(receiver, actualLpToMint); _mint(receiver, actualLpToMint);
// Emit SwapMint event with gross transfer, net input and fee (planned exact-in) // Emit SwapMint event with gross transfer, net input and fee (planned exact-in)
emit SwapMint(payer, receiver, i, totalTransfer, amountInUint, feeUintActual); emit SwapMint(payer, receiver, inputTokenIndex, totalTransfer, amountInUint, feeUintActual);
// Emit standard Mint event which records deposit amounts and LP minted // Emit standard Mint event which records deposit amounts and LP minted
emit Mint(payer, receiver, new uint256[](n), actualLpToMint); emit Mint(payer, receiver, new uint256[](n), actualLpToMint);
@@ -694,22 +730,23 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
return actualLpToMint; return actualLpToMint;
} }
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `i` 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.
/// @param payer who burns LP tokens /// @param payer who burns LP tokens
/// @param receiver who receives the single asset /// @param receiver who receives the single asset
/// @param lpAmount amount of LP tokens to burn /// @param lpAmount amount of LP tokens to burn
/// @param i index of target asset to receive /// @param inputTokenIndex index of target asset to receive
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return amountOutUint uint amount of asset i sent to receiver /// @return amountOutUint uint amount of asset i sent to receiver
function burnSwap( function burnSwap(
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
uint256 i, uint256 inputTokenIndex,
uint256 deadline uint256 deadline
) external nonReentrant returns (uint256 amountOutUint) { ) external nonReentrant returns (uint256 amountOutUint) {
uint256 n = tokens.length; uint256 n = tokens.length;
require(i < n, "burnSwap: idx"); require(inputTokenIndex < n, "burnSwap: idx");
require(lpAmount > 0, "burnSwap: zero lp"); require(lpAmount > 0, "burnSwap: zero lp");
require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline"); require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline");
@@ -721,14 +758,14 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
int128 alpha = ABDKMath64x64.divu(lpAmount, supply); int128 alpha = ABDKMath64x64.divu(lpAmount, supply);
// Use LMSR view to compute single-asset payout and burned size-metric // Use LMSR view to compute single-asset payout and burned size-metric
(int128 payoutInternal, ) = lmsr.swapAmountsForBurn(i, alpha); (int128 payoutInternal, ) = lmsr.swapAmountsForBurn(inputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool // Convert payoutInternal -> uint (floor) to favor pool
amountOutUint = _internalToUintFloor(payoutInternal, bases[i]); amountOutUint = _internalToUintFloor(payoutInternal, bases[inputTokenIndex]);
require(amountOutUint > 0, "burnSwap: output zero"); require(amountOutUint > 0, "burnSwap: output zero");
// Transfer the payout to receiver // Transfer the payout to receiver
_safeTransfer(tokens[i], receiver, amountOutUint); _safeTransfer(tokens[inputTokenIndex], receiver, amountOutUint);
// Burn LP tokens from payer (authorization via allowance) // Burn LP tokens from payer (authorization via allowance)
if (msg.sender != payer) { if (msg.sender != payer) {
@@ -747,7 +784,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
} }
// Emit BurnSwap with public-facing info only (do not expose ΔS or LP burned) // Emit BurnSwap with public-facing info only (do not expose ΔS or LP burned)
emit BurnSwap(payer, receiver, i, amountOutUint); emit BurnSwap(payer, receiver, inputTokenIndex, amountOutUint);
// If entire pool drained, deinit; else update proportionally // If entire pool drained, deinit; else update proportionally
bool allZero = true; bool allZero = true;
@@ -765,6 +802,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
} }
/// @inheritdoc IPartyPool
function flashRepaymentAmounts(uint256[] memory loanAmounts) external view function flashRepaymentAmounts(uint256[] memory loanAmounts) external view
returns (uint256[] memory repaymentAmounts) { returns (uint256[] memory repaymentAmounts) {
repaymentAmounts = new uint256[](tokens.length); repaymentAmounts = new uint256[](tokens.length);
@@ -777,10 +815,12 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
} }
/// @notice Receive token0 and/or token1 and pay it back, plus a fee, in the callback /// @notice Receive token amounts and require them to be repaid plus a fee inside a callback.
/// @dev The caller of this method receives a callback in the form of IPartyFlashCallback#partyFlashCallback /// @dev The caller must implement IPartyFlashCallback#partyFlashCallback which receives (amounts, repaymentAmounts, data).
/// This function verifies that, after the callback returns, the pool's balances have increased by at least the fees
/// for each borrowed token. Reverts if repayment (including fee) did not occur.
/// @param recipient The address which will receive the token amounts /// @param recipient The address which will receive the token amounts
/// @param amounts The amount of each token to send /// @param amounts The amount of each token to send (array length must equal pool size)
/// @param data Any data to be passed through to the callback /// @param data Any data to be passed through to the callback
function flash( function flash(
address recipient, address recipient,
@@ -886,6 +926,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
} }
/// @notice Helper to compute size metric (sum of all asset quantities) from internal balances /// @notice Helper to compute size metric (sum of all asset quantities) from internal balances
/// @dev Returns the sum of all provided qInternal_ entries as a Q64.64 value.
function _computeSizeMetric(int128[] memory qInternal_) private pure returns (int128) { function _computeSizeMetric(int128[] memory qInternal_) private pure returns (int128) {
int128 total = int128(0); int128 total = int128(0);
for (uint i = 0; i < qInternal_.length; ) { for (uint i = 0; i < qInternal_.length; ) {