PartyPlanner; chain.json
This commit is contained in:
@@ -37,13 +37,13 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
|
||||
/// @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
|
||||
IERC20[] public tokens; // effectively immutable since there is no interface to change the tokens
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function numTokens() external view returns (uint256) { return tokens.length; }
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function allTokens() external view returns (address[] memory) { return tokens; }
|
||||
function allTokens() external view returns (IERC20[] memory) { return tokens; }
|
||||
|
||||
// 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
|
||||
@@ -84,7 +84,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
|
||||
/// @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(IERC20=>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).
|
||||
@@ -102,7 +102,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
constructor(
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
address[] memory _tokens,
|
||||
IERC20[] memory _tokens,
|
||||
uint256[] memory _bases,
|
||||
int128 _tradeFrac,
|
||||
int128 _targetSlippage,
|
||||
@@ -168,6 +168,120 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
return depositAmounts;
|
||||
}
|
||||
|
||||
/// @notice Initial mint to set up pool for the first time.
|
||||
/// @dev Assumes tokens have already been transferred to the pool prior to calling.
|
||||
/// Can only be called when the pool is uninitialized (totalSupply() == 0 or lmsr.nAssets == 0).
|
||||
/// @param receiver address that receives the LP tokens
|
||||
/// @param lpTokens The number of LP tokens to issue for this mint. If 0, then the number of tokens returned will equal the LMSR internal q total
|
||||
function initialMint(address receiver, uint256 lpTokens) external nonReentrant
|
||||
returns (uint256 lpMinted) {
|
||||
uint256 n = tokens.length;
|
||||
|
||||
// Check if this is initial deposit - revert if not
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
require(isInitialDeposit, "initialMint: pool already initialized");
|
||||
|
||||
// Update cached balances for all assets
|
||||
int128[] memory newQInternal = new int128[](n);
|
||||
uint256[] memory depositAmounts = new uint256[](n);
|
||||
for (uint i = 0; i < n; ) {
|
||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||
cachedUintBalances[i] = bal;
|
||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||
depositAmounts[i] = bal;
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Initialize the stabilized LMSR state
|
||||
lmsr.init(newQInternal, tradeFrac, targetSlippage);
|
||||
|
||||
// Compute actual LP tokens to mint based on size metric (scaled)
|
||||
if( lpTokens != 0 )
|
||||
lpMinted = lpTokens;
|
||||
else {
|
||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||
lpMinted = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
}
|
||||
|
||||
require(lpMinted > 0, "initialMint: zero LP amount");
|
||||
_mint(receiver, lpMinted);
|
||||
emit Mint(address(0), receiver, depositAmounts, lpMinted);
|
||||
}
|
||||
|
||||
/// @notice Proportional mint for existing pool.
|
||||
/// @dev Payer must approve the required token amounts before calling.
|
||||
/// Can only be called when pool is already initialized (totalSupply() > 0 and lmsr.nAssets > 0).
|
||||
/// Rounds follow the pool-favorable conventions documented in helpers (ceil inputs, floor outputs).
|
||||
/// @param payer address that provides the input tokens
|
||||
/// @param receiver address that receives the LP tokens
|
||||
/// @param lpTokenAmount desired amount of LP tokens to mint
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external nonReentrant
|
||||
returns (uint256 lpMinted) {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
||||
uint256 n = tokens.length;
|
||||
|
||||
// Check if this is NOT initial deposit - revert if it is
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
require(!isInitialDeposit, "mint: use initialMint for pool initialization");
|
||||
require(lpTokenAmount > 0, "mint: zero LP amount");
|
||||
|
||||
// Capture old pool size metric (scaled) by computing from current balances
|
||||
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||
|
||||
// Calculate required deposit amounts for the desired LP tokens
|
||||
uint256[] memory depositAmounts = mintDepositAmounts(lpTokenAmount);
|
||||
|
||||
// Transfer in all token amounts
|
||||
for (uint i = 0; i < n; ) {
|
||||
if (depositAmounts[i] > 0) {
|
||||
tokens[i].safeTransferFrom(payer, address(this), depositAmounts[i]);
|
||||
}
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Update cached balances for all assets
|
||||
int128[] memory newQInternal = new int128[](n);
|
||||
for (uint i = 0; i < n; ) {
|
||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||
cachedUintBalances[i] = bal;
|
||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Update for proportional change
|
||||
lmsr.updateForProportionalChange(newQInternal);
|
||||
|
||||
// Compute actual LP tokens to mint based on change in size metric (scaled)
|
||||
// floor truncation rounds in favor of the pool
|
||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
uint256 actualLpToMint;
|
||||
|
||||
require(oldScaled > 0, "mint: oldScaled zero");
|
||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||
// Proportional issuance: totalSupply * delta / oldScaled
|
||||
if (delta > 0) {
|
||||
// floor truncation rounds in favor of the pool
|
||||
actualLpToMint = (totalSupply() * delta) / oldScaled;
|
||||
} else {
|
||||
actualLpToMint = 0;
|
||||
}
|
||||
|
||||
// Ensure the calculated LP amount is not too different from requested
|
||||
require(actualLpToMint > 0, "mint: zero LP minted");
|
||||
|
||||
// Allow actual amount to be at most 0.00001% less than requested
|
||||
// This accounts for rounding in deposit calculations
|
||||
uint256 minAcceptable = lpTokenAmount * 99_999 / 100_000;
|
||||
require(actualLpToMint >= minAcceptable, "mint: insufficient LP minted");
|
||||
|
||||
_mint(receiver, actualLpToMint);
|
||||
emit Mint(payer, receiver, depositAmounts, actualLpToMint);
|
||||
return actualLpToMint;
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function burnReceiveAmounts(uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
|
||||
return _burnReceiveAmounts(lpTokenAmount);
|
||||
@@ -194,106 +308,6 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
return withdrawAmounts;
|
||||
}
|
||||
|
||||
/// @notice Proportional mint (or initial supply if first call).
|
||||
/// @dev - For initial supply: assumes tokens have already been transferred to the pool prior to calling.
|
||||
/// - 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 receiver address that receives the LP tokens
|
||||
/// @param lpTokenAmount desired amount of LP tokens to mint (ignored for initial deposit)
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function mint(address payer, address receiver, uint256 lpTokenAmount, uint256 deadline) external nonReentrant {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "mint: deadline exceeded");
|
||||
uint256 n = tokens.length;
|
||||
// Check if this is initial deposit
|
||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
||||
|
||||
require(lpTokenAmount > 0 || isInitialDeposit, "mint: zero LP amount");
|
||||
|
||||
// Capture old pool size metric (scaled) by computing from current balances
|
||||
uint256 oldScaled = 0;
|
||||
if (!isInitialDeposit) {
|
||||
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
||||
oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||
}
|
||||
|
||||
// For non-initial deposits, transfer tokens from payer
|
||||
uint256[] memory depositAmounts = new uint256[](n);
|
||||
|
||||
if (!isInitialDeposit) {
|
||||
// Calculate required deposit amounts for the desired LP tokens
|
||||
depositAmounts = mintDepositAmounts(lpTokenAmount);
|
||||
|
||||
// Transfer in all token amounts
|
||||
for (uint i = 0; i < n; ) {
|
||||
if (depositAmounts[i] > 0) {
|
||||
_safeTransferFrom(tokens[i], payer, address(this), depositAmounts[i]);
|
||||
}
|
||||
unchecked { i++; }
|
||||
}
|
||||
}
|
||||
|
||||
// Update cached balances for all assets
|
||||
int128[] memory newQInternal = new int128[](n);
|
||||
for (uint i = 0; i < n; ) {
|
||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
||||
cachedUintBalances[i] = bal;
|
||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
||||
|
||||
// For initial deposit, record the actual deposited amounts
|
||||
if (isInitialDeposit) {
|
||||
depositAmounts[i] = bal;
|
||||
}
|
||||
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// If first time, call init, otherwise update proportional change.
|
||||
if (isInitialDeposit) {
|
||||
// Initialize the stabilized LMSR state
|
||||
lmsr.init(newQInternal, tradeFrac, targetSlippage);
|
||||
} else {
|
||||
// Update for proportional change
|
||||
lmsr.updateForProportionalChange(newQInternal);
|
||||
}
|
||||
|
||||
// Compute actual LP tokens to mint based on change in size metric (scaled)
|
||||
// floor truncation rounds in favor of the pool
|
||||
int128 newTotal = _computeSizeMetric(newQInternal);
|
||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||
uint256 actualLpToMint;
|
||||
|
||||
if (isInitialDeposit) {
|
||||
// Initial provisioning: mint newScaled (as LP units)
|
||||
actualLpToMint = newScaled;
|
||||
} else {
|
||||
require(oldScaled > 0, "mint: oldScaled zero");
|
||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||
// Proportional issuance: totalSupply * delta / oldScaled
|
||||
if (delta > 0) {
|
||||
// floor truncation rounds in favor of the pool
|
||||
actualLpToMint = (totalSupply() * delta) / oldScaled;
|
||||
} else {
|
||||
actualLpToMint = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// For subsequent mints, ensure the calculated LP amount is not too different from requested
|
||||
if (!isInitialDeposit) {
|
||||
// Allow for some rounding error but ensure we're not far off from requested amount
|
||||
require(actualLpToMint > 0, "mint: zero LP minted");
|
||||
|
||||
// Allow actual amount to be at most 0.00001% less than requested
|
||||
// This accounts for rounding in deposit calculations
|
||||
uint256 minAcceptable = lpTokenAmount * 99_999 / 100_000;
|
||||
require(actualLpToMint >= minAcceptable, "mint: insufficient LP minted");
|
||||
}
|
||||
|
||||
require( actualLpToMint > 0, "mint: zero LP amount");
|
||||
_mint(receiver, actualLpToMint);
|
||||
emit Mint(payer, receiver, depositAmounts, actualLpToMint);
|
||||
}
|
||||
|
||||
/// @notice Burn LP tokens and withdraw the proportional basket to receiver.
|
||||
/// @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.
|
||||
@@ -324,7 +338,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
// Transfer underlying tokens out to receiver according to computed proportions
|
||||
for (uint i = 0; i < n; ) {
|
||||
if (withdrawAmounts[i] > 0) {
|
||||
_safeTransfer(tokens[i], receiver, withdrawAmounts[i]);
|
||||
tokens[i].safeTransfer(receiver, withdrawAmounts[i]);
|
||||
}
|
||||
unchecked { i++; }
|
||||
}
|
||||
@@ -369,6 +383,139 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
Swaps
|
||||
---------------------- */
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function swapAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function swapToLimitAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
|
||||
/// @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 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 i (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(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
|
||||
require(maxAmountIn > 0, "swap: input zero");
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
|
||||
|
||||
// Read previous balances for affected assets
|
||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
|
||||
// Compute amounts using the same path as views
|
||||
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
|
||||
_quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
||||
|
||||
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
|
||||
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
tokens[outputTokenIndex].safeTransfer(receiver, amountOutUint);
|
||||
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut");
|
||||
|
||||
// Update cached uint balances for i and j using actual balances
|
||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
||||
cachedUintBalances[outputTokenIndex] = balJAfter;
|
||||
|
||||
// Apply swap to LMSR state with the internal amounts actually used
|
||||
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalUsed, amountOutInternal);
|
||||
|
||||
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], totalTransferAmount, amountOutUint);
|
||||
|
||||
return (totalTransferAmount, amountOutUint, feeUint);
|
||||
}
|
||||
|
||||
/// @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 deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
|
||||
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
||||
|
||||
// Read previous balances for affected assets
|
||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
|
||||
// Compute amounts using the same path as views
|
||||
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
|
||||
_quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
|
||||
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
|
||||
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransferAmount);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
tokens[outputTokenIndex].safeTransfer(receiver, amountOutUint);
|
||||
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
require(balJAfter == prevBalJ - amountOutUint, "swapToLimit: non-standard tokenOut");
|
||||
|
||||
// Update caches to actual balances
|
||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
||||
cachedUintBalances[outputTokenIndex] = balJAfter;
|
||||
|
||||
// Apply swap to LMSR state with the internal amounts
|
||||
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
|
||||
|
||||
// Maintain original event semantics (logs input without fee)
|
||||
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], amountInUsedUint, amountOutUint);
|
||||
|
||||
return (amountInUsedUint, amountOutUint, feeUint);
|
||||
}
|
||||
|
||||
/// @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) {
|
||||
if (feePpm == 0) return 0;
|
||||
// ceil division: (num + denom - 1) / denom
|
||||
return (x * feePpm + 1_000_000 - 1) / 1_000_000;
|
||||
}
|
||||
|
||||
/// @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),
|
||||
@@ -474,139 +621,6 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
require(amountOutUint > 0, "swapToLimit: output zero");
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function swapAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function swapToLimitAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
|
||||
|
||||
/// @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 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 i (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(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external nonReentrant returns (uint256 amountIn, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swap: idx");
|
||||
require(maxAmountIn > 0, "swap: input zero");
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
|
||||
|
||||
// Read previous balances for affected assets
|
||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
|
||||
// Compute amounts using the same path as views
|
||||
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalUsed, int128 amountOutInternal, , uint256 feeUint) =
|
||||
_quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
||||
|
||||
// Transfer the exact amount from payer and require exact receipt (revert on fee-on-transfer)
|
||||
_safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransferAmount);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swap: non-standard tokenIn");
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
_safeTransfer(tokens[outputTokenIndex], receiver, amountOutUint);
|
||||
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
require(balJAfter == prevBalJ - amountOutUint, "swap: non-standard tokenOut");
|
||||
|
||||
// Update cached uint balances for i and j using actual balances
|
||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
||||
cachedUintBalances[outputTokenIndex] = balJAfter;
|
||||
|
||||
// Apply swap to LMSR state with the internal amounts actually used
|
||||
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalUsed, amountOutInternal);
|
||||
|
||||
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], totalTransferAmount, amountOutUint);
|
||||
|
||||
return (totalTransferAmount, amountOutUint, feeUint);
|
||||
}
|
||||
|
||||
/// @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 deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline
|
||||
) external returns (uint256 amountInUsed, uint256 amountOut, uint256 fee) {
|
||||
uint256 n = tokens.length;
|
||||
require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
|
||||
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
||||
|
||||
// Read previous balances for affected assets
|
||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
|
||||
// Compute amounts using the same path as views
|
||||
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
|
||||
_quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
||||
|
||||
// Transfer the exact amount needed from payer and require exact receipt (revert on fee-on-transfer)
|
||||
_safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransferAmount);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
_safeTransfer(tokens[outputTokenIndex], receiver, amountOutUint);
|
||||
uint256 balJAfter = IERC20(tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
require(balJAfter == prevBalJ - amountOutUint, "swapToLimit: non-standard tokenOut");
|
||||
|
||||
// Update caches to actual balances
|
||||
cachedUintBalances[inputTokenIndex] = balIAfter;
|
||||
cachedUintBalances[outputTokenIndex] = balJAfter;
|
||||
|
||||
// Apply swap to LMSR state with the internal amounts
|
||||
lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
|
||||
|
||||
// Maintain original event semantics (logs input without fee)
|
||||
emit Swap(payer, receiver, tokens[inputTokenIndex], tokens[outputTokenIndex], amountInUsedUint, amountOutUint);
|
||||
|
||||
return (amountInUsedUint, amountOutUint, feeUint);
|
||||
}
|
||||
|
||||
/// @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) {
|
||||
if (feePpm == 0) return 0;
|
||||
// ceil division: (num + denom - 1) / denom
|
||||
return (x * feePpm + 1_000_000 - 1) / 1_000_000;
|
||||
}
|
||||
|
||||
/// @notice Compute fee and net amounts for a gross input (fee rounded up to favor the pool).
|
||||
/// @return feeUint fee taken (uint) and netUint remaining for protocol use (uint)
|
||||
function _computeFee(uint256 gross) internal view returns (uint256 feeUint, uint256 netUint) {
|
||||
@@ -673,7 +687,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
|
||||
// Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer)
|
||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
_safeTransferFrom(tokens[inputTokenIndex], payer, address(this), totalTransfer);
|
||||
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransfer);
|
||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn");
|
||||
|
||||
@@ -765,7 +779,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
require(amountOutUint > 0, "burnSwap: output zero");
|
||||
|
||||
// Transfer the payout to receiver
|
||||
_safeTransfer(tokens[inputTokenIndex], receiver, amountOutUint);
|
||||
tokens[inputTokenIndex].safeTransfer(receiver, amountOutUint);
|
||||
|
||||
// Burn LP tokens from payer (authorization via allowance)
|
||||
if (msg.sender != payer) {
|
||||
@@ -853,7 +867,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
initialBalances[i] = IERC20(tokens[i]).balanceOf(address(this));
|
||||
|
||||
// Transfer token to recipient
|
||||
_safeTransfer(tokens[i], recipient, amount);
|
||||
tokens[i].safeTransfer(recipient, amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -913,18 +927,6 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
return floorValue;
|
||||
}
|
||||
|
||||
/* ----------------------
|
||||
ERC20 helpers (minimal)
|
||||
---------------------- */
|
||||
|
||||
function _safeTransferFrom(address token, address from, address to, uint256 amt) internal {
|
||||
IERC20(token).safeTransferFrom(from, to, amt);
|
||||
}
|
||||
|
||||
function _safeTransfer(address token, address to, uint256 amt) internal {
|
||||
IERC20(token).safeTransfer(to, amt);
|
||||
}
|
||||
|
||||
/// @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) {
|
||||
|
||||
Reference in New Issue
Block a user