pure kappa formulation: target slippage extracted into pool creator code
This commit is contained in:
@@ -10,7 +10,8 @@ interface IPartyPlanner {
|
|||||||
// Event emitted when a new pool is created
|
// Event emitted when a new pool is created
|
||||||
event PartyStarted(PartyPool indexed pool, string name, string symbol, IERC20[] tokens);
|
event PartyStarted(PartyPool indexed pool, string name, string symbol, IERC20[] tokens);
|
||||||
|
|
||||||
/// @notice Creates a new PartyPool instance and initializes it with initial deposits
|
/// @notice Creates a new PartyPool instance and initializes it with initial deposits (legacy signature).
|
||||||
|
/// @dev Deprecated in favour of the kappa-based overload below; kept for backwards compatibility.
|
||||||
/// @param name_ LP token name
|
/// @param name_ LP token name
|
||||||
/// @param symbol_ LP token symbol
|
/// @param symbol_ LP token symbol
|
||||||
/// @param _tokens token addresses (n)
|
/// @param _tokens token addresses (n)
|
||||||
@@ -27,7 +28,7 @@ interface IPartyPlanner {
|
|||||||
/// @return pool Address of the newly created and initialized PartyPool
|
/// @return pool Address of the newly created and initialized PartyPool
|
||||||
/// @return lpAmount Amount of LP tokens minted to the receiver
|
/// @return lpAmount Amount of LP tokens minted to the receiver
|
||||||
function createPool(
|
function createPool(
|
||||||
// Pool constructor args
|
// Pool constructor args (legacy)
|
||||||
string memory name_,
|
string memory name_,
|
||||||
string memory symbol_,
|
string memory symbol_,
|
||||||
IERC20[] memory _tokens,
|
IERC20[] memory _tokens,
|
||||||
@@ -45,6 +46,39 @@ interface IPartyPlanner {
|
|||||||
uint256 deadline
|
uint256 deadline
|
||||||
) external returns (PartyPool pool, uint256 lpAmount);
|
) external returns (PartyPool pool, uint256 lpAmount);
|
||||||
|
|
||||||
|
/// @notice Creates a new PartyPool instance and initializes it with initial deposits (kappa-based).
|
||||||
|
/// @param name_ LP token name
|
||||||
|
/// @param symbol_ LP token symbol
|
||||||
|
/// @param _tokens token addresses (n)
|
||||||
|
/// @param _bases scaling bases for each token (n) - used when converting to/from internal 64.64 amounts
|
||||||
|
/// @param _kappa liquidity parameter κ in 64.64 fixed-point used to derive b = κ * S(q)
|
||||||
|
/// @param _swapFeePpm fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||||
|
/// @param _flashFeePpm fee in parts-per-million, taken for flash loans
|
||||||
|
/// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated
|
||||||
|
/// @param payer address that provides the initial token deposits
|
||||||
|
/// @param receiver address that receives the minted LP tokens
|
||||||
|
/// @param initialDeposits amounts of each token to deposit initially
|
||||||
|
/// @param deadline Reverts if nonzero and the current blocktime is later than the deadline
|
||||||
|
/// @return pool Address of the newly created and initialized PartyPool
|
||||||
|
/// @return lpAmount Amount of LP tokens minted to the receiver
|
||||||
|
function createPool(
|
||||||
|
// Pool constructor args (kappa-based)
|
||||||
|
string memory name_,
|
||||||
|
string memory symbol_,
|
||||||
|
IERC20[] memory _tokens,
|
||||||
|
uint256[] memory _bases,
|
||||||
|
int128 _kappa,
|
||||||
|
uint256 _swapFeePpm,
|
||||||
|
uint256 _flashFeePpm,
|
||||||
|
bool _stable,
|
||||||
|
// Initial deposit information
|
||||||
|
address payer,
|
||||||
|
address receiver,
|
||||||
|
uint256[] memory initialDeposits,
|
||||||
|
uint256 initialLpAmount,
|
||||||
|
uint256 deadline
|
||||||
|
) external returns (PartyPool pool, uint256 lpAmount);
|
||||||
|
|
||||||
/// @notice Checks if a pool is supported
|
/// @notice Checks if a pool is supported
|
||||||
/// @param pool The pool address to check
|
/// @param pool The pool address to check
|
||||||
/// @return bool True if the pool is supported, false otherwise
|
/// @return bool True if the pool is supported, false otherwise
|
||||||
|
|||||||
@@ -70,19 +70,16 @@ interface IPartyPool is IERC20Metadata {
|
|||||||
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
|
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
|
||||||
function denominators() external view returns (uint256[] memory);
|
function denominators() external view returns (uint256[] memory);
|
||||||
|
|
||||||
/// @notice Trade fraction (Q64.64) representing a reference trade size as fraction of one asset's inventory.
|
|
||||||
/// @dev Used by the LMSR stabilization logic to compute target slippage.
|
|
||||||
function tradeFrac() external view returns (int128); // ABDK 64x64
|
|
||||||
|
|
||||||
/// @notice Target slippage (Q64.64) applied for the reference trade size specified by tradeFrac.
|
|
||||||
function targetSlippage() external view returns (int128); // ABDK 64x64
|
|
||||||
|
|
||||||
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
||||||
function swapFeePpm() external view returns (uint256);
|
function swapFeePpm() external view returns (uint256);
|
||||||
|
|
||||||
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
|
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
|
||||||
function flashFeePpm() external view returns (uint256);
|
function flashFeePpm() external view returns (uint256);
|
||||||
|
|
||||||
|
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
||||||
|
/// @dev Pools are constructed with a κ value; this getter exposes the κ used by the pool.
|
||||||
|
function kappa() external view returns (int128);
|
||||||
|
|
||||||
/// @notice Mapping from token address => (index+1). A zero value indicates the token is not in the pool.
|
/// @notice Mapping from token address => (index+1). A zero value indicates the token is not in the pool.
|
||||||
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
|
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
|
||||||
function tokenAddressToIndexPlusOne(IERC20) external view returns (uint);
|
function tokenAddressToIndexPlusOne(IERC20) external view returns (uint);
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ library LMSRStabilized {
|
|||||||
function init(
|
function init(
|
||||||
State storage s,
|
State storage s,
|
||||||
int128[] memory initialQInternal,
|
int128[] memory initialQInternal,
|
||||||
int128 tradeFrac,
|
int128 kappa
|
||||||
int128 targetSlippage
|
|
||||||
) internal {
|
) internal {
|
||||||
s.nAssets = initialQInternal.length;
|
s.nAssets = initialQInternal.length;
|
||||||
|
|
||||||
@@ -50,8 +49,8 @@ library LMSRStabilized {
|
|||||||
console2.log("nAssets", s.nAssets);
|
console2.log("nAssets", s.nAssets);
|
||||||
console2.log("qInternal.length", s.qInternal.length);
|
console2.log("qInternal.length", s.qInternal.length);
|
||||||
|
|
||||||
// Compute kappa from slippage parameters
|
// Set kappa directly (caller provides kappa)
|
||||||
setKappaFromSlippage(s, tradeFrac, targetSlippage);
|
s.kappa = kappa;
|
||||||
console2.log("kappa (64x64)");
|
console2.log("kappa (64x64)");
|
||||||
console2.logInt(s.kappa);
|
console2.logInt(s.kappa);
|
||||||
require(s.kappa > int128(0), "LMSR: kappa>0");
|
require(s.kappa > int128(0), "LMSR: kappa>0");
|
||||||
@@ -827,260 +826,81 @@ library LMSRStabilized {
|
|||||||
Slippage -> b computation & resize-triggered rescale
|
Slippage -> b computation & resize-triggered rescale
|
||||||
-------------------- */
|
-------------------- */
|
||||||
|
|
||||||
/// @notice Compute and set kappa from slippage parameters and current asset quantities
|
/// @notice Internal helper to compute kappa from slippage parameters.
|
||||||
/// This should be called during initialization or when recalibrating the pool parameters
|
/// @dev Returns κ in Q64.64. Implemented as internal so callers within the library can use it
|
||||||
function setKappaFromSlippage(
|
/// without resorting to external calls.
|
||||||
State storage s,
|
function _computeKappaFromSlippage(
|
||||||
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
|
|
||||||
uint256 nAssets,
|
uint256 nAssets,
|
||||||
int128 tradeFrac,
|
int128 tradeFrac,
|
||||||
int128 targetSlippage
|
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) {
|
) internal pure returns (int128) {
|
||||||
require(nAssets > 1, "LMSR: n>1 required");
|
require(nAssets > 1, "LMSR: n>1 required");
|
||||||
require(q > int128(0), "LMSR: q>0");
|
|
||||||
// f must be in (0,1)
|
// f must be in (0,1)
|
||||||
int128 f = tradeFrac;
|
int128 f = tradeFrac;
|
||||||
require(f > int128(0), "LMSR: f=0");
|
require(f > int128(0), "LMSR: f=0");
|
||||||
require(f < ONE, "LMSR: f>=1");
|
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);
|
|
||||||
|
|
||||||
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 nMinus1 = ABDKMath64x64.fromUInt(nAssets - 1);
|
|
||||||
int128 numerator = ONE.sub(targetSlippage.mul(nMinus1)); // 1 - s*(n-1)
|
|
||||||
int128 denominator = ONE.add(targetSlippage); // 1 + s
|
|
||||||
|
|
||||||
console2.log("equal-case intermediates:");
|
|
||||||
console2.log("numerator = 1 - s*(n-1)");
|
|
||||||
console2.logInt(numerator);
|
|
||||||
console2.log("denominator = 1 + s");
|
|
||||||
console2.logInt(denominator);
|
|
||||||
|
|
||||||
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;
|
|
||||||
} 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);
|
int128 onePlusS = ONE.add(targetSlippage);
|
||||||
|
|
||||||
console2.log("heterogeneous intermediates:");
|
|
||||||
console2.log("onePlusS = 1 + s");
|
|
||||||
console2.logInt(onePlusS);
|
|
||||||
|
|
||||||
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
int128 n64 = ABDKMath64x64.fromUInt(nAssets);
|
||||||
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
int128 nMinus1_64 = ABDKMath64x64.fromUInt(nAssets - 1);
|
||||||
|
|
||||||
// denom = n - (1+s)
|
// If 1 + s >= n then equal-inventories closed-form applies
|
||||||
int128 denom = n64.sub(onePlusS);
|
bool useEqual = (onePlusS >= n64);
|
||||||
console2.log("denom = n - (1+s)");
|
|
||||||
console2.logInt(denom);
|
|
||||||
|
|
||||||
// Guard and clamp pathological cases similar to previous logic
|
// E candidate used in deriving y = -ln(E)/f (same expression in both branches)
|
||||||
int128 eps = ABDKMath64x64.divu(1, 1_000_000_000); // small epsilon ~1e-9 in Q64.64
|
int128 numerator = ONE.sub(targetSlippage.mul(nMinus1_64)); // 1 - s*(n-1)
|
||||||
if (onePlusS >= n64) {
|
int128 denominator = onePlusS; // 1 + s
|
||||||
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);
|
if (useEqual) {
|
||||||
console2.log("prod = (1+s)*(n-1)");
|
// Guard numerator to ensure E in (0,1)
|
||||||
console2.logInt(prod);
|
require(numerator > int128(0), "LMSR: s too large for n");
|
||||||
|
} else {
|
||||||
if (!(prod > int128(0) && denom < prod)) {
|
// In heterogeneous logic we also require the candidate to be in range; keep same guard
|
||||||
if (denom >= prod) {
|
require(numerator > int128(0), "LMSR: bad slippage or n");
|
||||||
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:
|
int128 E_candidate = numerator.div(denominator);
|
||||||
// E = (1 - s*(n-1)) / (1 + s)
|
require(E_candidate > int128(0) && E_candidate < ONE, "LMSR: bad E ratio");
|
||||||
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
|
// y = -ln(E) / f
|
||||||
|
int128 lnE = _ln(E_candidate);
|
||||||
int128 y = lnE.neg().div(f);
|
int128 y = lnE.neg().div(f);
|
||||||
console2.log("y = -ln(E)/f");
|
|
||||||
console2.logInt(y);
|
|
||||||
require(y > int128(0), "LMSR: y<=0");
|
require(y > int128(0), "LMSR: y<=0");
|
||||||
|
|
||||||
// b = q / y
|
// kappa = 1 / y (since b = q / y -> kappa = b / q = 1 / y)
|
||||||
int128 b = q.div(y);
|
int128 kappa = ONE.div(y);
|
||||||
console2.log("b = q / y (computed)");
|
require(kappa > int128(0), "LMSR: kappa<=0");
|
||||||
console2.logInt(b);
|
|
||||||
require(b > int128(0), "LMSR: b<=0");
|
|
||||||
|
|
||||||
// Simulate slippage using this b to verify
|
return kappa;
|
||||||
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;
|
/// @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.
|
/// @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.
|
/// 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 {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma solidity ^0.8.30;
|
|||||||
|
|
||||||
import "./IPartyPlanner.sol";
|
import "./IPartyPlanner.sol";
|
||||||
import "./PartyPool.sol";
|
import "./PartyPool.sol";
|
||||||
|
import "./LMSRStabilized.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
|
||||||
@@ -19,15 +20,14 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
mapping(IERC20 => bool) private _tokenSupported;
|
mapping(IERC20 => bool) private _tokenSupported;
|
||||||
mapping(IERC20 => PartyPool[]) private _poolsByToken;
|
mapping(IERC20 => PartyPool[]) private _poolsByToken;
|
||||||
|
|
||||||
/// @inheritdoc IPartyPlanner
|
/// Main createPool variant: accepts kappa directly (preferred).
|
||||||
function createPool(
|
function createPool(
|
||||||
// Pool constructor args
|
// Pool constructor args
|
||||||
string memory name_,
|
string memory name_,
|
||||||
string memory symbol_,
|
string memory symbol_,
|
||||||
IERC20[] memory _tokens,
|
IERC20[] memory _tokens,
|
||||||
uint256[] memory _bases,
|
uint256[] memory _bases,
|
||||||
int128 _tradeFrac,
|
int128 _kappa,
|
||||||
int128 _targetSlippage,
|
|
||||||
uint256 _swapFeePpm,
|
uint256 _swapFeePpm,
|
||||||
uint256 _flashFeePpm,
|
uint256 _flashFeePpm,
|
||||||
bool _stable,
|
bool _stable,
|
||||||
@@ -37,25 +37,23 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
uint256[] memory initialDeposits,
|
uint256[] memory initialDeposits,
|
||||||
uint256 initialLpAmount,
|
uint256 initialLpAmount,
|
||||||
uint256 deadline
|
uint256 deadline
|
||||||
) external returns (PartyPool pool, uint256 lpAmount) {
|
) public returns (PartyPool pool, uint256 lpAmount) {
|
||||||
// Validate inputs
|
// Validate inputs
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "Planner: deadline exceeded");
|
require(deadline == 0 || block.timestamp <= deadline, "Planner: deadline exceeded");
|
||||||
require(_tokens.length == initialDeposits.length, "Planner: tokens and deposits length mismatch");
|
require(_tokens.length == initialDeposits.length, "Planner: tokens and deposits length mismatch");
|
||||||
require(payer != address(0), "Planner: payer cannot be zero address");
|
require(payer != address(0), "Planner: payer cannot be zero address");
|
||||||
require(receiver != address(0), "Planner: receiver cannot be zero address");
|
require(receiver != address(0), "Planner: receiver cannot be zero address");
|
||||||
|
|
||||||
// Validate fixed-point fractions: must be less than 1.0 in 64.64 fixed-point
|
// Validate kappa > 0 (Q64.64)
|
||||||
require(_tradeFrac < FIXED_ONE_64x64, "Planner: tradeFrac must be < 1 (64.64)");
|
require(_kappa > int128(0), "Planner: kappa must be > 0");
|
||||||
require(_targetSlippage < FIXED_ONE_64x64, "Planner: targetSlippage must be < 1 (64.64)");
|
|
||||||
|
|
||||||
// Create a new PartyPool instance
|
// Create a new PartyPool instance (kappa-based constructor)
|
||||||
pool = new PartyPool(
|
pool = new PartyPool(
|
||||||
name_,
|
name_,
|
||||||
symbol_,
|
symbol_,
|
||||||
_tokens,
|
_tokens,
|
||||||
_bases,
|
_bases,
|
||||||
_tradeFrac,
|
_kappa,
|
||||||
_targetSlippage,
|
|
||||||
_swapFeePpm,
|
_swapFeePpm,
|
||||||
_flashFeePpm,
|
_flashFeePpm,
|
||||||
_stable
|
_stable
|
||||||
@@ -91,6 +89,50 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
lpAmount = pool.initialMint(receiver, initialLpAmount);
|
lpAmount = pool.initialMint(receiver, initialLpAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Backwards-compatible convenience overload: compute kappa from (tradeFrac, targetSlippage) then call kappa-based createPool.
|
||||||
|
function createPool(
|
||||||
|
// Pool constructor args (old signature)
|
||||||
|
string memory name_,
|
||||||
|
string memory symbol_,
|
||||||
|
IERC20[] memory _tokens,
|
||||||
|
uint256[] memory _bases,
|
||||||
|
int128 _tradeFrac,
|
||||||
|
int128 _targetSlippage,
|
||||||
|
uint256 _swapFeePpm,
|
||||||
|
uint256 _flashFeePpm,
|
||||||
|
bool _stable,
|
||||||
|
// Initial deposit information
|
||||||
|
address payer,
|
||||||
|
address receiver,
|
||||||
|
uint256[] memory initialDeposits,
|
||||||
|
uint256 initialLpAmount,
|
||||||
|
uint256 deadline
|
||||||
|
) external returns (PartyPool pool, uint256 lpAmount) {
|
||||||
|
// Validate fixed-point fractions: must be less than 1.0 in 64.64 fixed-point
|
||||||
|
require(_tradeFrac < FIXED_ONE_64x64, "Planner: tradeFrac must be < 1 (64.64)");
|
||||||
|
require(_targetSlippage < FIXED_ONE_64x64, "Planner: targetSlippage must be < 1 (64.64)");
|
||||||
|
|
||||||
|
// Compute kappa from slippage params using LMSR helper (kappa depends only on n, f and s)
|
||||||
|
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(_tokens.length, _tradeFrac, _targetSlippage);
|
||||||
|
|
||||||
|
// Delegate to the kappa-based createPool variant
|
||||||
|
return createPool(
|
||||||
|
name_,
|
||||||
|
symbol_,
|
||||||
|
_tokens,
|
||||||
|
_bases,
|
||||||
|
computedKappa,
|
||||||
|
_swapFeePpm,
|
||||||
|
_flashFeePpm,
|
||||||
|
_stable,
|
||||||
|
payer,
|
||||||
|
receiver,
|
||||||
|
initialDeposits,
|
||||||
|
initialLpAmount,
|
||||||
|
deadline
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// @inheritdoc IPartyPlanner
|
/// @inheritdoc IPartyPlanner
|
||||||
function getPoolSupported(address pool) external view returns (bool) {
|
function getPoolSupported(address pool) external view returns (bool) {
|
||||||
return _poolSupported[PartyPool(pool)];
|
return _poolSupported[PartyPool(pool)];
|
||||||
|
|||||||
@@ -49,12 +49,10 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
// priced the same. This target is actually a minimum slippage that the pool imposes on traders, and the actual
|
// priced the same. This target is actually a minimum slippage that the pool imposes on traders, and the actual
|
||||||
// slippage cost can be multiples bigger in practice due to pool inventory imbalances.
|
// slippage cost can be multiples bigger in practice due to pool inventory imbalances.
|
||||||
|
|
||||||
/// @notice Trade fraction (Q64.64) representing a reference trade size as fraction of one asset's inventory.
|
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
||||||
/// @dev Used by the LMSR stabilization logic to compute target slippage.
|
/// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage
|
||||||
int128 public immutable tradeFrac; // slippage target trade size as a fraction of one asset's inventory
|
/// should use LMSRStabilized.computeKappaFromSlippage(...) to derive κ and pass it here.
|
||||||
|
int128 public immutable kappa; // kappa in Q64.64
|
||||||
/// @notice Target slippage (Q64.64) applied for the reference trade size specified by tradeFrac.
|
|
||||||
int128 public immutable targetSlippage; // target slippage applied to that trade size
|
|
||||||
|
|
||||||
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
||||||
uint256 public immutable swapFeePpm;
|
uint256 public immutable swapFeePpm;
|
||||||
@@ -94,8 +92,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
/// @param symbol_ LP token symbol
|
/// @param symbol_ LP token symbol
|
||||||
/// @param _tokens token addresses (n)
|
/// @param _tokens token addresses (n)
|
||||||
/// @param _bases scaling bases for each token (n) - used when converting to/from internal 64.64 amounts
|
/// @param _bases scaling bases for each token (n) - used when converting to/from internal 64.64 amounts
|
||||||
/// @param _tradeFrac trade fraction in 64.64 fixed-point (as used by LMSR)
|
/// @param _kappa liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
||||||
/// @param _targetSlippage target slippage in 64.64 fixed-point (as used by LMSR)
|
|
||||||
/// @param _swapFeePpm fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
/// @param _swapFeePpm fee in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||||
/// @param _flashFeePpm fee in parts-per-million, taken for flash loans
|
/// @param _flashFeePpm fee in parts-per-million, taken for flash loans
|
||||||
/// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated.
|
/// @param _stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated.
|
||||||
@@ -104,8 +101,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
string memory symbol_,
|
string memory symbol_,
|
||||||
IERC20[] memory _tokens,
|
IERC20[] memory _tokens,
|
||||||
uint256[] memory _bases,
|
uint256[] memory _bases,
|
||||||
int128 _tradeFrac,
|
int128 _kappa,
|
||||||
int128 _targetSlippage,
|
|
||||||
uint256 _swapFeePpm,
|
uint256 _swapFeePpm,
|
||||||
uint256 _flashFeePpm,
|
uint256 _flashFeePpm,
|
||||||
bool _stable
|
bool _stable
|
||||||
@@ -114,8 +110,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
require(_tokens.length == _bases.length, "Pool: lengths mismatch");
|
require(_tokens.length == _bases.length, "Pool: lengths mismatch");
|
||||||
tokens = _tokens;
|
tokens = _tokens;
|
||||||
bases = _bases;
|
bases = _bases;
|
||||||
tradeFrac = _tradeFrac;
|
kappa = _kappa;
|
||||||
targetSlippage = _targetSlippage;
|
|
||||||
require(_swapFeePpm < 1_000_000, "Pool: fee >= ppm");
|
require(_swapFeePpm < 1_000_000, "Pool: fee >= ppm");
|
||||||
swapFeePpm = _swapFeePpm;
|
swapFeePpm = _swapFeePpm;
|
||||||
require(_flashFeePpm < 1_000_000, "Pool: flash fee >= ppm");
|
require(_flashFeePpm < 1_000_000, "Pool: flash fee >= ppm");
|
||||||
@@ -192,8 +187,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
|||||||
unchecked { i++; }
|
unchecked { i++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the stabilized LMSR state
|
// Initialize the stabilized LMSR state with provided kappa
|
||||||
lmsr.init(newQInternal, tradeFrac, targetSlippage);
|
lmsr.init(newQInternal, kappa);
|
||||||
|
|
||||||
// Compute actual LP tokens to mint based on size metric (scaled)
|
// Compute actual LP tokens to mint based on size metric (scaled)
|
||||||
if( lpTokens != 0 )
|
if( lpTokens != 0 )
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pragma solidity ^0.8.30;
|
|||||||
import "forge-std/Test.sol";
|
import "forge-std/Test.sol";
|
||||||
import "@abdk/ABDKMath64x64.sol";
|
import "@abdk/ABDKMath64x64.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
import "../src/LMSRStabilized.sol";
|
||||||
import "../src/PartyPool.sol";
|
import "../src/PartyPool.sol";
|
||||||
|
|
||||||
// Import the flash callback interface
|
// Import the flash callback interface
|
||||||
@@ -169,7 +170,9 @@ contract GasTest is Test {
|
|||||||
for (uint i = 0; i < tokens.length; i++) {
|
for (uint i = 0; i < tokens.length; i++) {
|
||||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||||
}
|
}
|
||||||
PartyPool newPool = new PartyPool(poolName, poolName, ierc20Tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
// Compute kappa from slippage params and number of tokens, then construct pool with kappa
|
||||||
|
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
||||||
|
PartyPool newPool = new PartyPool(poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool before initial mint
|
// Transfer initial deposit amounts into pool before initial mint
|
||||||
for (uint256 i = 0; i < numTokens; i++) {
|
for (uint256 i = 0; i < numTokens; i++) {
|
||||||
@@ -208,7 +211,8 @@ contract GasTest is Test {
|
|||||||
for (uint i = 0; i < tokens.length; i++) {
|
for (uint i = 0; i < tokens.length; i++) {
|
||||||
ierc20Tokens[i] = IERC20(tokens[i]);
|
ierc20Tokens[i] = IERC20(tokens[i]);
|
||||||
}
|
}
|
||||||
PartyPool newPool = new PartyPool(poolName, poolName, ierc20Tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, true);
|
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(ierc20Tokens.length, tradeFrac, targetSlippage);
|
||||||
|
PartyPool newPool = new PartyPool(poolName, poolName, ierc20Tokens, bases, computedKappa, feePpm, feePpm, true);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool before initial mint
|
// Transfer initial deposit amounts into pool before initial mint
|
||||||
for (uint256 i = 0; i < numTokens; i++) {
|
for (uint256 i = 0; i < numTokens; i++) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
pragma solidity ^0.8.30;
|
pragma solidity ^0.8.30;
|
||||||
|
|
||||||
import "forge-std/Test.sol";
|
import "forge-std/Test.sol";
|
||||||
|
import "../src/LMSRStabilized.sol";
|
||||||
import "../src/PartyPlanner.sol";
|
import "../src/PartyPlanner.sol";
|
||||||
import "../src/PartyPool.sol";
|
import "../src/PartyPool.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
@@ -83,14 +84,15 @@ contract PartyPlannerTest is Test {
|
|||||||
uint256 initialTokenACount = planner.poolsByTokenCount(IERC20(address(tokenA)));
|
uint256 initialTokenACount = planner.poolsByTokenCount(IERC20(address(tokenA)));
|
||||||
uint256 initialTokenBCount = planner.poolsByTokenCount(IERC20(address(tokenB)));
|
uint256 initialTokenBCount = planner.poolsByTokenCount(IERC20(address(tokenB)));
|
||||||
|
|
||||||
// Create pool
|
// Compute kappa then create pool via kappa overload
|
||||||
|
int128 computedKappa = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||||
|
|
||||||
(PartyPool pool, uint256 lpAmount) = planner.createPool(
|
(PartyPool pool, uint256 lpAmount) = planner.createPool(
|
||||||
name,
|
name,
|
||||||
symbol,
|
symbol,
|
||||||
tokens,
|
tokens,
|
||||||
bases,
|
bases,
|
||||||
tradeFrac,
|
computedKappa,
|
||||||
targetSlippage,
|
|
||||||
swapFeePpm,
|
swapFeePpm,
|
||||||
flashFeePpm,
|
flashFeePpm,
|
||||||
false, // not stable
|
false, // not stable
|
||||||
@@ -163,9 +165,10 @@ contract PartyPlannerTest is Test {
|
|||||||
deposits1[0] = INITIAL_DEPOSIT_AMOUNT;
|
deposits1[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
deposits1[1] = INITIAL_DEPOSIT_AMOUNT;
|
deposits1[1] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
|
|
||||||
|
int128 kappa1 = LMSRStabilized.computeKappaFromSlippage(tokens1.length, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
(PartyPool pool1,) = planner.createPool(
|
(PartyPool pool1,) = planner.createPool(
|
||||||
"Pool 1", "LP1", tokens1, bases1,
|
"Pool 1", "LP1", tokens1, bases1,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
kappa1, 3000, 5000, false,
|
||||||
payer, receiver, deposits1, 1000e18, 0
|
payer, receiver, deposits1, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -182,9 +185,10 @@ contract PartyPlannerTest is Test {
|
|||||||
deposits2[0] = INITIAL_DEPOSIT_AMOUNT;
|
deposits2[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
deposits2[1] = INITIAL_DEPOSIT_AMOUNT / 1e12; // Adjust for 6 decimals
|
deposits2[1] = INITIAL_DEPOSIT_AMOUNT / 1e12; // Adjust for 6 decimals
|
||||||
|
|
||||||
|
int128 kappa2 = LMSRStabilized.computeKappaFromSlippage(tokens2.length, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
(PartyPool pool2,) = planner.createPool(
|
(PartyPool pool2,) = planner.createPool(
|
||||||
"Pool 2", "LP2", tokens2, bases2,
|
"Pool 2", "LP2", tokens2, bases2,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
kappa2, 3000, 5000, false,
|
||||||
payer, receiver, deposits2, 1000e18, 0
|
payer, receiver, deposits2, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -225,6 +229,7 @@ contract PartyPlannerTest is Test {
|
|||||||
|
|
||||||
// Test token/deposit length mismatch
|
// Test token/deposit length mismatch
|
||||||
vm.expectRevert("Planner: tokens and deposits length mismatch");
|
vm.expectRevert("Planner: tokens and deposits length mismatch");
|
||||||
|
// call old-signature convenience (it will still exist) for the mismatched-length revert check
|
||||||
planner.createPool(
|
planner.createPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
||||||
@@ -236,10 +241,12 @@ contract PartyPlannerTest is Test {
|
|||||||
validDeposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
validDeposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
validDeposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
validDeposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
|
|
||||||
|
int128 kappaErr = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
|
|
||||||
vm.expectRevert("Planner: payer cannot be zero address");
|
vm.expectRevert("Planner: payer cannot be zero address");
|
||||||
planner.createPool(
|
planner.createPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
kappaErr, 3000, 5000, false,
|
||||||
address(0), receiver, validDeposits, 1000e18, 0
|
address(0), receiver, validDeposits, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -247,17 +254,18 @@ contract PartyPlannerTest is Test {
|
|||||||
vm.expectRevert("Planner: receiver cannot be zero address");
|
vm.expectRevert("Planner: receiver cannot be zero address");
|
||||||
planner.createPool(
|
planner.createPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
kappaErr, 3000, 5000, false,
|
||||||
payer, address(0), validDeposits, 1000e18, 0
|
payer, address(0), validDeposits, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test deadline exceeded
|
// Test deadline exceeded
|
||||||
// The default timestamp is 1 and 1-0 is 0 which means "ignore deadline," so we need to set a proper timestamp.
|
// The default timestamp is 1 and 1-0 is 0 which means "ignore deadline," so we need to set a proper timestamp.
|
||||||
|
int128 kappaDeadline = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
vm.warp(1000);
|
vm.warp(1000);
|
||||||
vm.expectRevert("Planner: deadline exceeded");
|
vm.expectRevert("Planner: deadline exceeded");
|
||||||
planner.createPool(
|
planner.createPool(
|
||||||
"Test Pool", "TESTLP", tokens, bases,
|
"Test Pool", "TESTLP", tokens, bases,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
kappaDeadline, 3000, 5000, false,
|
||||||
payer, receiver, validDeposits, 1000e18, block.timestamp - 1
|
payer, receiver, validDeposits, 1000e18, block.timestamp - 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -280,11 +288,12 @@ contract PartyPlannerTest is Test {
|
|||||||
deposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
deposits[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
deposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
deposits[1] = INITIAL_DEPOSIT_AMOUNT;
|
||||||
|
|
||||||
|
int128 kappaLoop = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
||||||
(PartyPool pool,) = planner.createPool(
|
(PartyPool pool,) = planner.createPool(
|
||||||
string(abi.encodePacked("Pool ", vm.toString(i))),
|
string(abi.encodePacked("Pool ", vm.toString(i))),
|
||||||
string(abi.encodePacked("LP", vm.toString(i))),
|
string(abi.encodePacked("LP", vm.toString(i))),
|
||||||
tokens, bases,
|
tokens, bases,
|
||||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
kappaLoop, 3000, 5000, false,
|
||||||
payer, receiver, deposits, 1000e18, 0
|
payer, receiver, deposits, 1000e18, 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ pragma solidity ^0.8.30;
|
|||||||
import "forge-std/Test.sol";
|
import "forge-std/Test.sol";
|
||||||
import "@abdk/ABDKMath64x64.sol";
|
import "@abdk/ABDKMath64x64.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||||
|
import "../src/LMSRStabilized.sol";
|
||||||
import "../src/PartyPool.sol";
|
import "../src/PartyPool.sol";
|
||||||
|
|
||||||
// Import the flash callback interface
|
// Import the flash callback interface
|
||||||
@@ -195,7 +196,8 @@ contract PartyPoolTest is Test {
|
|||||||
// Deploy pool with a small fee to test fee-handling paths (use 1000 ppm = 0.1%)
|
// Deploy pool with a small fee to test fee-handling paths (use 1000 ppm = 0.1%)
|
||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
pool = new PartyPool("LP", "LP", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
int128 kappa3 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||||
|
pool = new PartyPool("LP", "LP", tokens, bases, kappa3, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Transfer initial deposit amounts into pool before initial mint (pool expects tokens already in contract)
|
// Transfer initial deposit amounts into pool before initial mint (pool expects tokens already in contract)
|
||||||
// We deposit equal amounts INIT_BAL for each token
|
// We deposit equal amounts INIT_BAL for each token
|
||||||
@@ -224,7 +226,8 @@ contract PartyPoolTest is Test {
|
|||||||
bases10[i] = BASE;
|
bases10[i] = BASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
pool10 = new PartyPool("LP10", "LP10", tokens10, bases10, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
int128 kappa10 = LMSRStabilized.computeKappaFromSlippage(tokens10.length, tradeFrac, targetSlippage);
|
||||||
|
pool10 = new PartyPool("LP10", "LP10", tokens10, bases10, kappa10, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Mint additional tokens for pool10 initial deposit
|
// Mint additional tokens for pool10 initial deposit
|
||||||
token0.mint(address(this), INIT_BAL);
|
token0.mint(address(this), INIT_BAL);
|
||||||
@@ -1227,10 +1230,12 @@ contract PartyPoolTest is Test {
|
|||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
// Pool with default initialization (lpTokens = 0)
|
// Pool with default initialization (lpTokens = 0)
|
||||||
PartyPool poolDefault = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
int128 kappaDefault = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||||
|
PartyPool poolDefault = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Pool with custom initialization (lpTokens = custom amount)
|
// Pool with custom initialization (lpTokens = custom amount)
|
||||||
PartyPool poolCustom = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
int128 kappaCustom = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||||
|
PartyPool poolCustom = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Mint additional tokens for both pools
|
// Mint additional tokens for both pools
|
||||||
token0.mint(address(this), INIT_BAL * 2);
|
token0.mint(address(this), INIT_BAL * 2);
|
||||||
@@ -1301,8 +1306,10 @@ contract PartyPoolTest is Test {
|
|||||||
|
|
||||||
uint256 feePpm = 1000;
|
uint256 feePpm = 1000;
|
||||||
|
|
||||||
PartyPool poolDefault = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
int128 kappaDefault2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||||
PartyPool poolCustom = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
PartyPool poolDefault = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, kappaDefault2, feePpm, feePpm, false);
|
||||||
|
int128 kappaCustom2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||||
|
PartyPool poolCustom = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, kappaCustom2, feePpm, feePpm, false);
|
||||||
|
|
||||||
// Mint additional tokens
|
// Mint additional tokens
|
||||||
token0.mint(address(this), INIT_BAL * 4);
|
token0.mint(address(this), INIT_BAL * 4);
|
||||||
|
|||||||
Reference in New Issue
Block a user