pure kappa formulation: target slippage extracted into pool creator code
This commit is contained in:
@@ -27,8 +27,7 @@ library LMSRStabilized {
|
||||
function init(
|
||||
State storage s,
|
||||
int128[] memory initialQInternal,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage
|
||||
int128 kappa
|
||||
) internal {
|
||||
s.nAssets = initialQInternal.length;
|
||||
|
||||
@@ -50,8 +49,8 @@ library LMSRStabilized {
|
||||
console2.log("nAssets", s.nAssets);
|
||||
console2.log("qInternal.length", s.qInternal.length);
|
||||
|
||||
// Compute kappa from slippage parameters
|
||||
setKappaFromSlippage(s, tradeFrac, targetSlippage);
|
||||
// Set kappa directly (caller provides kappa)
|
||||
s.kappa = kappa;
|
||||
console2.log("kappa (64x64)");
|
||||
console2.logInt(s.kappa);
|
||||
require(s.kappa > int128(0), "LMSR: kappa>0");
|
||||
@@ -827,260 +826,81 @@ library LMSRStabilized {
|
||||
Slippage -> b computation & resize-triggered rescale
|
||||
-------------------- */
|
||||
|
||||
/// @notice Compute and set kappa from slippage parameters and current asset quantities
|
||||
/// This should be called during initialization or when recalibrating the pool parameters
|
||||
function setKappaFromSlippage(
|
||||
State storage s,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage
|
||||
) internal {
|
||||
require(s.nAssets > 0, "LMSR: no assets");
|
||||
require(s.qInternal.length == s.nAssets, "LMSR: length mismatch");
|
||||
|
||||
int128 total = _computeSizeMetric(s.qInternal);
|
||||
|
||||
// Detect degenerate "all balances equal" case: if every qInternal equals the first,
|
||||
// prefer the equal-inventories closed-form to avoid taking the heterogeneous path.
|
||||
bool allEqual = true;
|
||||
int128 first = s.qInternal[0];
|
||||
for (uint i = 1; i < s.qInternal.length; ) {
|
||||
if (s.qInternal[i] != first) {
|
||||
allEqual = false;
|
||||
break;
|
||||
}
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
int128 targetB;
|
||||
if (allEqual) {
|
||||
// All assets have identical internal balances -> use equal-case core explicitly.
|
||||
targetB = _computeBFromSlippageCore(total, s.nAssets, tradeFrac, targetSlippage, true);
|
||||
} else {
|
||||
// Compute target b using representative per-asset q for improved numerical stability
|
||||
targetB = _computeBFromSlippage(total, s.nAssets, tradeFrac, targetSlippage);
|
||||
}
|
||||
|
||||
// Numeric trace for debugging / verification
|
||||
console2.log("setKappaFromSlippage: trace start");
|
||||
console2.log("Q (total, Q64.64):");
|
||||
console2.logInt(total);
|
||||
console2.log("tradeFrac (f, Q64.64):");
|
||||
console2.logInt(tradeFrac);
|
||||
console2.log("targetSlippage (s, Q64.64):");
|
||||
console2.logInt(targetSlippage);
|
||||
console2.log("nAssets:");
|
||||
console2.logUint(s.nAssets);
|
||||
console2.log("total (S(q), Q64.64):");
|
||||
console2.logInt(total);
|
||||
console2.log("targetB (computed, Q64.64):");
|
||||
console2.logInt(targetB);
|
||||
console2.log("setKappaFromSlippage: trace end");
|
||||
|
||||
// Compute kappa = b_target / S(q)
|
||||
s.kappa = targetB.div(total);
|
||||
require(s.kappa > int128(0), "LMSR: kappa<=0");
|
||||
|
||||
console2.log("Set kappa from slippage params:");
|
||||
console2.log("total");
|
||||
console2.logInt(total);
|
||||
console2.log("targetB");
|
||||
console2.logInt(targetB);
|
||||
console2.log("kappa");
|
||||
console2.logInt(s.kappa);
|
||||
}
|
||||
|
||||
/// @notice Public wrapper for computing b from slippage parameters.
|
||||
/// Picks the degenerate closed-form when the heterogeneous invariants are not satisfied,
|
||||
/// otherwise uses the heterogeneous derivation implemented in computeBFromSlippageCore.
|
||||
function _computeBFromSlippage(
|
||||
int128 q, // total assets
|
||||
/// @notice Internal helper to compute kappa from slippage parameters.
|
||||
/// @dev Returns κ in Q64.64. Implemented as internal so callers within the library can use it
|
||||
/// without resorting to external calls.
|
||||
function _computeKappaFromSlippage(
|
||||
uint256 nAssets,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage
|
||||
) internal pure returns (int128) {
|
||||
// Quick sanity checks that decide whether the heterogeneous formula is applicable.
|
||||
// If not, fall back to the closed-form equal-asset formula for stability.
|
||||
int128 onePlusS = ONE.add(targetSlippage);
|
||||
|
||||
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
||||
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||
|
||||
// If 1 + s >= n then heterogeneous formula degenerates; use equal-asset closed-form.
|
||||
if (onePlusS >= n64) {
|
||||
return _computeBFromSlippageCore(q, nAssets, tradeFrac, targetSlippage, true);
|
||||
}
|
||||
|
||||
// denom = n - (1+s)
|
||||
int128 denom = n64.sub(onePlusS);
|
||||
int128 prod = onePlusS.mul(nMinus1_64);
|
||||
|
||||
// If prod <= 0 or denom >= prod then heterogeneous formula is not in its valid range.
|
||||
if (!(prod > int128(0) && denom < prod)) {
|
||||
return _computeBFromSlippageCore(q, nAssets, tradeFrac, targetSlippage, true);
|
||||
}
|
||||
|
||||
// Otherwise use the heterogeneous derivation.
|
||||
return _computeBFromSlippageCore(q, nAssets, tradeFrac, targetSlippage, false);
|
||||
}
|
||||
|
||||
/// @notice Core implementation that computes b from slippage parameters.
|
||||
/// If assumeEqual == true, uses the closed-form algebra for equal inventories.
|
||||
/// Otherwise uses the general derivation (original heterogeneous formula).
|
||||
function _computeBFromSlippageCore(
|
||||
int128 q, // total assets
|
||||
uint256 nAssets,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage,
|
||||
bool assumeEqual
|
||||
) internal pure returns (int128) {
|
||||
require(nAssets > 1, "LMSR: n>1 required");
|
||||
require(q > int128(0), "LMSR: q>0");
|
||||
|
||||
// f must be in (0,1)
|
||||
int128 f = tradeFrac;
|
||||
require(f > int128(0), "LMSR: f=0");
|
||||
require(f < ONE, "LMSR: f>=1");
|
||||
|
||||
// Top-level input debug
|
||||
console2.log("computeBFromSlippageCore: inputs");
|
||||
console2.log("q (64.64)");
|
||||
console2.logInt(q);
|
||||
console2.log("nAssets");
|
||||
console2.logUint(nAssets);
|
||||
console2.log("tradeFrac f (64.64)");
|
||||
console2.logInt(f);
|
||||
console2.log("targetSlippage S (64.64)");
|
||||
console2.logInt(targetSlippage);
|
||||
console2.log("assumeEqual");
|
||||
console2.logUint(assumeEqual ? 1 : 0);
|
||||
int128 onePlusS = ONE.add(targetSlippage);
|
||||
|
||||
if (assumeEqual) {
|
||||
// Closed-form equal-asset simplification for an n-asset pool:
|
||||
// Let s be the target relative increase in OTHER assets' price-share when
|
||||
// removing fraction f of a single asset. For equal inventories we derive:
|
||||
// E = exp(-y*f) = (1 - s*(n-1)) / (1 + s)
|
||||
// where y = q / b. Therefore:
|
||||
// y = -ln(E) / f
|
||||
// b = q / y = q * f / (-ln(E))
|
||||
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
||||
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||
|
||||
int128 nMinus1 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||
int128 numerator = ONE.sub(targetSlippage.mul(nMinus1)); // 1 - s*(n-1)
|
||||
int128 denominator = ONE.add(targetSlippage); // 1 + s
|
||||
// If 1 + s >= n then equal-inventories closed-form applies
|
||||
bool useEqual = (onePlusS >= n64);
|
||||
|
||||
console2.log("equal-case intermediates:");
|
||||
console2.log("numerator = 1 - s*(n-1)");
|
||||
console2.logInt(numerator);
|
||||
console2.log("denominator = 1 + s");
|
||||
console2.logInt(denominator);
|
||||
// E candidate used in deriving y = -ln(E)/f (same expression in both branches)
|
||||
int128 numerator = ONE.sub(targetSlippage.mul(nMinus1_64)); // 1 - s*(n-1)
|
||||
int128 denominator = onePlusS; // 1 + s
|
||||
|
||||
require(numerator > int128(0), "LMSR: s too large for n"); // ensures ratio>0
|
||||
|
||||
int128 ratio = numerator.div(denominator); // E candidate
|
||||
console2.log("E candidate (ratio = numerator/denominator)");
|
||||
console2.logInt(ratio);
|
||||
|
||||
// E must be strictly between 0 and 1 for a positive y
|
||||
require(ratio > int128(0) && ratio < ONE, "LMSR: bad E ratio");
|
||||
|
||||
int128 lnE = _ln(ratio); // ln(E) < 0
|
||||
console2.log("ln(E)");
|
||||
console2.logInt(lnE);
|
||||
|
||||
// y = -ln(E) / f
|
||||
int128 y = lnE.neg().div(f);
|
||||
console2.log("y = -ln(E)/f");
|
||||
console2.logInt(y);
|
||||
require(y > int128(0), "LMSR: y<=0");
|
||||
|
||||
int128 b = q.div(y);
|
||||
console2.log("b = q / y (computed)");
|
||||
console2.logInt(b);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
|
||||
// Simulate the slippage using this b to verify
|
||||
int128 expArg = y.mul(f).neg();
|
||||
int128 E_sim = _exp(expArg);
|
||||
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
||||
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||
int128 simulatedSlippage = n64.div(nMinus1_64.add(E_sim)).sub(ONE);
|
||||
console2.log("simulatedSlippage (using computed b)");
|
||||
console2.logInt(simulatedSlippage);
|
||||
|
||||
return b;
|
||||
if (useEqual) {
|
||||
// Guard numerator to ensure E in (0,1)
|
||||
require(numerator > int128(0), "LMSR: s too large for n");
|
||||
} else {
|
||||
// Heterogeneous / general case (original derivation):
|
||||
// E = exp(-y * f) where y = q / b
|
||||
// and E = (1+s) * (n-1) / (n - (1+s))
|
||||
// so y = -ln(E) / f and b = q / y.
|
||||
int128 onePlusS = ONE.add(targetSlippage);
|
||||
|
||||
console2.log("heterogeneous intermediates:");
|
||||
console2.log("onePlusS = 1 + s");
|
||||
console2.logInt(onePlusS);
|
||||
|
||||
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
||||
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||
|
||||
// denom = n - (1+s)
|
||||
int128 denom = n64.sub(onePlusS);
|
||||
console2.log("denom = n - (1+s)");
|
||||
console2.logInt(denom);
|
||||
|
||||
// Guard and clamp pathological cases similar to previous logic
|
||||
int128 eps = ABDKMath64x64.divu(1, 1_000_000_000); // small epsilon ~1e-9 in Q64.64
|
||||
if (onePlusS >= n64) {
|
||||
console2.log('clamping');
|
||||
onePlusS = n64.sub(eps);
|
||||
denom = n64.sub(onePlusS);
|
||||
}
|
||||
require(denom > int128(0), "LMSR: bad slippage or n");
|
||||
|
||||
int128 prod = onePlusS.mul(nMinus1_64);
|
||||
console2.log("prod = (1+s)*(n-1)");
|
||||
console2.logInt(prod);
|
||||
|
||||
if (!(prod > int128(0) && denom < prod)) {
|
||||
if (denom >= prod) {
|
||||
onePlusS = onePlusS.sub(eps);
|
||||
denom = n64.sub(onePlusS);
|
||||
prod = onePlusS.mul(nMinus1_64);
|
||||
}
|
||||
require(prod > int128(0) && denom < prod, "LMSR: slippage out of range");
|
||||
}
|
||||
|
||||
// Correct E candidate for the slippage relation:
|
||||
// E = (1 - s*(n-1)) / (1 + s)
|
||||
int128 E_candidate = (ONE.sub(targetSlippage.mul(nMinus1_64))).div(onePlusS);
|
||||
console2.log("E candidate ((1 - s*(n-1)) / (1+s))");
|
||||
console2.logInt(E_candidate);
|
||||
|
||||
// Compute ln(E) directly from the ratio E_candidate for improved numerical stability
|
||||
int128 lnE = _ln(E_candidate);
|
||||
console2.log("lnE = ln(E_candidate)");
|
||||
console2.logInt(lnE);
|
||||
|
||||
// y = -ln(E) / f
|
||||
int128 y = lnE.neg().div(f);
|
||||
console2.log("y = -ln(E)/f");
|
||||
console2.logInt(y);
|
||||
require(y > int128(0), "LMSR: y<=0");
|
||||
|
||||
// b = q / y
|
||||
int128 b = q.div(y);
|
||||
console2.log("b = q / y (computed)");
|
||||
console2.logInt(b);
|
||||
require(b > int128(0), "LMSR: b<=0");
|
||||
|
||||
// Simulate slippage using this b to verify
|
||||
int128 expArg = y.mul(f).neg();
|
||||
int128 E_sim = _exp(expArg);
|
||||
int128 simulatedSlippage = n64.div(nMinus1_64.add(E_sim)).sub(ONE);
|
||||
console2.log("simulatedSlippage (heterogeneous)");
|
||||
console2.logInt(simulatedSlippage);
|
||||
|
||||
return b;
|
||||
// In heterogeneous logic we also require the candidate to be in range; keep same guard
|
||||
require(numerator > int128(0), "LMSR: bad slippage or n");
|
||||
}
|
||||
|
||||
int128 E_candidate = numerator.div(denominator);
|
||||
require(E_candidate > int128(0) && E_candidate < ONE, "LMSR: bad E ratio");
|
||||
|
||||
// y = -ln(E) / f
|
||||
int128 lnE = _ln(E_candidate);
|
||||
int128 y = lnE.neg().div(f);
|
||||
require(y > int128(0), "LMSR: y<=0");
|
||||
|
||||
// kappa = 1 / y (since b = q / y -> kappa = b / q = 1 / y)
|
||||
int128 kappa = ONE.div(y);
|
||||
require(kappa > int128(0), "LMSR: kappa<=0");
|
||||
|
||||
return kappa;
|
||||
}
|
||||
|
||||
/// @notice Compute kappa from slippage parameters.
|
||||
/// @dev External wrapper that delegates to internal implementation.
|
||||
function computeKappaFromSlippage(
|
||||
uint256 nAssets,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage
|
||||
) external pure returns (int128) {
|
||||
return _computeKappaFromSlippage(nAssets, tradeFrac, targetSlippage);
|
||||
}
|
||||
|
||||
/// @notice Legacy-compatible init: compute kappa from slippage parameters and delegate to kappa-based init.
|
||||
/// @dev Provides backward compatibility for callers that still use the (q, tradeFrac, targetSlippage) init signature.
|
||||
function init(
|
||||
State storage s,
|
||||
int128[] memory initialQInternal,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage
|
||||
) internal {
|
||||
// compute kappa using the internal helper
|
||||
int128 kappa = _computeKappaFromSlippage(initialQInternal.length, tradeFrac, targetSlippage);
|
||||
// forward to the new kappa-based init
|
||||
init(s, initialQInternal, kappa);
|
||||
}
|
||||
|
||||
|
||||
/// @notice De-initialize the LMSR state when the entire pool is drained.
|
||||
/// This resets the state so the pool can be re-initialized by init(...) on next mint.
|
||||
function deinit(State storage s) internal {
|
||||
|
||||
Reference in New Issue
Block a user