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 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 symbol_ LP token symbol
|
||||
/// @param _tokens token addresses (n)
|
||||
@@ -27,7 +28,7 @@ interface IPartyPlanner {
|
||||
/// @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
|
||||
// Pool constructor args (legacy)
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory _tokens,
|
||||
@@ -45,6 +46,39 @@ interface IPartyPlanner {
|
||||
uint256 deadline
|
||||
) 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
|
||||
/// @param pool The pool address to check
|
||||
/// @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.
|
||||
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.
|
||||
function swapFeePpm() external view returns (uint256);
|
||||
|
||||
/// @notice Flash-loan fee in parts-per-million (ppm) applied to flash borrow amounts.
|
||||
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.
|
||||
/// @dev Use index = tokenAddressToIndexPlusOne[token] - 1 when non-zero.
|
||||
function tokenAddressToIndexPlusOne(IERC20) external view returns (uint);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -3,6 +3,7 @@ pragma solidity ^0.8.30;
|
||||
|
||||
import "./IPartyPlanner.sol";
|
||||
import "./PartyPool.sol";
|
||||
import "./LMSRStabilized.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
@@ -19,15 +20,14 @@ contract PartyPlanner is IPartyPlanner {
|
||||
mapping(IERC20 => bool) private _tokenSupported;
|
||||
mapping(IERC20 => PartyPool[]) private _poolsByToken;
|
||||
|
||||
/// @inheritdoc IPartyPlanner
|
||||
/// Main createPool variant: accepts kappa directly (preferred).
|
||||
function createPool(
|
||||
// Pool constructor args
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory _tokens,
|
||||
uint256[] memory _bases,
|
||||
int128 _tradeFrac,
|
||||
int128 _targetSlippage,
|
||||
int128 _kappa,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
bool _stable,
|
||||
@@ -37,25 +37,23 @@ contract PartyPlanner is IPartyPlanner {
|
||||
uint256[] memory initialDeposits,
|
||||
uint256 initialLpAmount,
|
||||
uint256 deadline
|
||||
) external returns (PartyPool pool, uint256 lpAmount) {
|
||||
) public returns (PartyPool pool, uint256 lpAmount) {
|
||||
// Validate inputs
|
||||
require(deadline == 0 || block.timestamp <= deadline, "Planner: deadline exceeded");
|
||||
require(_tokens.length == initialDeposits.length, "Planner: tokens and deposits length mismatch");
|
||||
require(payer != address(0), "Planner: payer 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
|
||||
require(_tradeFrac < FIXED_ONE_64x64, "Planner: tradeFrac must be < 1 (64.64)");
|
||||
require(_targetSlippage < FIXED_ONE_64x64, "Planner: targetSlippage must be < 1 (64.64)");
|
||||
// Validate kappa > 0 (Q64.64)
|
||||
require(_kappa > int128(0), "Planner: kappa must be > 0");
|
||||
|
||||
// Create a new PartyPool instance
|
||||
// Create a new PartyPool instance (kappa-based constructor)
|
||||
pool = new PartyPool(
|
||||
name_,
|
||||
symbol_,
|
||||
_tokens,
|
||||
_bases,
|
||||
_tradeFrac,
|
||||
_targetSlippage,
|
||||
_kappa,
|
||||
_swapFeePpm,
|
||||
_flashFeePpm,
|
||||
_stable
|
||||
@@ -90,6 +88,50 @@ contract PartyPlanner is IPartyPlanner {
|
||||
// Call mint on the new pool to initialize it with the transferred tokens
|
||||
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
|
||||
function getPoolSupported(address pool) external view returns (bool) {
|
||||
|
||||
@@ -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
|
||||
// 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.
|
||||
/// @dev Used by the LMSR stabilization logic to compute target slippage.
|
||||
int128 public immutable tradeFrac; // slippage target trade size as a fraction of one asset's inventory
|
||||
|
||||
/// @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 Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
||||
/// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage
|
||||
/// should use LMSRStabilized.computeKappaFromSlippage(...) to derive κ and pass it here.
|
||||
int128 public immutable kappa; // kappa in Q64.64
|
||||
|
||||
/// @notice Per-swap fee in parts-per-million (ppm). Fee is taken from input amounts before LMSR computations.
|
||||
uint256 public immutable swapFeePpm;
|
||||
@@ -94,8 +92,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
/// @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 _tradeFrac trade fraction in 64.64 fixed-point (as used by LMSR)
|
||||
/// @param _targetSlippage target slippage in 64.64 fixed-point (as used by LMSR)
|
||||
/// @param _kappa liquidity parameter κ (Q64.64) 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.
|
||||
@@ -104,8 +101,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
string memory symbol_,
|
||||
IERC20[] memory _tokens,
|
||||
uint256[] memory _bases,
|
||||
int128 _tradeFrac,
|
||||
int128 _targetSlippage,
|
||||
int128 _kappa,
|
||||
uint256 _swapFeePpm,
|
||||
uint256 _flashFeePpm,
|
||||
bool _stable
|
||||
@@ -114,8 +110,7 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
require(_tokens.length == _bases.length, "Pool: lengths mismatch");
|
||||
tokens = _tokens;
|
||||
bases = _bases;
|
||||
tradeFrac = _tradeFrac;
|
||||
targetSlippage = _targetSlippage;
|
||||
kappa = _kappa;
|
||||
require(_swapFeePpm < 1_000_000, "Pool: fee >= ppm");
|
||||
swapFeePpm = _swapFeePpm;
|
||||
require(_flashFeePpm < 1_000_000, "Pool: flash fee >= ppm");
|
||||
@@ -192,8 +187,8 @@ contract PartyPool is IPartyPool, ERC20, ReentrancyGuard {
|
||||
unchecked { i++; }
|
||||
}
|
||||
|
||||
// Initialize the stabilized LMSR state
|
||||
lmsr.init(newQInternal, tradeFrac, targetSlippage);
|
||||
// Initialize the stabilized LMSR state with provided kappa
|
||||
lmsr.init(newQInternal, kappa);
|
||||
|
||||
// Compute actual LP tokens to mint based on size metric (scaled)
|
||||
if( lpTokens != 0 )
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma solidity ^0.8.30;
|
||||
import "forge-std/Test.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
|
||||
// Import the flash callback interface
|
||||
@@ -169,7 +170,9 @@ contract GasTest is Test {
|
||||
for (uint i = 0; i < tokens.length; 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
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
@@ -208,7 +211,8 @@ contract GasTest is Test {
|
||||
for (uint i = 0; i < tokens.length; 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
|
||||
for (uint256 i = 0; i < numTokens; i++) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "forge-std/Test.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPlanner.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
@@ -83,14 +84,15 @@ contract PartyPlannerTest is Test {
|
||||
uint256 initialTokenACount = planner.poolsByTokenCount(IERC20(address(tokenA)));
|
||||
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(
|
||||
name,
|
||||
symbol,
|
||||
tokens,
|
||||
bases,
|
||||
tradeFrac,
|
||||
targetSlippage,
|
||||
computedKappa,
|
||||
swapFeePpm,
|
||||
flashFeePpm,
|
||||
false, // not stable
|
||||
@@ -163,9 +165,10 @@ contract PartyPlannerTest is Test {
|
||||
deposits1[0] = 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(
|
||||
"Pool 1", "LP1", tokens1, bases1,
|
||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
||||
kappa1, 3000, 5000, false,
|
||||
payer, receiver, deposits1, 1000e18, 0
|
||||
);
|
||||
|
||||
@@ -182,9 +185,10 @@ contract PartyPlannerTest is Test {
|
||||
deposits2[0] = INITIAL_DEPOSIT_AMOUNT;
|
||||
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(
|
||||
"Pool 2", "LP2", tokens2, bases2,
|
||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
||||
kappa2, 3000, 5000, false,
|
||||
payer, receiver, deposits2, 1000e18, 0
|
||||
);
|
||||
|
||||
@@ -225,6 +229,7 @@ contract PartyPlannerTest is Test {
|
||||
|
||||
// Test token/deposit 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(
|
||||
"Test Pool", "TESTLP", tokens, bases,
|
||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
||||
@@ -236,10 +241,12 @@ contract PartyPlannerTest is Test {
|
||||
validDeposits[0] = 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");
|
||||
planner.createPool(
|
||||
"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
|
||||
);
|
||||
|
||||
@@ -247,17 +254,18 @@ contract PartyPlannerTest is Test {
|
||||
vm.expectRevert("Planner: receiver cannot be zero address");
|
||||
planner.createPool(
|
||||
"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
|
||||
);
|
||||
|
||||
// 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.
|
||||
int128 kappaDeadline = LMSRStabilized.computeKappaFromSlippage(tokens.length, int128((1 << 64) - 1), int128(1 << 62));
|
||||
vm.warp(1000);
|
||||
vm.expectRevert("Planner: deadline exceeded");
|
||||
planner.createPool(
|
||||
"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
|
||||
);
|
||||
}
|
||||
@@ -280,11 +288,12 @@ contract PartyPlannerTest is Test {
|
||||
deposits[0] = 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(
|
||||
string(abi.encodePacked("Pool ", vm.toString(i))),
|
||||
string(abi.encodePacked("LP", vm.toString(i))),
|
||||
tokens, bases,
|
||||
int128((1 << 64) - 1), int128(1 << 62), 3000, 5000, false,
|
||||
kappaLoop, 3000, 5000, false,
|
||||
payer, receiver, deposits, 1000e18, 0
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ pragma solidity ^0.8.30;
|
||||
import "forge-std/Test.sol";
|
||||
import "@abdk/ABDKMath64x64.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import "../src/LMSRStabilized.sol";
|
||||
import "../src/PartyPool.sol";
|
||||
|
||||
// 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%)
|
||||
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)
|
||||
// We deposit equal amounts INIT_BAL for each token
|
||||
@@ -224,7 +226,8 @@ contract PartyPoolTest is Test {
|
||||
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
|
||||
token0.mint(address(this), INIT_BAL);
|
||||
@@ -1227,10 +1230,12 @@ contract PartyPoolTest is Test {
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
// 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)
|
||||
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
|
||||
token0.mint(address(this), INIT_BAL * 2);
|
||||
@@ -1301,8 +1306,10 @@ contract PartyPoolTest is Test {
|
||||
|
||||
uint256 feePpm = 1000;
|
||||
|
||||
PartyPool poolDefault = new PartyPool("LP_DEFAULT", "LP_DEFAULT", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
||||
PartyPool poolCustom = new PartyPool("LP_CUSTOM", "LP_CUSTOM", tokens, bases, tradeFrac, targetSlippage, feePpm, feePpm, false);
|
||||
int128 kappaDefault2 = LMSRStabilized.computeKappaFromSlippage(tokens.length, tradeFrac, targetSlippage);
|
||||
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
|
||||
token0.mint(address(this), INIT_BAL * 4);
|
||||
|
||||
Reference in New Issue
Block a user