fixed-b
This commit is contained in:
@@ -30,7 +30,8 @@ contract LMSRStabilizedTest is Test {
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[2] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 b = _computeBFromSlippage(3, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, b);
|
||||
}
|
||||
|
||||
function initAlmostBalanced() internal {
|
||||
@@ -38,7 +39,8 @@ contract LMSRStabilizedTest is Test {
|
||||
q[0] = ABDKMath64x64.fromUInt(999_999);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[2] = ABDKMath64x64.fromUInt(1_000_001);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 b = _computeBFromSlippage(3, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, b);
|
||||
}
|
||||
|
||||
function initImbalanced() internal {
|
||||
@@ -47,7 +49,8 @@ contract LMSRStabilizedTest is Test {
|
||||
q[1] = ABDKMath64x64.fromUInt(1e9);
|
||||
q[2] = ABDKMath64x64.fromUInt(1);
|
||||
q[3] = ABDKMath64x64.divu(1, 1e9);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 b = _computeBFromSlippage(4, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, b);
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +196,6 @@ contract LMSRStabilizedTest is Test {
|
||||
|
||||
// Verify basic state is still functional
|
||||
assertTrue(s.nAssets > 0, "State should still be initialized");
|
||||
assertTrue(s.kappa > int128(0), "Kappa should still be positive");
|
||||
}
|
||||
|
||||
function testRescalingAfterDeposit() public {
|
||||
@@ -211,7 +213,6 @@ contract LMSRStabilizedTest is Test {
|
||||
|
||||
// Store initial parameters
|
||||
int128 initialB = _computeB(initialQ);
|
||||
int128 initialKappa = s.kappa;
|
||||
|
||||
// Simulate a deposit by increasing all asset quantities by 50%
|
||||
int128[] memory newQ = new int128[](s.nAssets);
|
||||
@@ -223,18 +224,15 @@ contract LMSRStabilizedTest is Test {
|
||||
// Apply the update for proportional change
|
||||
s.updateForProportionalChange(newQ);
|
||||
|
||||
// Verify that b has been rescaled proportionally
|
||||
// Verify that b remains constant after proportional deposit (fixed-b model)
|
||||
int128 newB = _computeB(s.qInternal);
|
||||
int128 expectedRatio = ABDKMath64x64.fromUInt(3).div(ABDKMath64x64.fromUInt(2)); // 1.5x
|
||||
int128 expectedRatio = ABDKMath64x64.fromInt(1); // invariant b
|
||||
int128 actualRatio = newB.div(initialB);
|
||||
|
||||
int128 tolerance = ABDKMath64x64.divu(1, 1000); // 0.1% tolerance
|
||||
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b did not scale proportionally after deposit");
|
||||
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b should remain constant after deposit");
|
||||
|
||||
// Verify kappa remained unchanged
|
||||
assertTrue((s.kappa.sub(initialKappa)).abs() < tolerance, "kappa should not change after deposit");
|
||||
|
||||
// Verify slippage target is still met by performing a trade
|
||||
// Perform a trade and verify outputs are reasonable
|
||||
int128 tradeAmount = s.qInternal[0].mul(stdTradeSize);
|
||||
(int128 amountIn, int128 amountOut) = s.swapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||
|
||||
@@ -250,8 +248,10 @@ contract LMSRStabilizedTest is Test {
|
||||
int128 slippage = slippageRatio.sub(ABDKMath64x64.fromInt(1));
|
||||
console2.log('post-deposit slippage', slippage);
|
||||
|
||||
int128 relativeError = slippage.sub(stdSlippage).abs().div(stdSlippage);
|
||||
assertLt(relativeError, ABDKMath64x64.divu(1, 100), "Slippage target not met after deposit");
|
||||
// With fixed b, theoretical slippage is exp(a/b) - 1
|
||||
int128 expectedSlippage = _exp(tradeAmount.div(newB)).sub(ABDKMath64x64.fromInt(1));
|
||||
int128 slippageError = (slippage.sub(expectedSlippage)).abs();
|
||||
assertLt(slippageError, ABDKMath64x64.divu(1, 1_000_000), "Observed slippage deviates from model");
|
||||
}
|
||||
|
||||
/// @notice Test balanced2 handling of limitPrice that causes truncation of input a
|
||||
@@ -260,7 +260,8 @@ contract LMSRStabilizedTest is Test {
|
||||
int128[] memory q = new int128[](2);
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, bInit);
|
||||
|
||||
// Compute b for constructing meaningful a and limits
|
||||
int128 b = _computeB(q);
|
||||
@@ -296,7 +297,8 @@ contract LMSRStabilizedTest is Test {
|
||||
int128[] memory q = new int128[](2);
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, bInit);
|
||||
|
||||
// Small input a
|
||||
int128 a = q[0].mul(ABDKMath64x64.divu(1, 1000)); // 0.1% of asset
|
||||
@@ -326,7 +328,8 @@ contract LMSRStabilizedTest is Test {
|
||||
int128[] memory q = new int128[](2);
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, bInit);
|
||||
|
||||
int128 limitPrice = ABDKMath64x64.fromInt(1); // equal to current price
|
||||
|
||||
@@ -359,7 +362,6 @@ contract LMSRStabilizedTest is Test {
|
||||
|
||||
// Store initial parameters
|
||||
int128 initialB = _computeB(initialQ);
|
||||
int128 initialKappa = s.kappa;
|
||||
|
||||
// Simulate a withdrawal by decreasing all asset quantities by 30%
|
||||
int128[] memory newQ = new int128[](s.nAssets);
|
||||
@@ -371,18 +373,15 @@ contract LMSRStabilizedTest is Test {
|
||||
// Apply the update for proportional change
|
||||
s.updateForProportionalChange(newQ);
|
||||
|
||||
// Verify that b has been rescaled proportionally
|
||||
// Verify that b remains constant after proportional withdrawal (fixed-b model)
|
||||
int128 newB = _computeB(s.qInternal);
|
||||
int128 expectedRatio = ABDKMath64x64.fromUInt(7).div(ABDKMath64x64.fromUInt(10)); // 0.7x
|
||||
int128 expectedRatio = ABDKMath64x64.fromInt(1); // invariant b
|
||||
int128 actualRatio = newB.div(initialB);
|
||||
|
||||
int128 tolerance = ABDKMath64x64.divu(1, 1000); // 0.1% tolerance
|
||||
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b did not scale proportionally after withdrawal");
|
||||
assertTrue((actualRatio.sub(expectedRatio)).abs() < tolerance, "b should remain constant after withdrawal");
|
||||
|
||||
// Verify kappa remained unchanged
|
||||
assertTrue((s.kappa.sub(initialKappa)).abs() < tolerance, "kappa should not change after withdrawal");
|
||||
|
||||
// Verify slippage target is still met by performing a trade
|
||||
// Perform a trade and verify outputs are reasonable
|
||||
int128 tradeAmount = s.qInternal[0].mul(stdTradeSize);
|
||||
(int128 amountIn, int128 amountOut) = s.swapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||
|
||||
@@ -398,8 +397,10 @@ contract LMSRStabilizedTest is Test {
|
||||
int128 slippage = slippageRatio.sub(ABDKMath64x64.fromInt(1));
|
||||
console2.log('post-withdrawal slippage', slippage);
|
||||
|
||||
int128 relativeError = slippage.sub(stdSlippage).abs().div(stdSlippage);
|
||||
assertLt(relativeError, ABDKMath64x64.divu(1, 100), "Slippage target not met after withdrawal");
|
||||
// With fixed b, theoretical slippage is exp(a/b) - 1
|
||||
int128 expectedSlippage = _exp(tradeAmount.div(newB)).sub(ABDKMath64x64.fromInt(1));
|
||||
int128 slippageError = (slippage.sub(expectedSlippage)).abs();
|
||||
assertLt(slippageError, ABDKMath64x64.divu(1, 1_000_000), "Observed slippage deviates from model");
|
||||
}
|
||||
|
||||
// --- tests probing numerical stability and boundary conditions ---
|
||||
@@ -431,8 +432,8 @@ 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 {
|
||||
/// @notice If q_j == 0, kernel should still handle computation without revert (wrapper enforces caps)
|
||||
function testZeroQuantityOutputAssetDoesNotRevert() public {
|
||||
initBalanced();
|
||||
|
||||
// Create mock qInternal where asset 1 has zero quantity
|
||||
@@ -446,8 +447,10 @@ contract LMSRStabilizedTest is Test {
|
||||
|
||||
int128 tradeAmount = mockQInternal[0].mul(stdTradeSize);
|
||||
|
||||
vm.expectRevert(bytes("LMSR: e_j==0"));
|
||||
this.externalSwapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||
// Should not revert; exact-input uses full input and returns a defined output
|
||||
(int128 usedIn, int128 outAmt) = this.externalSwapAmountsForExactInput(0, 1, tradeAmount, 0);
|
||||
assertEq(usedIn, tradeAmount, "exact-input should consume full input without limit");
|
||||
assertTrue(outAmt >= 0, "output amount should be non-negative when q_j == 0");
|
||||
}
|
||||
|
||||
/// @notice swapAmountsForPriceLimit returns zero if limit equals current price
|
||||
@@ -534,18 +537,16 @@ contract LMSRStabilizedTest is Test {
|
||||
this.externalSwapAmountsForExactInput(0, 1, a, 0);
|
||||
}
|
||||
|
||||
// Helper function to compute b from qInternal (either from provided array or state)
|
||||
// Helper function to fetch fixed b (independent of qInternal)
|
||||
function _computeB(int128[] memory qInternal) internal view returns (int128) {
|
||||
int128 sizeMetric = _computeSizeMetric(qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
return s.kappa.mul(sizeMetric);
|
||||
// silence unused warning for qInternal in tests
|
||||
qInternal;
|
||||
return s.bFixed;
|
||||
}
|
||||
|
||||
// Overload that uses state's cached qInternal
|
||||
// Overload that uses state's fixed b
|
||||
function _computeB() internal view returns (int128) {
|
||||
int128 sizeMetric = _computeSizeMetric(s.qInternal);
|
||||
require(sizeMetric > int128(0), "LMSR: size metric zero");
|
||||
return s.kappa.mul(sizeMetric);
|
||||
return s.bFixed;
|
||||
}
|
||||
|
||||
// Helper function to compute size metric (sum of all asset quantities)
|
||||
@@ -558,6 +559,41 @@ contract LMSRStabilizedTest is Test {
|
||||
return total;
|
||||
}
|
||||
|
||||
// Local helper: compute fixed b from a target slippage profile.
|
||||
// For a trade of fraction f of S with target slippage s across n assets:
|
||||
// E = (1 - s*(n-1)) / (1 + s)
|
||||
// y = -ln(E) / f
|
||||
// b = S / y
|
||||
function _computeBFromSlippage(
|
||||
uint256 nAssets,
|
||||
int128[] memory qInternal,
|
||||
int128 tradeFrac,
|
||||
int128 targetSlippage
|
||||
) internal pure returns (int128) {
|
||||
require(nAssets > 1, "test: n>1");
|
||||
int128 S = _computeSizeMetric(qInternal);
|
||||
require(S > int128(0), "test: S<=0");
|
||||
|
||||
int128 f = tradeFrac;
|
||||
require(f > int128(0) && f < ABDKMath64x64.fromInt(1), "test: f out of range");
|
||||
|
||||
int128 one = ABDKMath64x64.fromInt(1);
|
||||
int128 nMinus1 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||
|
||||
// E must be in (0,1)
|
||||
int128 numerator = one.sub(targetSlippage.mul(nMinus1)); // 1 - s*(n-1)
|
||||
int128 denominator = one.add(targetSlippage); // 1 + s
|
||||
require(numerator > int128(0), "test: bad slippage");
|
||||
|
||||
int128 E = numerator.div(denominator);
|
||||
require(E > int128(0) && E < one, "test: E out of range");
|
||||
|
||||
int128 y = ABDKMath64x64.ln(E).neg().div(f);
|
||||
require(y > int128(0), "test: y<=0");
|
||||
|
||||
return S.div(y);
|
||||
}
|
||||
|
||||
// Helper function to update the state's cached qInternal
|
||||
function _updateCachedQInternal(int128[] memory mockQInternal) internal {
|
||||
// First ensure qInternal array exists with the right size
|
||||
@@ -955,7 +991,8 @@ contract LMSRStabilizedTest is Test {
|
||||
int128[] memory q = new int128[](2);
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, bInit);
|
||||
|
||||
// Small trade (well within u <= 0.5 and delta <= 1%)
|
||||
int128 a = q[0].mul(ABDKMath64x64.divu(1, 1000)); // 0.1% of asset
|
||||
@@ -986,7 +1023,8 @@ contract LMSRStabilizedTest is Test {
|
||||
int128[] memory q = new int128[](2);
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, bInit);
|
||||
|
||||
// Prepare newQ starting from equal quantities; we'll grow q0 until delta > DELTA_MAX
|
||||
int128[] memory newQ = new int128[](2);
|
||||
@@ -1045,7 +1083,8 @@ contract LMSRStabilizedTest is Test {
|
||||
int128[] memory q = new int128[](2);
|
||||
q[0] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
q[1] = ABDKMath64x64.fromUInt(1_000_000);
|
||||
s.init(q, stdTradeSize, stdSlippage);
|
||||
int128 bInit = _computeBFromSlippage(2, q, stdTradeSize, stdSlippage);
|
||||
s.init(q, bInit);
|
||||
|
||||
// Compute b
|
||||
int128 b = _computeB(q);
|
||||
|
||||
Reference in New Issue
Block a user