From dd9b7474a64e10ce7bfee3b27b5519b5bab135fb Mon Sep 17 00:00:00 2001 From: tim Date: Wed, 29 Oct 2025 19:35:41 -0400 Subject: [PATCH] removed state.nAssets --- src/IPartyPool.sol | 12 ++++---- src/LMSRStabilized.sol | 48 +++++++++--------------------- src/LMSRStabilizedBalancedPair.sol | 5 ++-- src/PartyPool.sol | 11 +------ src/PartyPoolMintImpl.sol | 24 ++++++++------- src/PartyPoolSwapImpl.sol | 2 +- src/PartyPoolViewer.sol | 38 +++++++++++++---------- test/LMSRStabilized.t.sol | 45 ++++++++++------------------ test/NativeTest.t.sol | 4 +-- 9 files changed, 76 insertions(+), 113 deletions(-) diff --git a/src/IPartyPool.sol b/src/IPartyPool.sol index d6dd3f1..f3ec118 100644 --- a/src/IPartyPool.sol +++ b/src/IPartyPool.sol @@ -250,13 +250,11 @@ interface IPartyPool is IERC20Metadata, IOwnable { bool unwrap ) external returns (uint256 amountOutUint); - /** - * @dev Initiate a flash loan. - * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ + /// @dev Initiate a flash loan. + /// @param receiver The receiver of the tokens in the loan, and the receiver of the callback. + /// @param token The loan currency. + /// @param amount The amount of tokens lent. + /// @param data Arbitrary data structure, intended to contain user-defined parameters. function flashLoan( IERC3156FlashBorrower receiver, address token, diff --git a/src/LMSRStabilized.sol b/src/LMSRStabilized.sol index 6baf9b9..bbe24db 100644 --- a/src/LMSRStabilized.sol +++ b/src/LMSRStabilized.sol @@ -12,7 +12,6 @@ library LMSRStabilized { using ABDKMath64x64 for int128; struct State { - uint256 nAssets; int128 kappa; // liquidity parameter κ (64.64 fixed point) int128[] qInternal; // cached internal balances in 64.64 fixed-point format } @@ -28,8 +27,6 @@ library LMSRStabilized { int128[] memory initialQInternal, int128 kappa ) internal { - s.nAssets = initialQInternal.length; - // Initialize qInternal cache if (s.qInternal.length != initialQInternal.length) { s.qInternal = new int128[](initialQInternal.length); @@ -98,7 +95,7 @@ library LMSRStabilized { int128 a, int128 limitPrice ) 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) @@ -113,7 +110,6 @@ library LMSRStabilized { /// /// 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 qInternal Cached internal balances in 64.64 fixed-point format /// @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 amountOut Amount of output asset j in 64.64 fixed-point format function swapAmountsForExactInput( - uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 i, @@ -217,7 +212,7 @@ library LMSRStabilized { uint256 j, int128 limitPrice ) 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 @@ -226,7 +221,6 @@ library LMSRStabilized { /// 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. /// - /// @param nAssets Number of assets in the pool /// @param kappa Liquidity parameter κ (64.64 fixed point) /// @param qInternal Cached internal balances in 64.64 fixed-point format /// @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 amountOut Corresponding maximum output amount in 64.64 fixed-point format function swapAmountsForPriceLimit( - uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 i, @@ -318,7 +311,7 @@ library LMSRStabilized { uint256 i, int128 a ) 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. @@ -327,7 +320,6 @@ library LMSRStabilized { /// 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). /// 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 qInternal Cached internal balances in 64.64 fixed-point format /// @param i Index of input asset @@ -335,13 +327,13 @@ library LMSRStabilized { /// @return amountIn Actual amount of input consumed /// @return amountOut LP size-metric increase (alpha * S) function swapAmountsForMint( - uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 i, int128 a ) 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"); int128 sizeMetric = _computeSizeMetric(qInternal); @@ -351,8 +343,6 @@ library LMSRStabilized { int128 invB = ABDKMath64x64.div(ONE, b); int128 S = sizeMetric; - uint256 n = nAssets; - // Precompute r0_j = exp((q_i - q_j) / b) for all j to avoid recomputing during search. int128[] memory r0 = new int128[](n); for (uint256 j = 0; j < n; ) { @@ -530,7 +520,7 @@ library LMSRStabilized { uint256 i, int128 alpha ) 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. @@ -542,7 +532,6 @@ library LMSRStabilized { /// - 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). /// 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 qInternal Cached internal balances in 64.64 fixed-point format /// @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 amountIn LP size-metric redeemed (alpha * S) function swapAmountsForBurn( - uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 i, int128 alpha ) internal pure returns (int128 amountOut, int128 amountIn) { - require(i < nAssets, "LMSR: idx"); require(alpha > int128(0) && alpha <= ONE, "LMSR: alpha"); int128 sizeMetric = _computeSizeMetric(qInternal); @@ -565,7 +552,7 @@ library LMSRStabilized { require(b > int128(0), "LMSR: b<=0"); int128 invB = ABDKMath64x64.div(ONE, b); - uint256 n = nAssets; + uint256 n = qInternal.length; // Size metric and burned size (amountIn returned) int128 S = sizeMetric; @@ -675,7 +662,6 @@ library LMSRStabilized { int128 amountIn, int128 amountOut ) internal { - require(i < s.nAssets && j < s.nAssets, "LMSR: idx"); require(amountIn > int128(0), "LMSR: amountIn <= 0"); require(amountOut > int128(0), "LMSR: amountOut <= 0"); @@ -690,15 +676,14 @@ library LMSRStabilized { /// Updates the internal qInternal cache with the new balances /// @param newQInternal New asset quantities after mint/redeem (64.64 format) function updateForProportionalChange(State storage s, int128[] memory newQInternal) internal { - require(newQInternal.length == s.nAssets, "LMSR: length mismatch"); - // Compute new total for validation int128 newTotal = _computeSizeMetric(newQInternal); require(newTotal > int128(0), "LMSR: new total zero"); // 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]; unchecked { i++; } } @@ -775,19 +760,17 @@ library LMSRStabilized { /// @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. 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 /// @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 qInternal Cached internal balances in 64.64 fixed-point format /// @param baseTokenIndex Index of base token /// @param quoteTokenIndex Index of quote token /// @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) { - require(baseTokenIndex < nAssets && quoteTokenIndex < nAssets, "LMSR: idx"); + function price(int128 kappa, int128[] memory qInternal, uint256 baseTokenIndex, uint256 quoteTokenIndex) internal pure returns (int128) { int128 sizeMetric = _computeSizeMetric(qInternal); require(sizeMetric > int128(0), "LMSR: size metric zero"); 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) /// @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) { - 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) /// @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 qInternal Cached internal balances in 64.64 fixed-point format /// @param quoteTokenIndex Index of quote token /// @return Pool price in 64.64 fixed-point format - function poolPrice(uint256 nAssets, int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) { - require(quoteTokenIndex < nAssets, "LMSR: idx"); + function poolPrice(int128 kappa, int128[] memory qInternal, uint256 quoteTokenIndex) internal pure returns (int128) { // Compute b and ensure positivity int128 sizeMetric = _computeSizeMetric(qInternal); 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) int128 acc = int128(0); - uint256 n = nAssets; + uint256 n = qInternal.length; for (uint256 j = 0; j < n; ) { // factor = exp((q_j - q_quote) / b) 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. function deinit(State storage s) internal { // Reset core state - s.nAssets = 0; s.kappa = int128(0); // Clear qInternal array diff --git a/src/LMSRStabilizedBalancedPair.sol b/src/LMSRStabilizedBalancedPair.sol index 5bd7c03..59e4beb 100644 --- a/src/LMSRStabilizedBalancedPair.sol +++ b/src/LMSRStabilizedBalancedPair.sol @@ -35,10 +35,11 @@ library LMSRStabilizedBalancedPair { int128 limitPrice ) internal view returns (int128 amountIn, int128 amountOut) { // 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 (s.nAssets != 2) { + if (nAssets != 2) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } diff --git a/src/PartyPool.sol b/src/PartyPool.sol index 51f0100..dcf8aa0 100644 --- a/src/PartyPool.sol +++ b/src/PartyPool.sol @@ -148,9 +148,6 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool 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 for (uint i = 0; i < n;) { _tokenAddressToIndexPlusOne[tokens_[i]] = i + 1; @@ -444,13 +441,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, 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. - */ + /// @inheritdoc IPartyPool function flashLoan( IERC3156FlashBorrower receiver, address tokenAddr, diff --git a/src/PartyPoolMintImpl.sol b/src/PartyPoolMintImpl.sol index 953cba6..701d768 100644 --- a/src/PartyPoolMintImpl.sol +++ b/src/PartyPoolMintImpl.sol @@ -30,7 +30,7 @@ contract PartyPoolMintImpl is PartyPoolBase { uint256 n = _tokens.length; // 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"); // 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; // 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(lpTokenAmount > 0, "mint: zero LP amount"); @@ -88,7 +88,7 @@ contract PartyPoolMintImpl is PartyPoolBase { uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE); // 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 for (uint i = 0; i < n; ) { @@ -160,7 +160,7 @@ contract PartyPoolMintImpl is PartyPoolBase { // Use cached balances; assume standard ERC20 transfers without external interference // 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 for (uint i = 0; i < n; ) { @@ -213,8 +213,9 @@ contract PartyPoolMintImpl is PartyPoolBase { /// @param lpTokenAmount The amount of LP _tokens desired /// @return depositAmounts Array of token amounts to deposit (rounded up) function mintAmounts(uint256 lpTokenAmount, - uint256 numAssets, uint256 totalSupply, uint256[] memory cachedUintBalances) public pure + uint256 totalSupply, uint256[] memory cachedUintBalances) public pure returns (uint256[] memory depositAmounts) { + uint256 numAssets = cachedUintBalances.length; depositAmounts = new uint256[](numAssets); // If this is the first mint or pool is empty, return zeros @@ -236,8 +237,9 @@ contract PartyPoolMintImpl is PartyPoolBase { } function burnAmounts(uint256 lpTokenAmount, - uint256 numAssets, uint256 totalSupply, uint256[] memory cachedUintBalances) public pure + uint256 totalSupply, uint256[] memory cachedUintBalances) public pure returns (uint256[] memory withdrawAmounts) { + uint256 numAssets = cachedUintBalances.length; withdrawAmounts = new uint256[](numAssets); // 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) { require(inputTokenIndex < bases_.length, "swapMintAmounts: idx"); 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 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 (int128 amountInInternalUsed, int128 sizeIncreaseInternal) = - LMSRStabilized.swapAmountsForMint(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal, + LMSRStabilized.swapAmountsForMint(lmsrState.kappa, lmsrState.qInternal, inputTokenIndex, netInternalGuess); // 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(maxAmountIn > 0, "swapMint: input zero"); 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) (, uint256 netUintGuess) = _computeFee(maxAmountIn, swapFeePpm); @@ -467,7 +469,7 @@ contract PartyPoolMintImpl is PartyPoolBase { .mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee // 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); // 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 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); uint256 payoutGrossUint = _internalToUintFloorPure(payoutGrossInternal, bases_[outputTokenIndex]); outFee = (payoutGrossUint > amountOut) ? (payoutGrossUint - amountOut) : 0; diff --git a/src/PartyPoolSwapImpl.sol b/src/PartyPoolSwapImpl.sol index c3f71f4..15e074a 100644 --- a/src/PartyPoolSwapImpl.sol +++ b/src/PartyPoolSwapImpl.sol @@ -73,7 +73,7 @@ contract PartyPoolSwapImpl is PartyPoolBase { ) external pure returns (uint256 amountIn, uint256 amountOut, uint256 inFee) { // Compute internal maxima at the price limit (int128 amountInInternal, int128 amountOutInternal) = LMSRStabilized.swapAmountsForPriceLimit( - bases.length, kappa, qInternal, + kappa, qInternal, inputTokenIndex, outputTokenIndex, limitPrice); // Convert input to uint (ceil) and output to uint (floor) diff --git a/src/PartyPoolViewer.sol b/src/PartyPoolViewer.sol index 0565eaa..0ea42d6 100644 --- a/src/PartyPoolViewer.sol +++ b/src/PartyPoolViewer.sol @@ -33,9 +33,10 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer { /// @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) { LMSRStabilized.State memory lmsr = pool.LMSR(); - require(baseTokenIndex < lmsr.nAssets && quoteTokenIndex < lmsr.nAssets, "price: idx"); - require(lmsr.nAssets > 0, "price: uninit"); - return LMSRStabilized.price(lmsr.nAssets, pool.kappa(), lmsr.qInternal, baseTokenIndex, quoteTokenIndex); + uint256 nAssets = lmsr.qInternal.length; + require(nAssets > 0, "price: uninit"); + 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. @@ -46,11 +47,12 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer { /// @return price Q64.64 value equal to quote per LP token unit function poolPrice(IPartyPool pool, uint256 quoteTokenIndex) external view returns (int128) { LMSRStabilized.State memory lmsr = pool.LMSR(); - require(lmsr.nAssets > 0, "poolPrice: uninit"); - require(quoteTokenIndex < lmsr.nAssets, "poolPrice: idx"); + uint256 nAssets = lmsr.qInternal.length; + require(nAssets > 0, "poolPrice: uninit"); + require(quoteTokenIndex < nAssets, "poolPrice: idx"); // 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 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) { LMSRStabilized.State memory lmsr = pool.LMSR(); - uint256[] memory cachedUintBalances = new uint256[](lmsr.nAssets); - for( uint256 i=0; i int128(0), "swapToLimit: limit <= 0"); - require(lmsr.nAssets > 0, "swapToLimit: pool uninitialized"); + require(nAssets > 0, "swapToLimit: pool uninitialized"); return SWAP_IMPL.swapToLimitAmounts( inputTokenIndex, outputTokenIndex, limitPrice, @@ -143,8 +148,9 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer { function flashRepaymentAmounts(IPartyPool pool, uint256[] memory loanAmounts) external view returns (uint256[] memory repaymentAmounts) { LMSRStabilized.State memory lmsr = pool.LMSR(); - repaymentAmounts = new uint256[](lmsr.nAssets); - for (uint256 i = 0; i < lmsr.nAssets; i++) { + uint256 nAssets = lmsr.qInternal.length; + repaymentAmounts = new uint256[](nAssets); + for (uint256 i = 0; i < nAssets; i++) { uint256 amount = loanAmounts[i]; if (amount > 0) { repaymentAmounts[i] = amount + _ceilFee(amount, pool.flashFeePpm()); diff --git a/test/LMSRStabilized.t.sol b/test/LMSRStabilized.t.sol index 5aec7e1..cc30ede 100644 --- a/test/LMSRStabilized.t.sol +++ b/test/LMSRStabilized.t.sol @@ -192,7 +192,7 @@ contract LMSRStabilizedTest is Test { initAlmostBalanced(); // 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"); } @@ -213,9 +213,10 @@ contract LMSRStabilizedTest is Test { int128 initialB = _computeB(initialQ); int128 initialKappa = s.kappa; + uint256 nAssets = s.qInternal.length; // Simulate a deposit by increasing all asset quantities by 50% - int128[] memory newQ = new int128[](s.nAssets); - for (uint i = 0; i < s.nAssets; i++) { + int128[] memory newQ = new int128[](nAssets); + for (uint i = 0; i < nAssets; i++) { // Increase by 50% 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; // Simulate a withdrawal by decreasing all asset quantities by 30% - int128[] memory newQ = new int128[](s.nAssets); - for (uint i = 0; i < s.nAssets; i++) { + uint256 nAssets = s.qInternal.length; + int128[] memory newQ = new int128[](nAssets); + for (uint i = 0; i < nAssets; i++) { // Decrease by 30% 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 { initAlmostBalanced(); // 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) @@ -431,25 +433,6 @@ contract LMSRStabilizedTest is Test { 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 function testSwapAmountsForPriceLimitZeroWhenLimitEqualsPrice() public { initBalanced(); @@ -693,7 +676,8 @@ contract LMSRStabilizedTest is Test { initBalanced(); // 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[1] = 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); // Store a backup of the original values to restore between swaps - int128[] memory backupQ = new int128[](s.nAssets); - 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]; } @@ -798,8 +782,9 @@ contract LMSRStabilizedTest is Test { int128 tradeAmount1 = s.qInternal[1].mul(tradeSize); // Store original state to restore between tests - int128[] memory backupQ = new int128[](s.nAssets); - for (uint i = 0; i < s.nAssets; i++) { + uint256 nAssets = s.qInternal.length; + int128[] memory backupQ = new int128[](nAssets); + for (uint i = 0; i < nAssets; i++) { backupQ[i] = s.qInternal[i]; } diff --git a/test/NativeTest.t.sol b/test/NativeTest.t.sol index bb815d1..c9e6110 100644 --- a/test/NativeTest.t.sol +++ b/test/NativeTest.t.sol @@ -426,7 +426,7 @@ contract NativeTest is Test { uint256 aliceLpBefore = pool.balanceOf(alice); // 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, // receiver 2, // inputTokenIndex (WETH) @@ -457,7 +457,7 @@ contract NativeTest is Test { uint256 aliceEthBefore = alice.balance; // Send excess native currency - (, uint256 lpMinted, ) = pool.swapMint{value: totalSent}( + (, uint256 lpMinted,) = pool.swapMint{value: totalSent}( alice, alice, 2, // WETH