removed tax coin support
This commit is contained in:
@@ -24,11 +24,14 @@ Naturally multi-asset, Liquidity Party altcoin pools provide direct, one-hop swa
|
|||||||
|
|
||||||
| Assets | Pairs | Swap Gas | Mint Gas |
|
| Assets | Pairs | Swap Gas | Mint Gas |
|
||||||
|-------:|------:|---------:|----------:|
|
|-------:|------:|---------:|----------:|
|
||||||
| 2 | 1 | 146,000 | 149,000 |
|
| 2 | 1 | 132,000 | 143,000 |
|
||||||
| 10 | 45 | 157,000 | 426,000 |
|
| 2* | 1 | 119,000 | 143,000 |
|
||||||
| 20 | 190 | 171,000 | 772,000 |
|
| 10 | 45 | 142,000 | 412,000 |
|
||||||
| 50 | 1225 | 213,000 | 1,810,000 |
|
| 20 | 190 | 157,000 | 749,000 |
|
||||||
| 100 | 4950 | 283,000 | 3,542,000 |
|
| 50 | 1225 | 199,000 | 1,760,000 |
|
||||||
|
| 100 | 4950 | 269,000 | 2,684,000 |
|
||||||
|
|
||||||
|
\* Stablecoin pair pool optimization
|
||||||
|
|
||||||
Liquidity Party aggregates scarce, low market cap assets into a single pool, providing one-hop liquidity for exotic pairs without fragmenting LP assets. CP pools would need 190x the LP assets to provide the same pairwise liquidity as a single 20-asset Liquidity Party pool, due to asset fragmentation.
|
Liquidity Party aggregates scarce, low market cap assets into a single pool, providing one-hop liquidity for exotic pairs without fragmenting LP assets. CP pools would need 190x the LP assets to provide the same pairwise liquidity as a single 20-asset Liquidity Party pool, due to asset fragmentation.
|
||||||
|
|
||||||
|
|||||||
100
doc/whitepaper2.md
Normal file
100
doc/whitepaper2.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# LMSR-based Multi-Asset AMM
|
||||||
|
|
||||||
|
Abstract
|
||||||
|
We present a multi-asset automated market maker whose pricing kernel is the Logarithmic Market Scoring Rule (LMSR). The pool maintains the convex potential $C(\mathbf{q}) = b(\mathbf{q}) \log\!\Big(\sum_i e^{q_i / b(\mathbf{q})}\Big)$ over normalized inventories $\mathbf{q}$, and sets the effective liquidity parameter proportional to pool size as $b(\mathbf{q}) = \kappa \, S(\mathbf{q})$ with $S(\mathbf{q}) = \sum_i q_i$ and fixed $\kappa>0$. This proportional parameterization preserves scale-invariant responsiveness while retaining softmax-derived pairwise price ratios under a quasi-static-$b$ view, enabling any-to-any swaps within a single potential. We derive and use closed-form expressions for two-asset reductions to compute exact-in, exact-out, limit-hitting (swap-to-limit), and capped-output trades. We discuss stability techniques such as log-sum-exp, ratio-once shortcuts, and domain guards for fixed-point arithmetic. Liquidity operations (proportional and single-asset joins/exits) follow directly from the same potential and admit monotone, invertible mappings. Parameters are immutable post-deployment for transparency and predictable depth calibration.
|
||||||
|
|
||||||
|
Introduction and Motivation
|
||||||
|
Multi-asset liquidity typically trades off simplicity and expressivity. Classical CFMMs define multiplicative invariants over reserves, while LMSR specifies a convex cost function whose gradient yields prices. Our goal is a multi-asset AMM that uses LMSR to support any-to-any swaps, shares risk across many assets, and scales depth predictably with pool size. By setting $b(\mathbf{q})=\kappa S(\mathbf{q})$, we achieve scale invariance: proportional rescaling of all balances scales $b$ proportionally and preserves pairwise price ratios, so the market’s responsiveness is consistent across liquidity regimes. The derivations below formulate instantaneous prices, closed-form swap mappings, limit logic, and liquidity operations tailored to this parameterization.
|
||||||
|
|
||||||
|
System Model and Pricing Kernel
|
||||||
|
We consider $n\ge 2$ normalized assets with state vector $\mathbf{q}=(q_0,\dots,q_{n-1})\in\mathbb{R}_{\ge 0}^{\,n}$ and size metric $S(\mathbf{q})=\sum_i q_i$. The kernel is the LMSR cost function
|
||||||
|
$$
|
||||||
|
C(\mathbf{q}) = b(\mathbf{q}) \log\!\left(\sum_{i=0}^{n-1} e^{q_i / b(\mathbf{q})}\right), \qquad b(\mathbf{q})=\kappa\,S(\mathbf{q}),\quad \kappa>0.
|
||||||
|
$$
|
||||||
|
For numerical stability we evaluate $C$ with a log-sum-exp recentering. Let $y_i := q_i/b(\mathbf{q})$ and $M:=\max_i y_i$. Then
|
||||||
|
$$
|
||||||
|
C(\mathbf{q}) \;=\; b(\mathbf{q}) \left( M + \log \sum_{i=0}^{n-1} e^{\,y_i - M} \right),
|
||||||
|
$$
|
||||||
|
which prevents overflow/underflow when the $y_i$ are dispersed. Quantities are represented in fixed-point with explicit range and domain guards; equations are presented over the reals for clarity.
|
||||||
|
|
||||||
|
Gradient, Price Shares, and Pairwise Prices
|
||||||
|
With $b$ treated as a constant parameter, the LMSR gradient recovers softmax shares
|
||||||
|
$$
|
||||||
|
\frac{\partial C}{\partial q_i} \;=\; \frac{e^{q_i/b}}{\sum_k e^{q_k/b}} \;=:\; \pi_i(\mathbf{q}),
|
||||||
|
$$
|
||||||
|
so that the ratio of marginal prices is $\pi_j/\pi_i = \exp\!\big((q_j-q_i)/b\big)$. When $b(\mathbf{q})=\kappa S(\mathbf{q})$ depends on state, $\frac{\partial C}{\partial q_i}$ acquires a common additive term across $i$ from $\partial b/\partial q_i$, but pairwise ratios remain governed by softmax differences. We therefore use a quasi-static-$b$ view for pricing steps, holding $b$ fixed at the pre-trade state for the infinitesimal move, and define the instantaneous pairwise marginal price ratio for exchanging $i$ into $j$ as
|
||||||
|
$$
|
||||||
|
P(i\to j \mid \mathbf{q}) \;=\; \exp\!\left(\frac{q_j - q_i}{b(\mathbf{q})}\right).
|
||||||
|
$$
|
||||||
|
This ratio drives swap computations and is invariant to proportional rescaling $\mathbf{q}\mapsto \lambda\mathbf{q}$ because $b$ scales by the same factor.
|
||||||
|
|
||||||
|
Two-Asset Reduction and Exact Swap Mappings
|
||||||
|
Swaps are computed in the two-asset subspace spanned by the in-asset $i$ and out-asset $j$, with all other coordinates held fixed under a quasi-static-$b$ step. Let
|
||||||
|
$$
|
||||||
|
r_0 \;:=\; \exp\!\left(\frac{q_i - q_j}{b}\right), \qquad b \equiv b(\mathbf{q})\;\text{ held quasi-static}.
|
||||||
|
$$
|
||||||
|
Along the $i\!\to\! j$ path, the instantaneous ratio evolves multiplicatively as $r(t)=r_0\,e^{t/b}$ where $t$ denotes cumulative input of asset $i$. In the two-asset reduction the infinitesimal output satisfies
|
||||||
|
$$
|
||||||
|
\mathrm{d}y \;=\; \frac{r(t)}{1+r(t)}\,\mathrm{d}t.
|
||||||
|
$$
|
||||||
|
Integrating from $t=0$ to $t=a$ yields the exact-in closed form
|
||||||
|
$$
|
||||||
|
y(a) \;=\; b \,\ln\!\Big( 1 + r_0 \,\big(1 - e^{-a/b}\big) \Big).
|
||||||
|
$$
|
||||||
|
This mapping has $y(0)=0$, is strictly increasing and concave in $a$, and satisfies $y'(0)=\frac{r_0}{1+r_0}$ with asymptote $\lim_{a\to\infty} y = b\,\ln(1+r_0)$. The inverse exact-out mapping follows by solving for $a$ in terms of target $y$. Writing $E:=e^{y/b}$, we obtain
|
||||||
|
$$
|
||||||
|
a(y) \;=\; b \,\ln\!\left(\frac{r_0}{\,r_0 + 1 - E\,}\right),
|
||||||
|
$$
|
||||||
|
which is strictly increasing and convex for $y\in\big[0,\, b\ln(1+r_0)\big]$. These two expressions are the workhorses for exact-in and exact-out swaps in our kernel.
|
||||||
|
|
||||||
|
Price Limits, Swap-to-Limit, and Capacity Caps
|
||||||
|
Users may provide a maximum acceptable marginal price ratio $\Lambda>0$ for $p_i/p_j$. The marginal ratio trajectory $r(t)=r_0 e^{t/b}$ first reaches the limit at the unique
|
||||||
|
$$
|
||||||
|
a_{\text{lim}} \;=\; b \,\ln\!\left(\frac{\Lambda}{r_0}\right),
|
||||||
|
$$
|
||||||
|
and the output realized at that truncation is
|
||||||
|
$$
|
||||||
|
y_{\text{lim}} \;=\; b \,\ln\!\Big( 1 + r_0 \,\big(1 - r_0/\Lambda\big) \Big).
|
||||||
|
$$
|
||||||
|
Outputs are further bounded by available inventory; if a computed $y$ would exceed $q_j$, we cap at $y=q_j$ and compute the implied input by inverting the exact-out formula,
|
||||||
|
$$
|
||||||
|
a_{\text{cap}} \;=\; b \,\ln\!\left(\frac{r_0}{\,r_0 + 1 - e^{\,q_j/b}\,}\right).
|
||||||
|
$$
|
||||||
|
These limit and capacity branches ensure monotone, conservative behavior near domain edges.
|
||||||
|
|
||||||
|
Liquidity Operations from the Same Potential
|
||||||
|
Liquidity is accounted via pool shares $L$ taken proportional to the size metric, and we set $L=S(\mathbf{q})$ without loss of generality. At initialization with seed balances $\mathbf{q}^{(0)}$ the pool sets $L^{(0)}=S^{(0)}$ and $b^{(0)}=\kappa S^{(0)}$. A proportional deposit that scales balances to $\mathbf{q}'=(1+\alpha)\mathbf{q}$ mints $\Delta L = \alpha S(\mathbf{q})$ shares and scales liquidity to $b'=(1+\alpha)b$. Single-asset deposits target a proportional growth while rebalancing through kernel swaps: providing amount $a$ of asset $i$ induces a growth factor $\alpha\ge 0$ satisfying the monotone equation
|
||||||
|
$$
|
||||||
|
a \;=\; a_{\text{req}}(\alpha) \;=\; \alpha q_i \;+\; \sum_{j\ne i} b \,\ln\!\left(\frac{r_{0,j}}{\,r_{0,j} + 1 - e^{\,\alpha q_j/b}\,}\right), \quad r_{0,j}:=\exp\!\left(\frac{q_i-q_j}{b}\right),
|
||||||
|
$$
|
||||||
|
and mints $\Delta L=\alpha S(\mathbf{q})$ upon the unique solution. Proportional withdrawals burn $\Delta L$ and return $\alpha=\Delta L/S(\mathbf{q})$ of each asset, updating $b$ to $(1-\alpha)b$. Single-asset withdrawals redeem $\alpha q_i$ directly and swap each redeemed $\alpha q_j$ for $j\ne i$ into $i$ using the exact-in mapping evaluated on the local post-burn state; any capacity overrun is handled by a cap-and-invert branch as above. Because all operations reduce to the same two-asset closed forms, they inherit monotonicity and uniqueness.
|
||||||
|
|
||||||
|
Fees and Economic Considerations
|
||||||
|
Swap fees are applied outside the fee-free kernel. For an exact-in submission $a$ on asset $i$, the effective kernel input is $a_{\text{eff}}=(1-f_{\text{swap}})a$. The kernel computes output using $a_{\text{eff}}$, while the retained fee remains in the pool, increasing $S(\mathbf{q})$ relative to outstanding $L$ and thereby accruing value to LPs implicitly. Scale invariance under $b=\kappa S$ implies that proportional growth of inventories preserves price ratios while deepening notional liquidity linearly. The classical LMSR bounded-loss intuition for constant $b$ gives $b\ln n$ in appropriate units; under our proportional $b$, this scales with $S$, so the instantaneous bound per unit of $S$ is proportional to $\kappa\ln n$.
|
||||||
|
|
||||||
|
Balanced Regime, Approximations, and Stability
|
||||||
|
Near balance it is useful to parameterize $\delta := (q_i - q_j)/b$ and $\tau := a/b$. The exact mapping
|
||||||
|
$$
|
||||||
|
y(a) \;=\; b \,\ln\!\Big(1 + e^{\delta}\,\big(1 - e^{-\tau}\big)\Big)
|
||||||
|
$$
|
||||||
|
admits small-argument expansions when $|\delta|\ll 1$ and $|\tau|\ll 1$. Using $e^{\pm x}\approx 1\pm x+\tfrac{x^2}{2}$ and $\ln(1+u)\approx u - \tfrac{u^2}{2}$, we obtain
|
||||||
|
$$
|
||||||
|
y(a) \;\approx\; b \left[ r_0 \tau - \frac{1}{2} r_0 \tau^2 \right] + \mathcal{O}\!\left(\tau^3,\, |\delta|\,\tau^2\right), \qquad r_0=e^{\delta}\approx 1+\delta+\tfrac{\delta^2}{2},
|
||||||
|
$$
|
||||||
|
and at $\delta=0$ the symmetry reduces to $y(a)\approx \tfrac{a}{2} - \tfrac{a^2}{4b} + \cdots$. In designated near-balance domains one may replace $\exp$ and $\ln$ with verified polynomial approximations to reduce computational cost while maintaining monotonicity and bounded error; a dispatcher enforces preconditions on $|\delta|$, $|a|/b$, and positivity of inner arguments, otherwise falling back to the exact forms. Regardless of path, our numerical policy prioritizes monotonicity, domain safety, and conservative branches at decision boundaries.
|
||||||
|
|
||||||
|
Numerical Methods and Safety Guarantees
|
||||||
|
We evaluate log-sum-exp with recentring, compute ratios like $r_0=\exp((q_i-q_j)/b)$ directly rather than dividing exponentials, and guard all $\exp$ and $\ln$ calls to bounded domains with explicit checks on positivity of inner terms such as $r_0+1-e^{y/b}$. Fixed-point implementations precompute reciprocals like $1/b$ to reduce dispersion, clamp to capacity before inversion, and select cap-and-invert rather than extrapolating when inner terms approach zero. These measures ensure the swap maps remain strictly order-preserving and free of nonphysical outputs. Property-based and differential testing can confirm monotonicity of $y(a)$ and $a(y)$, uniqueness of limit hits when $\Lambda>r_0$, and adherence to predefined error budgets.
|
||||||
|
|
||||||
|
Deployment and Parameter Fixity
|
||||||
|
The parameter tuple $(\kappa, f_{\text{swap}}, \phi)$ is set at deployment and remains immutable, with $\kappa>0$ defining $b(\mathbf{q})=\kappa S(\mathbf{q})$, $f_{\text{swap}}$ the swap fee rate, and $\phi$ the protocol share of fees. Given the initial state $\mathbf{q}^{(0)}$ with $S^{(0)}>0$, the induced pricing map is fully determined by
|
||||||
|
$$
|
||||||
|
C(\mathbf{q}) = b(\mathbf{q}) \log\!\left(\sum_i e^{q_i / b(\mathbf{q})}\right), \qquad b(\mathbf{q})=\kappa S(\mathbf{q}),
|
||||||
|
$$
|
||||||
|
and the two-asset closed forms above. Fixity eliminates governance risk, makes depth calibration transparent, and simplifies integration for external routers and valuation tools.
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
By coupling LMSR with the proportional parameterization $b(\mathbf{q})=\kappa S(\mathbf{q})$, we obtain a multi-asset AMM that preserves softmax-driven price ratios under a quasi-static-$b$ view and supports any-to-any swaps via a single convex potential. Exact two-asset reductions yield closed-form mappings for exact-in, exact-out, limit-hitting, and capped-output trades, and the same formulas underpin liquidity operations with monotonicity and uniqueness. Numerical stability follows from log-sum-exp evaluation, ratio-first derivations, guarded transcendental domains, and optional near-balance approximations, while fixed parameters provide predictable scaling and transparent economics.
|
||||||
|
|
||||||
|
References
|
||||||
|
Hanson, R. (2002). Logarithmic Market Scoring Rules for Modular Combinatorial Information Aggregation. https://mason.gmu.edu/~rhanson/mktscore.pdf
|
||||||
@@ -9,7 +9,7 @@ remappings = [
|
|||||||
optimizer=true
|
optimizer=true
|
||||||
optimizer_runs=999999999
|
optimizer_runs=999999999
|
||||||
viaIR=true
|
viaIR=true
|
||||||
gas_reports = ['PartyPool', 'PartyPlanner', 'PartyPoolSwapImpl', 'PartyPoolMintImpl',]
|
gas_reports = ['PartyPool', 'PartyPoolBalancedPair', 'PartyPlanner', 'PartyPoolSwapImpl', 'PartyPoolMintImpl',]
|
||||||
fs_permissions = [{ access = "write", path = "chain.json"}]
|
fs_permissions = [{ access = "write", path = "chain.json"}]
|
||||||
|
|
||||||
[lint]
|
[lint]
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ contract PartyPlanner is IPartyPlanner {
|
|||||||
for (uint256 i = 0; i < _tokens.length; i++) {
|
for (uint256 i = 0; i < _tokens.length; i++) {
|
||||||
if (initialDeposits[i] > 0) {
|
if (initialDeposits[i] > 0) {
|
||||||
IERC20(_tokens[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]);
|
IERC20(_tokens[i]).safeTransferFrom(payer, address(pool), initialDeposits[i]);
|
||||||
|
require(IERC20(_tokens[i]).balanceOf(address(pool)) == initialDeposits[i], 'fee-on-transfer tokens not supported');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,12 +87,12 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
unchecked { i++; }
|
unchecked { i++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cached balances for all assets
|
// Update cached balances and internal q for all assets using depositAmounts
|
||||||
int128[] memory newQInternal = new int128[](n);
|
int128[] memory newQInternal = new int128[](n);
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
uint256 newBal = cachedUintBalances[i] + depositAmounts[i];
|
||||||
cachedUintBalances[i] = bal;
|
cachedUintBalances[i] = newBal;
|
||||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
newQInternal[i] = _uintToInternalFloor(newBal, bases[i]);
|
||||||
unchecked { i++; }
|
unchecked { i++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,6 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
uint256 newScaled = ABDKMath64x64.mulu(newTotal, LP_SCALE);
|
||||||
uint256 actualLpToMint;
|
uint256 actualLpToMint;
|
||||||
|
|
||||||
require(oldScaled > 0, "mint: oldScaled zero");
|
|
||||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||||
// Proportional issuance: totalSupply * delta / oldScaled
|
// Proportional issuance: totalSupply * delta / oldScaled
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
@@ -143,15 +142,8 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
uint256 supply = _totalSupply;
|
uint256 supply = _totalSupply;
|
||||||
require(supply > 0, "burn: empty supply");
|
require(supply > 0, "burn: empty supply");
|
||||||
require(lmsr.nAssets > 0, "burn: uninit pool");
|
|
||||||
require(_balances[payer] >= lpAmount, "burn: insufficient LP");
|
|
||||||
|
|
||||||
// Refresh cached balances to reflect current on-chain balances before computing withdrawal amounts
|
// Use cached balances; assume standard ERC20 transfers without external interference
|
||||||
for (uint i = 0; i < n; ) {
|
|
||||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
|
||||||
cachedUintBalances[i] = bal;
|
|
||||||
unchecked { i++; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
||||||
withdrawAmounts = burnAmounts(lpAmount, lmsr.nAssets, _totalSupply, cachedUintBalances);
|
withdrawAmounts = burnAmounts(lpAmount, lmsr.nAssets, _totalSupply, cachedUintBalances);
|
||||||
@@ -164,12 +156,12 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
unchecked { i++; }
|
unchecked { i++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cached balances and internal q for all assets
|
// Update cached balances and internal q for all assets using computed withdrawals
|
||||||
int128[] memory newQInternal = new int128[](n);
|
int128[] memory newQInternal = new int128[](n);
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
uint256 bal = IERC20(tokens[i]).balanceOf(address(this));
|
uint256 newBal = cachedUintBalances[i] - withdrawAmounts[i];
|
||||||
cachedUintBalances[i] = bal;
|
cachedUintBalances[i] = newBal;
|
||||||
newQInternal[i] = _uintToInternalFloor(bal, bases[i]);
|
newQInternal[i] = _uintToInternalFloor(newBal, bases[i]);
|
||||||
unchecked { i++; }
|
unchecked { i++; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +184,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
// Burn exactly the requested LP amount from payer (authorization via allowance)
|
// Burn exactly the requested LP amount from payer (authorization via allowance)
|
||||||
if (msg.sender != payer) {
|
if (msg.sender != payer) {
|
||||||
uint256 allowed = _allowances[payer][msg.sender];
|
uint256 allowed = _allowances[payer][msg.sender];
|
||||||
require(allowed >= lpAmount, "burn: allowance insufficient");
|
// Rely on Solidity's checked arithmetic to revert on underflow if allowance is insufficient
|
||||||
_approve(payer, msg.sender, allowed - lpAmount);
|
_approve(payer, msg.sender, allowed - lpAmount);
|
||||||
}
|
}
|
||||||
_burn(payer, lpAmount);
|
_burn(payer, lpAmount);
|
||||||
@@ -374,25 +366,22 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
uint256 totalTransfer = amountInUint + feeUintActual;
|
uint256 totalTransfer = amountInUint + feeUintActual;
|
||||||
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
|
require(totalTransfer > 0 && totalTransfer <= maxAmountIn, "swapMint: transfer exceeds max");
|
||||||
|
|
||||||
// Record pre-balance and transfer tokens from payer, require exact receipt (revert on fee-on-transfer)
|
// Transfer tokens from payer (assume standard ERC20 without transfer fees)
|
||||||
uint256 prevBalI = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
||||||
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransfer);
|
tokens[inputTokenIndex].safeTransferFrom(payer, address(this), totalTransfer);
|
||||||
uint256 balIAfter = IERC20(tokens[inputTokenIndex]).balanceOf(address(this));
|
|
||||||
require(balIAfter == prevBalI + totalTransfer, "swapMint: non-standard tokenIn");
|
|
||||||
|
|
||||||
// Accrue protocol share (floor) from the fee on the input token
|
// Accrue protocol share (floor) from the fee on the input token
|
||||||
|
uint256 protoShare = 0;
|
||||||
if (protocolFeePpm > 0 && feeUintActual > 0) {
|
if (protocolFeePpm > 0 && feeUintActual > 0) {
|
||||||
uint256 protoShare = (feeUintActual * protocolFeePpm) / 1_000_000;
|
protoShare = (feeUintActual * protocolFeePpm) / 1_000_000;
|
||||||
if (protoShare > 0) {
|
if (protoShare > 0) {
|
||||||
protocolFeesOwed[inputTokenIndex] += protoShare;
|
protocolFeesOwed[inputTokenIndex] += protoShare;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Update cached balance for the input token to effective onchain - owed
|
// Update cached effective balance directly: add totalTransfer minus protocol share
|
||||||
_recordCachedBalance(inputTokenIndex, balIAfter);
|
cachedUintBalances[inputTokenIndex] += (totalTransfer - protoShare);
|
||||||
|
|
||||||
// Compute old and new scaled size metrics to determine LP minted
|
// Compute old and new scaled size metrics to determine LP minted
|
||||||
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
int128 oldTotal = _computeSizeMetric(lmsr.qInternal);
|
||||||
require(oldTotal > int128(0), "swapMint: zero total");
|
|
||||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||||
|
|
||||||
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
int128 newTotal = oldTotal.add(sizeIncreaseInternal);
|
||||||
@@ -405,7 +394,6 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
||||||
actualLpToMint = newScaled;
|
actualLpToMint = newScaled;
|
||||||
} else {
|
} else {
|
||||||
require(oldScaled > 0, "swapMint: oldScaled zero");
|
|
||||||
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
uint256 delta = (newScaled > oldScaled) ? (newScaled - oldScaled) : 0;
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
// floor truncation rounds in favor of pool
|
// floor truncation rounds in favor of pool
|
||||||
@@ -499,7 +487,6 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
uint256 supply = _totalSupply;
|
uint256 supply = _totalSupply;
|
||||||
require(supply > 0, "burnSwap: empty supply");
|
require(supply > 0, "burnSwap: empty supply");
|
||||||
require(_balances[payer] >= lpAmount, "burnSwap: insufficient LP");
|
|
||||||
|
|
||||||
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
|
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
|
||||||
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
|
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
|
||||||
@@ -519,8 +506,9 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
uint256 feeTokenUint = (payoutGrossUint > amountOutUint) ? (payoutGrossUint - amountOutUint) : 0;
|
uint256 feeTokenUint = (payoutGrossUint > amountOutUint) ? (payoutGrossUint - amountOutUint) : 0;
|
||||||
|
|
||||||
// Accrue protocol share (floor) from the token-side fee
|
// Accrue protocol share (floor) from the token-side fee
|
||||||
|
uint256 protoShare = 0;
|
||||||
if (protocolFeePpm > 0 && feeTokenUint > 0) {
|
if (protocolFeePpm > 0 && feeTokenUint > 0) {
|
||||||
uint256 protoShare = (feeTokenUint * protocolFeePpm) / 1_000_000;
|
protoShare = (feeTokenUint * protocolFeePpm) / 1_000_000;
|
||||||
if (protoShare > 0) {
|
if (protoShare > 0) {
|
||||||
protocolFeesOwed[inputTokenIndex] += protoShare;
|
protocolFeesOwed[inputTokenIndex] += protoShare;
|
||||||
}
|
}
|
||||||
@@ -532,18 +520,20 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
// Burn LP tokens from payer (authorization via allowance)
|
// Burn LP tokens from payer (authorization via allowance)
|
||||||
if (msg.sender != payer) {
|
if (msg.sender != payer) {
|
||||||
uint256 allowed = _allowances[payer][msg.sender];
|
uint256 allowed = _allowances[payer][msg.sender];
|
||||||
require(allowed >= lpAmount, "burnSwap: allowance insufficient");
|
|
||||||
_approve(payer, msg.sender, allowed - lpAmount);
|
_approve(payer, msg.sender, allowed - lpAmount);
|
||||||
}
|
}
|
||||||
_burn(payer, lpAmount);
|
_burn(payer, lpAmount);
|
||||||
|
|
||||||
// Update cached balances by reading on-chain balances for all tokens
|
// Update cached balances using computed payout and protocol fee; no on-chain reads
|
||||||
int128[] memory newQInternal = new int128[](n);
|
int128[] memory newQInternal = new int128[](n);
|
||||||
for (uint256 idx = 0; idx < n; idx++) {
|
for (uint256 idx = 0; idx < n; idx++) {
|
||||||
uint256 bal = IERC20(tokens[idx]).balanceOf(address(this));
|
uint256 newBal = cachedUintBalances[idx];
|
||||||
cachedUintBalances[idx] = bal;
|
if (idx == inputTokenIndex) {
|
||||||
_recordCachedBalance(inputTokenIndex, bal);
|
// Effective LP balance decreases by net payout and increased protocol owed
|
||||||
newQInternal[idx] = _uintToInternalFloor(bal, bases[idx]);
|
newBal = newBal - amountOutUint - protoShare;
|
||||||
|
}
|
||||||
|
cachedUintBalances[idx] = newBal;
|
||||||
|
newQInternal[idx] = _uintToInternalFloor(newBal, bases[idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit BurnSwap with public-facing info only (do not expose ΔS or LP burned)
|
// Emit BurnSwap with public-facing info only (do not expose ΔS or LP burned)
|
||||||
|
|||||||
Reference in New Issue
Block a user