// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.30; import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol"; import {LMSRStabilized} from "./LMSRStabilized.sol"; /// @notice Specialized functions for the 2-asset stablecoin case library LMSRStabilizedBalancedPair { using ABDKMath64x64 for int128; // Precomputed Q64.64 representation of 1.0 (1 << 64). int128 private constant ONE = 0x10000000000000000; /// @notice Specialized 2-asset balanced approximation of swapAmountsForExactInput. /// - Assumes exactly two assets and that the two assets' internal balances are within ~1% of parity. /// - Implements a gas-optimized two-tier Taylor approximation to avoid most exp()/ln() calls: /// * Tier 1 (quadratic, cheapest): for small u = a/b (u <= 0.1) we compute /// X = u*(1 + δ) - u^2/2 /// ln(1+X) ≈ X - X^2/2 /// and return amountOut ≈ b * lnApprox. This Horner-style form minimizes multiplies/divides /// and temporaries compared to the earlier a^2/a^3 expansion. /// * Tier 2 (cubic correction): for moderate u (0.1 < u <= 0.5) we add the X^3/3 term: /// ln(1+X) ≈ X - X^2/2 + X^3/3 /// which improves accuracy while still being significantly cheaper than full exp/ln. /// - For cases where |δ| (the per-asset imbalance scaled by b) or u are outside the safe ranges, /// or when limitPrice handling cannot be reliably approximated, the function falls back to the /// numerically-exact swapAmountsForExactInput(...) implementation to preserve correctness. /// - The goal is to keep relative error well below 0.001% in the intended small-u, near-parity regime, /// while substantially reducing gas in the common fast path. function swapAmountsForExactInput( LMSRStabilized.State storage s, uint256 i, uint256 j, int128 a, int128 limitPrice ) internal view returns (int128 amountIn, int128 amountOut) { // Quick index check require(i < s.nAssets && j < s.nAssets, "LMSR: idx"); // If not exactly a two-asset pool, fall back to the general routine. if (s.nAssets != 2) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // Compute b and inverse early (needed to evaluate delta and limit-price) int128 b = LMSRStabilized._computeB(s); // Guard: if b not positive, fallback to exact implementation (will revert there if necessary) if (!(b > int128(0))) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } int128 invB = ABDKMath64x64.div(ONE, b); // Small-signal delta = (q_i - q_j) / b (used to approximate r0 = exp(delta)) int128 delta = s.qInternal[i].sub(s.qInternal[j]).mul(invB); // If a positive limitPrice is given, attempt a 2-asset near-parity polynomial solution if (limitPrice > int128(0)) { // Approximate r0 = exp(delta) using Taylor: 1 + δ + δ^2/2 + δ^3/6 int128 delta_sq = delta.mul(delta); int128 delta_cu = delta_sq.mul(delta); int128 r0_approx = ONE .add(delta) .add(delta_sq.div(ABDKMath64x64.fromUInt(2))) .add(delta_cu.div(ABDKMath64x64.fromUInt(6))); // If limitPrice <= r0 (current price) we must revert (same semantic as original) if (limitPrice <= r0_approx) { revert("LMSR: limitPrice <= current price"); } // Ratio = limitPrice / r0_approx int128 ratio = limitPrice.div(r0_approx); // x = ratio - 1; use Taylor for ln(1+x) when |x| is small int128 x = ratio.sub(ONE); int128 absX = x >= int128(0) ? x : x.neg(); // Acceptable range for ln Taylor approx: |x| <= 0.1 (conservative) int128 X_MAX = ABDKMath64x64.divu(1, 10); // 0.1 if (absX > X_MAX) { // Too large to safely approximate; fall back to exact computation return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // ln(1+x) ≈ x - x^2/2 + x^3/3 int128 x_sq = x.mul(x); int128 x_cu = x_sq.mul(x); int128 lnRatioApprox = x .sub(x_sq.div(ABDKMath64x64.fromUInt(2))) .add(x_cu.div(ABDKMath64x64.fromUInt(3))); // aLimitOverB = ln(limitPrice / r0) approximated int128 aLimitOverB = lnRatioApprox; // Must be > 0; otherwise fall back if (!(aLimitOverB > int128(0))) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // aLimit = b * aLimitOverB (in Q64.64) int128 aLimit64 = b.mul(aLimitOverB); // If computed aLimit is less than requested a, use the truncated value. if (aLimit64 < a) { a = aLimit64; } else { // console2.log("balanced2: limitPrice does not truncate input"); } // Note: after potential truncation we continue with the polynomial approximation below } // Small-signal delta already computed above; reuse it int128 absDelta = delta >= int128(0) ? delta : delta.neg(); // Allow balanced pools only: require |delta| <= 1% (approx ln(1.01) ~ 0.00995; we use conservative 0.01) int128 DELTA_MAX = ABDKMath64x64.divu(1, 100); // 0.01 if (absDelta > DELTA_MAX) { // Not balanced within 1% -> use exact routine return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // Scaled input u = a / b (Q64.64). For polynomial approximation we require moderate u. int128 u = a.mul(invB); if (u <= int128(0)) { // Non-positive input -> behave like exact implementation (will revert if invalid) return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // Restrict to a conservative polynomial radius for accuracy; fallback otherwise. // We choose u <= 0.5 (0.5 in Q64.64) as safe for cubic approximation in typical parameters. int128 U_MAX = ABDKMath64x64.divu(1, 2); // 0.5 if (u > U_MAX) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // Now compute a two-tier approximation using Horner-style evaluation to reduce mul/divs. // Primary tier (cheap quadratic): accurate for small u = a/b. // Secondary tier (cubic correction): used when u is moderate but still within U_MAX. // Precomputed thresholds int128 U_TIER1 = ABDKMath64x64.divu(1, 10); // 0.1 -> cheap quadratic tier int128 U_MAX_LOCAL = ABDKMath64x64.divu(1, 2); // 0.5 -> still allowed cubic tier // u is already computed above // Compute X = u*(1 + delta) - u^2/2 int128 u2 = u.mul(u); int128 X = u.mul(ONE.add(delta)).sub(u2.div(ABDKMath64x64.fromUInt(2))); // Compute X^2 once int128 X2 = X.mul(X); int128 lnApprox; if (u <= U_TIER1) { // Cheap quadratic ln(1+X) ≈ X - X^2/2 lnApprox = X.sub(X2.div(ABDKMath64x64.fromUInt(2))); } else if (u <= U_MAX_LOCAL) { // Secondary cubic correction: ln(1+X) ≈ X - X^2/2 + X^3/3 int128 X3 = X2.mul(X); lnApprox = X.sub(X2.div(ABDKMath64x64.fromUInt(2))).add(X3.div(ABDKMath64x64.fromUInt(3))); } else { // u beyond allowed range - fallback return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } int128 approxOut = b.mul(lnApprox); // Safety sanity: approximation must be > 0 if (approxOut <= int128(0)) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // Cap to available j balance: if approximated output exceeds q_j, it's likely approximation break; // fall back to the exact solver to handle capping/edge cases. int128 qj64 = s.qInternal[j]; if (approxOut >= qj64) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } // Everything looks fine; return approximated amountOut and used amountIn (a) amountIn = a; amountOut = approxOut; // Final guard: ensure output is sensible and not NaN-like (rely on positivity checks above) if (amountOut < int128(0)) { return LMSRStabilized.swapAmountsForExactInput(s, i, j, a, limitPrice); } return (amountIn, amountOut); } }