removed state.nAssets

This commit is contained in:
tim
2025-10-29 19:35:41 -04:00
parent 20758cfb35
commit dd9b7474a6
9 changed files with 76 additions and 113 deletions

View File

@@ -250,13 +250,11 @@ interface IPartyPool is IERC20Metadata, IOwnable {
bool unwrap bool unwrap
) external returns (uint256 amountOutUint); ) external returns (uint256 amountOutUint);
/** /// @dev Initiate a flash loan.
* @dev Initiate a flash loan. /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback. /// @param token The loan currency.
* @param token The loan currency. /// @param amount The amount of tokens lent.
* @param amount The amount of tokens lent. /// @param data Arbitrary data structure, intended to contain user-defined parameters.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan( function flashLoan(
IERC3156FlashBorrower receiver, IERC3156FlashBorrower receiver,
address token, address token,

View File

@@ -12,7 +12,6 @@ library LMSRStabilized {
using ABDKMath64x64 for int128; using ABDKMath64x64 for int128;
struct State { struct State {
uint256 nAssets;
int128 kappa; // liquidity parameter κ (64.64 fixed point) int128 kappa; // liquidity parameter κ (64.64 fixed point)
int128[] qInternal; // cached internal balances in 64.64 fixed-point format int128[] qInternal; // cached internal balances in 64.64 fixed-point format
} }
@@ -28,8 +27,6 @@ library LMSRStabilized {
int128[] memory initialQInternal, int128[] memory initialQInternal,
int128 kappa int128 kappa
) internal { ) internal {
s.nAssets = initialQInternal.length;
// Initialize qInternal cache // Initialize qInternal cache
if (s.qInternal.length != initialQInternal.length) { if (s.qInternal.length != initialQInternal.length) {
s.qInternal = new int128[](initialQInternal.length); s.qInternal = new int128[](initialQInternal.length);
@@ -98,7 +95,7 @@ library LMSRStabilized {
int128 a, int128 a,
int128 limitPrice int128 limitPrice
) internal view returns (int128 amountIn, int128 amountOut) { ) internal view returns (int128 amountIn, int128 amountOut) {
return swapAmountsForExactInput(s.nAssets, s.kappa, s.qInternal, i, j, a, limitPrice); return swapAmountsForExactInput(s.kappa, s.qInternal, i, j, a, limitPrice);
} }
/// @notice Pure version: Closed-form asset-i -> asset-j amountOut in 64.64 fixed-point format (fee-free kernel) /// @notice Pure version: Closed-form asset-i -> asset-j amountOut in 64.64 fixed-point format (fee-free kernel)
@@ -113,7 +110,6 @@ library LMSRStabilized {
/// ///
/// NOTE: Kernel is fee-free; fees should be handled by the wrapper/token layer. /// NOTE: Kernel is fee-free; fees should be handled by the wrapper/token layer.
/// ///
/// @param nAssets Number of assets in the pool
/// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format /// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param i Index of input asset /// @param i Index of input asset
@@ -123,7 +119,6 @@ library LMSRStabilized {
/// @return amountIn Actual amount of input asset used (may be less than `a` if limited by price) /// @return amountIn Actual amount of input asset used (may be less than `a` if limited by price)
/// @return amountOut Amount of output asset j in 64.64 fixed-point format /// @return amountOut Amount of output asset j in 64.64 fixed-point format
function swapAmountsForExactInput( function swapAmountsForExactInput(
uint256 nAssets,
int128 kappa, int128 kappa,
int128[] memory qInternal, int128[] memory qInternal,
uint256 i, uint256 i,
@@ -217,7 +212,7 @@ library LMSRStabilized {
uint256 j, uint256 j,
int128 limitPrice int128 limitPrice
) internal view returns (int128 amountIn, int128 amountOut) { ) internal view returns (int128 amountIn, int128 amountOut) {
return swapAmountsForPriceLimit(s.nAssets, s.kappa, s.qInternal, i, j, limitPrice); return swapAmountsForPriceLimit(s.kappa, s.qInternal, i, j, limitPrice);
} }
/// @notice Pure version: Maximum input/output pair possible when swapping from asset i to asset j /// @notice Pure version: Maximum input/output pair possible when swapping from asset i to asset j
@@ -226,7 +221,6 @@ library LMSRStabilized {
/// and the corresponding output amount (amountOut). If the output would exceed the /// and the corresponding output amount (amountOut). If the output would exceed the
/// j-balance, amountOut is capped and amountIn is solved for the capped output. /// j-balance, amountOut is capped and amountIn is solved for the capped output.
/// ///
/// @param nAssets Number of assets in the pool
/// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format /// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param i Index of input asset /// @param i Index of input asset
@@ -235,7 +229,6 @@ library LMSRStabilized {
/// @return amountIn Maximum input amount in 64.64 fixed-point format that reaches the price limit /// @return amountIn Maximum input amount in 64.64 fixed-point format that reaches the price limit
/// @return amountOut Corresponding maximum output amount in 64.64 fixed-point format /// @return amountOut Corresponding maximum output amount in 64.64 fixed-point format
function swapAmountsForPriceLimit( function swapAmountsForPriceLimit(
uint256 nAssets,
int128 kappa, int128 kappa,
int128[] memory qInternal, int128[] memory qInternal,
uint256 i, uint256 i,
@@ -318,7 +311,7 @@ library LMSRStabilized {
uint256 i, uint256 i,
int128 a int128 a
) internal view returns (int128 amountIn, int128 amountOut) { ) internal view returns (int128 amountIn, int128 amountOut) {
return swapAmountsForMint(s.nAssets, s.kappa, s.qInternal, i, a); return swapAmountsForMint(s.kappa, s.qInternal, i, a);
} }
/// @notice Pure version: Compute LP-size increase when minting from a single-token input using bisection only. /// @notice Pure version: Compute LP-size increase when minting from a single-token input using bisection only.
@@ -327,7 +320,6 @@ library LMSRStabilized {
/// where x_j(α) is the input to swap i->j that yields y_j = α*q_j and /// where x_j(α) is the input to swap i->j that yields y_j = α*q_j and
/// x_j = b * ln( r0_j / (r0_j + 1 - exp(y_j / b)) ), r0_j = exp((q_i - q_j)/b). /// x_j = b * ln( r0_j / (r0_j + 1 - exp(y_j / b)) ), r0_j = exp((q_i - q_j)/b).
/// Bisection is used (no Newton) to keep implementation compact and gas-friendly. /// Bisection is used (no Newton) to keep implementation compact and gas-friendly.
/// @param nAssets Number of assets in the pool
/// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format /// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param i Index of input asset /// @param i Index of input asset
@@ -335,13 +327,13 @@ library LMSRStabilized {
/// @return amountIn Actual amount of input consumed /// @return amountIn Actual amount of input consumed
/// @return amountOut LP size-metric increase (alpha * S) /// @return amountOut LP size-metric increase (alpha * S)
function swapAmountsForMint( function swapAmountsForMint(
uint256 nAssets,
int128 kappa, int128 kappa,
int128[] memory qInternal, int128[] memory qInternal,
uint256 i, uint256 i,
int128 a int128 a
) internal pure returns (int128 amountIn, int128 amountOut) { ) internal pure returns (int128 amountIn, int128 amountOut) {
require(i < nAssets, "LMSR: idx"); uint256 n = qInternal.length;
require(i < n, "LMSR: idx");
require(a > int128(0), "LMSR: amount <= 0"); require(a > int128(0), "LMSR: amount <= 0");
int128 sizeMetric = _computeSizeMetric(qInternal); int128 sizeMetric = _computeSizeMetric(qInternal);
@@ -351,8 +343,6 @@ library LMSRStabilized {
int128 invB = ABDKMath64x64.div(ONE, b); int128 invB = ABDKMath64x64.div(ONE, b);
int128 S = sizeMetric; int128 S = sizeMetric;
uint256 n = nAssets;
// Precompute r0_j = exp((q_i - q_j) / b) for all j to avoid recomputing during search. // Precompute r0_j = exp((q_i - q_j) / b) for all j to avoid recomputing during search.
int128[] memory r0 = new int128[](n); int128[] memory r0 = new int128[](n);
for (uint256 j = 0; j < n; ) { for (uint256 j = 0; j < n; ) {
@@ -530,7 +520,7 @@ library LMSRStabilized {
uint256 i, uint256 i,
int128 alpha int128 alpha
) internal view returns (int128 amountOut, int128 amountIn) { ) internal view returns (int128 amountOut, int128 amountIn) {
return swapAmountsForBurn(s.nAssets, s.kappa, s.qInternal, i, alpha); return swapAmountsForBurn(s.kappa, s.qInternal, i, alpha);
} }
/// @notice Pure version: Compute single-asset payout when burning a proportional share alpha of the pool. /// @notice Pure version: Compute single-asset payout when burning a proportional share alpha of the pool.
@@ -542,7 +532,6 @@ library LMSRStabilized {
/// - cap output to q_local[i] when necessary (solve inverse for input used) /// - cap output to q_local[i] when necessary (solve inverse for input used)
/// Treat any per-asset rhs<=0 as "this asset contributes zero" (do not revert). /// Treat any per-asset rhs<=0 as "this asset contributes zero" (do not revert).
/// Revert only if the final single-asset payout is zero. /// Revert only if the final single-asset payout is zero.
/// @param nAssets Number of assets in the pool
/// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format /// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param i Index of output asset /// @param i Index of output asset
@@ -550,13 +539,11 @@ library LMSRStabilized {
/// @return amountOut Amount of asset i received (in 64.64 fixed-point) /// @return amountOut Amount of asset i received (in 64.64 fixed-point)
/// @return amountIn LP size-metric redeemed (alpha * S) /// @return amountIn LP size-metric redeemed (alpha * S)
function swapAmountsForBurn( function swapAmountsForBurn(
uint256 nAssets,
int128 kappa, int128 kappa,
int128[] memory qInternal, int128[] memory qInternal,
uint256 i, uint256 i,
int128 alpha int128 alpha
) internal pure returns (int128 amountOut, int128 amountIn) { ) internal pure returns (int128 amountOut, int128 amountIn) {
require(i < nAssets, "LMSR: idx");
require(alpha > int128(0) && alpha <= ONE, "LMSR: alpha"); require(alpha > int128(0) && alpha <= ONE, "LMSR: alpha");
int128 sizeMetric = _computeSizeMetric(qInternal); int128 sizeMetric = _computeSizeMetric(qInternal);
@@ -565,7 +552,7 @@ library LMSRStabilized {
require(b > int128(0), "LMSR: b<=0"); require(b > int128(0), "LMSR: b<=0");
int128 invB = ABDKMath64x64.div(ONE, b); int128 invB = ABDKMath64x64.div(ONE, b);
uint256 n = nAssets; uint256 n = qInternal.length;
// Size metric and burned size (amountIn returned) // Size metric and burned size (amountIn returned)
int128 S = sizeMetric; int128 S = sizeMetric;
@@ -675,7 +662,6 @@ library LMSRStabilized {
int128 amountIn, int128 amountIn,
int128 amountOut int128 amountOut
) internal { ) internal {
require(i < s.nAssets && j < s.nAssets, "LMSR: idx");
require(amountIn > int128(0), "LMSR: amountIn <= 0"); require(amountIn > int128(0), "LMSR: amountIn <= 0");
require(amountOut > int128(0), "LMSR: amountOut <= 0"); require(amountOut > int128(0), "LMSR: amountOut <= 0");
@@ -690,15 +676,14 @@ library LMSRStabilized {
/// Updates the internal qInternal cache with the new balances /// Updates the internal qInternal cache with the new balances
/// @param newQInternal New asset quantities after mint/redeem (64.64 format) /// @param newQInternal New asset quantities after mint/redeem (64.64 format)
function updateForProportionalChange(State storage s, int128[] memory newQInternal) internal { function updateForProportionalChange(State storage s, int128[] memory newQInternal) internal {
require(newQInternal.length == s.nAssets, "LMSR: length mismatch");
// Compute new total for validation // Compute new total for validation
int128 newTotal = _computeSizeMetric(newQInternal); int128 newTotal = _computeSizeMetric(newQInternal);
require(newTotal > int128(0), "LMSR: new total zero"); require(newTotal > int128(0), "LMSR: new total zero");
// Update the cached qInternal with new values // Update the cached qInternal with new values
for (uint i = 0; i < s.nAssets; ) { uint256 n = newQInternal.length;
for (uint i = 0; i < n; ) {
s.qInternal[i] = newQInternal[i]; s.qInternal[i] = newQInternal[i];
unchecked { i++; } unchecked { i++; }
} }
@@ -775,19 +760,17 @@ library LMSRStabilized {
/// @notice Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64 /// @notice Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64
/// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0. /// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0.
function price(State storage s, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal view returns (int128) { function price(State storage s, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal view returns (int128) {
return price(s.nAssets, s.kappa, s.qInternal, baseTokenIndex, quoteTokenIndex); return price(s.kappa, s.qInternal, baseTokenIndex, quoteTokenIndex);
} }
/// @notice Pure version: Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64 /// @notice Pure version: Marginal price of `base` in terms of `quote` (p_quote / p_base) as Q64.64
/// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0. /// @dev Returns exp((q_quote - q_base) / b). Indices must be valid and b > 0.
/// @param nAssets Number of assets in the pool
/// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format /// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param baseTokenIndex Index of base token /// @param baseTokenIndex Index of base token
/// @param quoteTokenIndex Index of quote token /// @param quoteTokenIndex Index of quote token
/// @return Price in 64.64 fixed-point format /// @return Price in 64.64 fixed-point format
function price(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal pure returns (int128) { function price(int128 kappa, int128[] memory qInternal, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal pure returns (int128) {
require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "LMSR: idx");
int128 sizeMetric = _computeSizeMetric(qInternal); int128 sizeMetric = _computeSizeMetric(qInternal);
require(sizeMetric > int128(0), "LMSR: size metric zero"); require(sizeMetric > int128(0), "LMSR: size metric zero");
int128 b = kappa.mul(sizeMetric); int128 b = kappa.mul(sizeMetric);
@@ -803,18 +786,16 @@ library LMSRStabilized {
/// @notice Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64) /// @notice Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64)
/// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b) /// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b)
function poolPrice(State storage s, uint256 quoteTokenIndex) internal view returns (int128) { function poolPrice(State storage s, uint256 quoteTokenIndex) internal view returns (int128) {
return poolPrice(s.nAssets, s.kappa, s.qInternal, quoteTokenIndex); return poolPrice(s.kappa, s.qInternal, quoteTokenIndex);
} }
/// @notice Pure version: Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64) /// @notice Pure version: Price of one unit of the LP size-metric (S = sum q_i) denominated in `quote` asset (Q64.64)
/// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b) /// @dev Computes: poolPrice_quote = (1 / S) * sum_j q_j * exp((q_j - q_quote) / b)
/// @param nAssets Number of assets in the pool
/// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param kappa Liquidity parameter κ (64.64 fixed point)
/// @param qInternal Cached internal balances in 64.64 fixed-point format /// @param qInternal Cached internal balances in 64.64 fixed-point format
/// @param quoteTokenIndex Index of quote token /// @param quoteTokenIndex Index of quote token
/// @return Pool price in 64.64 fixed-point format /// @return Pool price in 64.64 fixed-point format
function poolPrice(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) { function poolPrice(int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) {
require(quoteTokenIndex < nAssets, "LMSR: idx");
// Compute b and ensure positivity // Compute b and ensure positivity
int128 sizeMetric = _computeSizeMetric(qInternal); int128 sizeMetric = _computeSizeMetric(qInternal);
require(sizeMetric > int128(0), "LMSR: size metric zero"); require(sizeMetric > int128(0), "LMSR: size metric zero");
@@ -830,7 +811,7 @@ library LMSRStabilized {
// Accumulate weighted exponentials: sum_j q_j * exp((q_j - q_quote) / b) // Accumulate weighted exponentials: sum_j q_j * exp((q_j - q_quote) / b)
int128 acc = int128(0); int128 acc = int128(0);
uint256 n = nAssets; uint256 n = qInternal.length;
for (uint256 j = 0; j < n; ) { for (uint256 j = 0; j < n; ) {
// factor = exp((q_j - q_quote) / b) // factor = exp((q_j - q_quote) / b)
int128 factor = _exp(qInternal[j].sub(qInternal[quoteTokenIndex]).mul(invB)); int128 factor = _exp(qInternal[j].sub(qInternal[quoteTokenIndex]).mul(invB));
@@ -917,7 +898,6 @@ library LMSRStabilized {
/// This resets the state so the pool can be re-initialized by init(...) on next mint. /// This resets the state so the pool can be re-initialized by init(...) on next mint.
function deinit(State storage s) internal { function deinit(State storage s) internal {
// Reset core state // Reset core state
s.nAssets = 0;
s.kappa = int128(0); s.kappa = int128(0);
// Clear qInternal array // Clear qInternal array

View File

@@ -35,10 +35,11 @@ library LMSRStabilizedBalancedPair {
int128 limitPrice int128 limitPrice
) internal view returns (int128 amountIn, int128 amountOut) { ) internal view returns (int128 amountIn, int128 amountOut) {
// Quick index check // Quick index check
require(i < s.nAssets && j < s.nAssets, "LMSR: idx"); uint256 nAssets = s.qInternal.length;
require(i < nAssets && j < nAssets, "LMSR: idx");
// If not exactly a two-asset pool, fall back to the general routine. // If not exactly a two-asset pool, fall back to the general routine.
if (s.nAssets != 2) { if (nAssets != 2) {
return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice);
} }

View File

@@ -148,9 +148,6 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
uint256 n = tokens_.length; uint256 n = tokens_.length;
// Initialize LMSR state nAssets; full init occurs on first mint when quantities are known.
_lmsr.nAssets = n;
// Initialize token address to index mapping // Initialize token address to index mapping
for (uint i = 0; i < n;) { for (uint i = 0; i < n;) {
_tokenAddressToIndexPlusOne[tokens_[i]] = i + 1; _tokenAddressToIndexPlusOne[tokens_[i]] = i + 1;
@@ -444,13 +441,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
} }
/** /// @inheritdoc IPartyPool
* @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the callback.
* @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
* @param tokenAddr The loan currency.
* @param amount The amount of tokens lent.
* @param data A data parameter to be passed on to the `receiver` for any custom use.
*/
function flashLoan( function flashLoan(
IERC3156FlashBorrower receiver, IERC3156FlashBorrower receiver,
address tokenAddr, address tokenAddr,

View File

@@ -30,7 +30,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
uint256 n = _tokens.length; uint256 n = _tokens.length;
// Check if this is initial deposit - revert if not // Check if this is initial deposit - revert if not
bool isInitialDeposit = _totalSupply == 0 || _lmsr.nAssets == 0; bool isInitialDeposit = _totalSupply == 0 || _lmsr.qInternal.length == 0;
require(isInitialDeposit, "initialMint: pool already initialized"); require(isInitialDeposit, "initialMint: pool already initialized");
// Read initial on-chain balances, require all > 0, and compute denominators (bases) from deposits. // Read initial on-chain balances, require all > 0, and compute denominators (bases) from deposits.
@@ -79,7 +79,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
uint256 n = _tokens.length; uint256 n = _tokens.length;
// Check if this is NOT initial deposit - revert if it is // Check if this is NOT initial deposit - revert if it is
bool isInitialDeposit = _totalSupply == 0 || _lmsr.nAssets == 0; bool isInitialDeposit = _totalSupply == 0 || _lmsr.qInternal.length == 0;
require(!isInitialDeposit, "mint: use initialMint for pool initialization"); require(!isInitialDeposit, "mint: use initialMint for pool initialization");
require(lpTokenAmount > 0, "mint: zero LP amount"); require(lpTokenAmount > 0, "mint: zero LP amount");
@@ -88,7 +88,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE); uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
// Calculate required deposit amounts for the desired LP _tokens // Calculate required deposit amounts for the desired LP _tokens
uint256[] memory depositAmounts = mintAmounts(lpTokenAmount, _lmsr.nAssets, _totalSupply, _cachedUintBalances); uint256[] memory depositAmounts = mintAmounts(lpTokenAmount, _totalSupply, _cachedUintBalances);
// Transfer in all token amounts // Transfer in all token amounts
for (uint i = 0; i < n; ) { for (uint i = 0; i < n; ) {
@@ -160,7 +160,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Use cached balances; assume standard ERC20 transfers without external interference // Use cached balances; assume standard ERC20 transfers without external interference
// Compute proportional withdrawal amounts for the requested LP amount (rounded down) // Compute proportional withdrawal amounts for the requested LP amount (rounded down)
withdrawAmounts = burnAmounts(lpAmount, _lmsr.nAssets, _totalSupply, _cachedUintBalances); withdrawAmounts = burnAmounts(lpAmount, _totalSupply, _cachedUintBalances);
// Transfer underlying _tokens out to receiver according to computed proportions // Transfer underlying _tokens out to receiver according to computed proportions
for (uint i = 0; i < n; ) { for (uint i = 0; i < n; ) {
@@ -213,8 +213,9 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @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 mintAmounts(uint256 lpTokenAmount, function mintAmounts(uint256 lpTokenAmount,
uint256 numAssets, uint256 totalSupply, uint256[] memory cachedUintBalances) public pure uint256 totalSupply, uint256[] memory cachedUintBalances) public pure
returns (uint256[] memory depositAmounts) { returns (uint256[] memory depositAmounts) {
uint256 numAssets = cachedUintBalances.length;
depositAmounts = new uint256[](numAssets); depositAmounts = new uint256[](numAssets);
// If this is the first mint or pool is empty, return zeros // If this is the first mint or pool is empty, return zeros
@@ -236,8 +237,9 @@ contract PartyPoolMintImpl is PartyPoolBase {
} }
function burnAmounts(uint256 lpTokenAmount, function burnAmounts(uint256 lpTokenAmount,
uint256 numAssets, uint256 totalSupply, uint256[] memory cachedUintBalances) public pure uint256 totalSupply, uint256[] memory cachedUintBalances) public pure
returns (uint256[] memory withdrawAmounts) { returns (uint256[] memory withdrawAmounts) {
uint256 numAssets = cachedUintBalances.length;
withdrawAmounts = new uint256[](numAssets); withdrawAmounts = new uint256[](numAssets);
// If supply is zero or pool uninitialized, return zeros // If supply is zero or pool uninitialized, return zeros
@@ -280,7 +282,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
) public pure returns (uint256 amountInUsed, uint256 lpMinted, uint256 inFee) { ) public pure returns (uint256 amountInUsed, uint256 lpMinted, uint256 inFee) {
require(inputTokenIndex < bases_.length, "swapMintAmounts: idx"); require(inputTokenIndex < bases_.length, "swapMintAmounts: idx");
require(maxAmountIn > 0, "swapMintAmounts: input zero"); require(maxAmountIn > 0, "swapMintAmounts: input zero");
require(lmsrState.nAssets > 0, "swapMintAmounts: uninit pool"); require(lmsrState.qInternal.length > 0, "swapMintAmounts: uninit pool");
// Compute fee on gross maxAmountIn to get an initial net estimate // Compute fee on gross maxAmountIn to get an initial net estimate
uint256 feeGuess = 0; uint256 feeGuess = 0;
@@ -296,7 +298,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
// 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) = (int128 amountInInternalUsed, int128 sizeIncreaseInternal) =
LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal, LMSRStabilized.swapAmountsForMint(lmsrState.kappa, lmsrState.qInternal,
inputTokenIndex, netInternalGuess); 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
@@ -359,7 +361,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
require(inputTokenIndex < 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");
require(_lmsr.nAssets > 0, "swapMint: uninit pool"); require(_lmsr.qInternal.length > 0, "swapMint: uninit pool");
// compute fee on gross maxAmountIn to get an initial net estimate (we'll recompute based on actual used) // compute fee on gross maxAmountIn to get an initial net estimate (we'll recompute based on actual used)
(, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm); (, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm);
@@ -467,7 +469,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee .mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
// 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, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal, (int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.kappa, lmsrState.qInternal,
outputTokenIndex, alpha); outputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool // Convert payoutInternal -> uint (floor) to favor pool
@@ -476,7 +478,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Compute gross payout (no swap fee) to derive token-side fee = gross - net // Compute gross payout (no swap fee) to derive token-side fee = gross - net
int128 alphaGross = ABDKMath64x64.divu(lpAmount, totalSupply_); // gross fraction (no swap fee) int128 alphaGross = ABDKMath64x64.divu(lpAmount, totalSupply_); // gross fraction (no swap fee)
(int128 payoutGrossInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal, (int128 payoutGrossInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.kappa, lmsrState.qInternal,
outputTokenIndex, alphaGross); outputTokenIndex, alphaGross);
uint256 payoutGrossUint = _internalToUintFloorPure(payoutGrossInternal, bases_[outputTokenIndex]); uint256 payoutGrossUint = _internalToUintFloorPure(payoutGrossInternal, bases_[outputTokenIndex]);
outFee = (payoutGrossUint > amountOut) ? (payoutGrossUint - amountOut) : 0; outFee = (payoutGrossUint > amountOut) ? (payoutGrossUint - amountOut) : 0;

View File

@@ -73,7 +73,7 @@ contract PartyPoolSwapImpl is PartyPoolBase {
) external pure returns (uint256 amountIn, uint256 amountOut, uint256 inFee) { ) external pure returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
// Compute internal maxima at the price limit // Compute internal maxima at the price limit
(int128 amountInInternal, int128 amountOutInternal) = LMSRStabilized.swapAmountsForPriceLimit( (int128 amountInInternal, int128 amountOutInternal) = LMSRStabilized.swapAmountsForPriceLimit(
bases.length, kappa, qInternal, kappa, qInternal,
inputTokenIndex, outputTokenIndex, limitPrice); inputTokenIndex, outputTokenIndex, limitPrice);
// Convert input to uint (ceil) and output to uint (floor) // Convert input to uint (ceil) and output to uint (floor)

View File

@@ -33,9 +33,10 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
/// @return price Q64.64 value equal to quote per base (p_quote / p_base) /// @return price Q64.64 value equal to quote per base (p_quote / p_base)
function price(IPartyPool pool, uint256 baseTokenIndex, uint256 quoteTokenIndex) external view returns (int128) { function price(IPartyPool pool, uint256 baseTokenIndex, uint256 quoteTokenIndex) external view returns (int128) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
require(baseTokenIndex < lmsr.nAssets && quoteTokenIndex < lmsr.nAssets, "price: idx"); uint256 nAssets = lmsr.qInternal.length;
require(lmsr.nAssets > 0, "price: uninit"); require(nAssets > 0, "price: uninit");
return LMSRStabilized.price(lmsr.nAssets, pool.kappa(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex); require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "price: idx");
return LMSRStabilized.price(pool.kappa(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex);
} }
/// @notice Price of one LP token denominated in `quote` as Q64.64. /// @notice Price of one LP token denominated in `quote` as Q64.64.
@@ -46,11 +47,12 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
/// @return price Q64.64 value equal to quote per LP token unit /// @return price Q64.64 value equal to quote per LP token unit
function poolPrice(IPartyPool pool, uint256 quoteTokenIndex) external view returns (int128) { function poolPrice(IPartyPool pool, uint256 quoteTokenIndex) external view returns (int128) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
require(lmsr.nAssets > 0, "poolPrice: uninit"); uint256 nAssets = lmsr.qInternal.length;
require(quoteTokenIndex < lmsr.nAssets, "poolPrice: idx"); require(nAssets > 0, "poolPrice: uninit");
require(quoteTokenIndex < nAssets, "poolPrice: idx");
// price per unit of qTotal (Q64.64) from LMSR // price per unit of qTotal (Q64.64) from LMSR
int128 pricePerQ = LMSRStabilized.poolPrice(lmsr.nAssets, pool.kappa(), lmsr.qInternal, quoteTokenIndex); int128 pricePerQ = LMSRStabilized.poolPrice( pool.kappa(), lmsr.qInternal, quoteTokenIndex);
// total internal q (qTotal) as Q64.64 // total internal q (qTotal) as Q64.64
int128 qTotal = LMSRStabilized._computeSizeMetric(lmsr.qInternal); int128 qTotal = LMSRStabilized._computeSizeMetric(lmsr.qInternal);
@@ -71,19 +73,21 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
function mintAmounts(IPartyPool pool, uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) { function mintAmounts(IPartyPool pool, uint256 lpTokenAmount) public view returns (uint256[] memory depositAmounts) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
uint256[] memory cachedUintBalances = new uint256[](lmsr.nAssets); uint256 nAssets = lmsr.qInternal.length;
for( uint256 i=0; i<lmsr.nAssets; i++ ) uint256[] memory cachedUintBalances = new uint256[](nAssets);
for( uint256 i=0; i<nAssets; i++ )
cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool)); cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool));
return MINT_IMPL.mintAmounts(lpTokenAmount, lmsr.nAssets, pool.totalSupply(), cachedUintBalances); return MINT_IMPL.mintAmounts(lpTokenAmount, pool.totalSupply(), cachedUintBalances);
} }
function burnAmounts(IPartyPool pool, uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) { function burnAmounts(IPartyPool pool, uint256 lpTokenAmount) external view returns (uint256[] memory withdrawAmounts) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
uint256[] memory cachedUintBalances = new uint256[](lmsr.nAssets); uint256 nAssets = lmsr.qInternal.length;
for( uint256 i=0; i<lmsr.nAssets; i++ ) uint256[] memory cachedUintBalances = new uint256[](nAssets);
for( uint256 i=0; i<nAssets; i++ )
cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool)); cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool));
return MINT_IMPL.burnAmounts(lpTokenAmount, lmsr.nAssets, pool.totalSupply(), cachedUintBalances); return MINT_IMPL.burnAmounts(lpTokenAmount, pool.totalSupply(), cachedUintBalances);
} }
@@ -99,9 +103,10 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
int128 limitPrice int128 limitPrice
) external view returns (uint256 amountIn, uint256 amountOut, uint256 inFee) { ) external view returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
require(inputTokenIndex < lmsr.nAssets && outputTokenIndex < lmsr.nAssets, "swapToLimit: idx"); uint256 nAssets = lmsr.qInternal.length;
require(inputTokenIndex < nAssets && outputTokenIndex < nAssets, "swapToLimit: idx");
require(limitPrice > int128(0), "swapToLimit: limit <= 0"); require(limitPrice > int128(0), "swapToLimit: limit <= 0");
require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized"); require(nAssets > 0, "swapToLimit: pool uninitialized");
return SWAP_IMPL.swapToLimitAmounts( return SWAP_IMPL.swapToLimitAmounts(
inputTokenIndex, outputTokenIndex, limitPrice, inputTokenIndex, outputTokenIndex, limitPrice,
@@ -143,8 +148,9 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
function flashRepaymentAmounts(IPartyPool pool, uint256[] memory loanAmounts) external view function flashRepaymentAmounts(IPartyPool pool, uint256[] memory loanAmounts) external view
returns (uint256[] memory repaymentAmounts) { returns (uint256[] memory repaymentAmounts) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
repaymentAmounts = new uint256[](lmsr.nAssets); uint256 nAssets = lmsr.qInternal.length;
for (uint256 i = 0; i < lmsr.nAssets; i++) { repaymentAmounts = new uint256[](nAssets);
for (uint256 i = 0; i < nAssets; i++) {
uint256 amount = loanAmounts[i]; uint256 amount = loanAmounts[i];
if (amount > 0) { if (amount > 0) {
repaymentAmounts[i] = amount + _ceilFee(amount, pool.flashFeePpm()); repaymentAmounts[i] = amount + _ceilFee(amount, pool.flashFeePpm());

View File

@@ -192,7 +192,7 @@ contract LMSRStabilizedTest is Test {
initAlmostBalanced(); initAlmostBalanced();
// Verify basic state is still functional // Verify basic state is still functional
assertTrue(s.nAssets > 0, "State should still be initialized"); assertTrue(s.qInternal.length > 0, "State should still be initialized");
assertTrue(s.kappa > int128(0), "Kappa should still be positive"); assertTrue(s.kappa > int128(0), "Kappa should still be positive");
} }
@@ -213,9 +213,10 @@ contract LMSRStabilizedTest is Test {
int128 initialB = _computeB(initialQ); int128 initialB = _computeB(initialQ);
int128 initialKappa = s.kappa; int128 initialKappa = s.kappa;
uint256 nAssets = s.qInternal.length;
// Simulate a deposit by increasing all asset quantities by 50% // Simulate a deposit by increasing all asset quantities by 50%
int128[] memory newQ = new int128[](s.nAssets); int128[] memory newQ = new int128[](nAssets);
for (uint i = 0; i < s.nAssets; i++) { for (uint i = 0; i < nAssets; i++) {
// Increase by 50% // Increase by 50%
newQ[i] = initialQ[i].mul(ABDKMath64x64.fromUInt(3).div(ABDKMath64x64.fromUInt(2))); // 1.5x newQ[i] = initialQ[i].mul(ABDKMath64x64.fromUInt(3).div(ABDKMath64x64.fromUInt(2))); // 1.5x
} }
@@ -362,8 +363,9 @@ contract LMSRStabilizedTest is Test {
int128 initialKappa = s.kappa; int128 initialKappa = s.kappa;
// Simulate a withdrawal by decreasing all asset quantities by 30% // Simulate a withdrawal by decreasing all asset quantities by 30%
int128[] memory newQ = new int128[](s.nAssets); uint256 nAssets = s.qInternal.length;
for (uint i = 0; i < s.nAssets; i++) { int128[] memory newQ = new int128[](nAssets);
for (uint i = 0; i < nAssets; i++) {
// Decrease by 30% // Decrease by 30%
newQ[i] = initialQ[i].mul(ABDKMath64x64.fromUInt(7).div(ABDKMath64x64.fromUInt(10))); // 0.7x newQ[i] = initialQ[i].mul(ABDKMath64x64.fromUInt(7).div(ABDKMath64x64.fromUInt(10))); // 0.7x
} }
@@ -408,7 +410,7 @@ contract LMSRStabilizedTest is Test {
function testRecenterShiftTooLargeReverts() public { function testRecenterShiftTooLargeReverts() public {
initAlmostBalanced(); initAlmostBalanced();
// Recentering has been removed, so this test now just verifies basic functionality // Recentering has been removed, so this test now just verifies basic functionality
assertTrue(s.nAssets > 0, "State should still be initialized"); assertTrue(s.qInternal.length > 0, "State should still be initialized");
} }
/// @notice limitPrice <= current price should revert (no partial fill) /// @notice limitPrice <= current price should revert (no partial fill)
@@ -431,25 +433,6 @@ contract LMSRStabilizedTest is Test {
this.externalSwapAmountsForExactInput(0, 1, tradeAmount, ABDKMath64x64.fromInt(1)); this.externalSwapAmountsForExactInput(0, 1, tradeAmount, ABDKMath64x64.fromInt(1));
} }
/// @notice If e_j == 0 we should revert early to avoid div-by-zero
function testEJZeroReverts() public {
initBalanced();
// Create mock qInternal where asset 1 has zero quantity
int128[] memory mockQInternal = new int128[](3);
mockQInternal[0] = ABDKMath64x64.fromUInt(1_000_000);
mockQInternal[1] = int128(0); // Zero quantity for asset 1
mockQInternal[2] = ABDKMath64x64.fromUInt(1_000_000);
// Update the state's cached qInternal
_updateCachedQInternal(mockQInternal);
int128 tradeAmount = mockQInternal[0].mul(stdTradeSize);
vm.expectRevert(bytes("LMSR: e_j==0"));
this.externalSwapAmountsForExactInput(0, 1, tradeAmount, 0);
}
/// @notice swapAmountsForPriceLimit returns zero if limit equals current price /// @notice swapAmountsForPriceLimit returns zero if limit equals current price
function testSwapAmountsForPriceLimitZeroWhenLimitEqualsPrice() public { function testSwapAmountsForPriceLimitZeroWhenLimitEqualsPrice() public {
initBalanced(); initBalanced();
@@ -693,7 +676,8 @@ contract LMSRStabilizedTest is Test {
initBalanced(); initBalanced();
// Create initial quantities // Create initial quantities
int128[] memory initialQValues = new int128[](s.nAssets); uint256 nAssets = s.qInternal.length;
int128[] memory initialQValues = new int128[](nAssets);
initialQValues[0] = ABDKMath64x64.fromUInt(1_000_000); initialQValues[0] = ABDKMath64x64.fromUInt(1_000_000);
initialQValues[1] = ABDKMath64x64.fromUInt(1_000_000); initialQValues[1] = ABDKMath64x64.fromUInt(1_000_000);
initialQValues[2] = ABDKMath64x64.fromUInt(1_000_000); initialQValues[2] = ABDKMath64x64.fromUInt(1_000_000);
@@ -705,8 +689,8 @@ contract LMSRStabilizedTest is Test {
int128 directSwapAmount = initialQValues[0].mul(stdTradeSize); int128 directSwapAmount = initialQValues[0].mul(stdTradeSize);
// Store a backup of the original values to restore between swaps // Store a backup of the original values to restore between swaps
int128[] memory backupQ = new int128[](s.nAssets); int128[] memory backupQ = new int128[](nAssets);
for (uint i = 0; i < s.nAssets; i++) { for (uint i = 0; i < nAssets; i++) {
backupQ[i] = s.qInternal[i]; backupQ[i] = s.qInternal[i];
} }
@@ -798,8 +782,9 @@ contract LMSRStabilizedTest is Test {
int128 tradeAmount1 = s.qInternal[1].mul(tradeSize); int128 tradeAmount1 = s.qInternal[1].mul(tradeSize);
// Store original state to restore between tests // Store original state to restore between tests
int128[] memory backupQ = new int128[](s.nAssets); uint256 nAssets = s.qInternal.length;
for (uint i = 0; i < s.nAssets; i++) { int128[] memory backupQ = new int128[](nAssets);
for (uint i = 0; i < nAssets; i++) {
backupQ[i] = s.qInternal[i]; backupQ[i] = s.qInternal[i];
} }

View File

@@ -426,7 +426,7 @@ contract NativeTest is Test {
uint256 aliceLpBefore = pool.balanceOf(alice); uint256 aliceLpBefore = pool.balanceOf(alice);
// Call swapMint with native currency: deposit ETH as WETH (index 2) // Call swapMint with native currency: deposit ETH as WETH (index 2)
(, uint256 lpMinted, ) = pool.swapMint{value: maxIn}( (, uint256 lpMinted,) = pool.swapMint{value: maxIn}(
alice, // payer alice, // payer
alice, // receiver alice, // receiver
2, // inputTokenIndex (WETH) 2, // inputTokenIndex (WETH)
@@ -457,7 +457,7 @@ contract NativeTest is Test {
uint256 aliceEthBefore = alice.balance; uint256 aliceEthBefore = alice.balance;
// Send excess native currency // Send excess native currency
(, uint256 lpMinted, ) = pool.swapMint{value: totalSent}( (, uint256 lpMinted,) = pool.swapMint{value: totalSent}(
alice, alice,
alice, alice,
2, // WETH 2, // WETH