Compare commits

2 Commits

Author SHA1 Message Date
tim
86410c9a91 burnSwap outputIndex/token naming fix 2025-10-28 12:17:01 -04:00
tim
5c5a000961 whitepaper rendering fix 2025-10-27 17:54:44 -04:00
6 changed files with 71 additions and 29 deletions

View File

@@ -8,78 +8,106 @@ Multi-asset liquidity typically trades off simplicity and expressivity. Classica
## System Model and Pricing Kernel ## 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 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. 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 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), 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. 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 ## Gradient, Price Shares, and Pairwise Prices
With $b$ treated as a constant parameter, the LMSR gradient recovers softmax shares 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}), \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 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). 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. 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 ## 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 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}. 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 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. \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 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). 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 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), 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. 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 ## 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 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), a_{\text{lim}} \;=\; b \,\ln\!\left(\frac{\Lambda}{r_0}\right),
$$ $$
and the output realized at that truncation is and the output realized at that truncation is
$$ $$
y_{\text{lim}} \;=\; b \,\ln\!\Big( 1 + r_0 \,\big(1 - r_0/\Lambda\big) \Big). 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, 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). 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. These limit and capacity branches ensure monotone, conservative behavior near domain edges.
## Liquidity Operations from the Same Potential ## 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 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), 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. 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.
### Single-Asset Mint and Redeem ### Single-Asset Mint and Redeem
#### Single-Asset Mint #### Single-Asset Mint
Given a deposit of amount $a>0$ of asset $i$, the pool targets a proportional growth factor $\alpha \ge 0$ so that the post-mint state can be rebalanced to $(1+\alpha)\,\mathbf{q}$ using fee-free kernel swaps from $i$ into each $j\ne i$. For each $j\ne i$, let $y_j := \alpha\,q_j$ and define $r_{0,j} := \exp\!\big((q_i - q_j)/b\big)$. The input required to realize $y_j$ via the exact-out inverse is Given a deposit of amount $a>0$ of asset $i$, the pool targets a proportional growth factor $\alpha \ge 0$ so that the post-mint state can be rebalanced to $(1+\alpha)\,\mathbf{q}$ using fee-free kernel swaps from $i$ into each $j\ne i$. For each $j\ne i$, let $y_j := \alpha\,q_j$ and define $r_{0,j} := \exp\!\big((q_i - q_j)/b\big)$. The input required to realize $y_j$ via the exact-out inverse is
$$ $$
x_j(\alpha) \;=\; b \,\ln\!\left(\frac{r_{0,j}}{\,r_{0,j} + 1 - e^{\,y_j/b}\,}\right), x_j(\alpha) \;=\; b \,\ln\!\left(\frac{r_{0,j}}{\,r_{0,j} + 1 - e^{\,y_j/b}\,}\right),
$$ $$
so the total required input for growth $\alpha$ is so the total required input for growth $\alpha$ is
$$ $$
a_{\text{req}}(\alpha) \;=\; \alpha\,q_i \;+\; \sum_{j\ne i} x_j(\alpha). a_{\text{req}}(\alpha) \;=\; \alpha\,q_i \;+\; \sum_{j\ne i} x_j(\alpha).
$$ $$
Properties and solver: Properties and solver:
- Monotonicity: $a_{\text{req}}(\alpha)$ is strictly increasing on its feasible domain, guaranteeing a unique solution. - Monotonicity: $a_{\text{req}}(\alpha)$ is strictly increasing on its feasible domain, guaranteeing a unique solution.
- Solver: bracket $\alpha$ (e.g., start from $\alpha\sim a/S$ and double until $a_{\text{req}}(\alpha)\ge a$ or a safety cap), then bisection to a small tolerance $\varepsilon$ (e.g., $\sim10^{-6}$ in fixed-point units). - Solver: bracket $\alpha$ (e.g., start from $\alpha\sim a/S$ and double until $a_{\text{req}}(\alpha)\ge a$ or a safety cap), then bisection to a small tolerance $\varepsilon$ (e.g., $\sim10^{-6}$ in fixed-point units).
@@ -91,28 +119,36 @@ Burning a proportional share $\alpha \in (0,1]$ returns a single asset $i$ by re
1) Form the local state after burn, $\mathbf{q}_{\text{local}}=(1-\alpha)\,\mathbf{q}$. 1) Form the local state after burn, $\mathbf{q}_{\text{local}}=(1-\alpha)\,\mathbf{q}$.
2) Start with the direct redemption $\alpha\,q_i$ in asset $i$. 2) Start with the direct redemption $\alpha\,q_i$ in asset $i$.
3) For each $j\ne i$, withdraw $a_j := \alpha\,q_j$ and swap $j\to i$ using the exact-in form evaluated at $\mathbf{q}_{\text{local}}$: 3) For each $j\ne i$, withdraw $a_j := \alpha\,q_j$ and swap $j\to i$ using the exact-in form evaluated at $\mathbf{q}_{\text{local}}$:
$$ $$
r_{0,j} \;=\; \exp\!\left(\frac{q^{\text{local}}_j - q^{\text{local}}_i}{b}\right),\qquad r_{0,j} \;=\; \exp\!\left(\frac{q^{\text{local}}_j - q^{\text{local}}_i}{b}\right),\qquad
y_{j\to i} \;=\; b \,\ln\!\Big(1 + r_{0,j}\,\big(1 - e^{-a_j/b}\big)\Big). y_{j\to i} \;=\; b \,\ln\!\Big(1 + r_{0,j}\,\big(1 - e^{-a_j/b}\big)\Big).
$$ $$
4) Capacity cap and inverse: if $y_{j\to i} > q^{\text{local}}_i$, cap to $y=q^{\text{local}}_i$ and solve the implied input via 4) Capacity cap and inverse: if $y_{j\to i} > q^{\text{local}}_i$, cap to $y=q^{\text{local}}_i$ and solve the implied input via
$$ $$
a_{j,\text{used}} \;=\; b \,\ln\!\left(\frac{r_{0,j}}{\,r_{0,j} + 1 - e^{\,q^{\text{local}}_i/b}\,}\right), a_{j,\text{used}} \;=\; b \,\ln\!\left(\frac{r_{0,j}}{\,r_{0,j} + 1 - e^{\,q^{\text{local}}_i/b}\,}\right),
$$ $$
then update $\mathbf{q}_{\text{local}}$ accordingly. then update $\mathbf{q}_{\text{local}}$ accordingly.
5) The single-asset payout is 5) The single-asset payout is
$$ $$
Y_i \;=\; \alpha\,q_i \;+\; \sum_{j\ne i} y_{j\to i}, \qquad \text{with LP burned } L_{\text{in}} = \alpha \, S(\mathbf{q}). Y_i \;=\; \alpha\,q_i \;+\; \sum_{j\ne i} y_{j\to i}, \qquad \text{with LP burned } L_{\text{in}} = \alpha \, S(\mathbf{q}).
$$ $$
Guards and behavior: Guards and behavior:
- Enforce $b>0$, positivity of inner terms (e.g., $r_{0,j} + 1 - e^{y/b} > 0$), and safe exponent ranges; treat any per-asset numerical failure as zero contribution rather than aborting the whole redeem. - Enforce $b>0$, positivity of inner terms (e.g., $r_{0,j} + 1 - e^{y/b} > 0$), and safe exponent ranges; treat any per-asset numerical failure as zero contribution rather than aborting the whole redeem.
- The mapping is monotone in $\alpha$; the cap-and-invert branch preserves safety near capacity. - The mapping is monotone in $\alpha$; the cap-and-invert branch preserves safety near capacity.
### LP Pricing vs. an Asset Token ### LP Pricing vs. an Asset Token
With LP supply set to $L=S(\mathbf{q})$, the instantaneous price of one LP share in units of asset $k$ aggregates marginal exchange rates from each asset into $k$: With LP supply set to $L=S(\mathbf{q})$, the instantaneous price of one LP share in units of asset $k$ aggregates marginal exchange rates from each asset into $k$:
$$ $$
P_L^{(k)}(\mathbf{q}) \;=\; \frac{1}{S(\mathbf{q})}\,\sum_{j=0}^{n-1} q_j \,\exp\!\left(\frac{q_j - q_k}{b(\mathbf{q})}\right). P_L^{(k)}(\mathbf{q}) \;=\; \frac{1}{S(\mathbf{q})}\,\sum_{j=0}^{n-1} q_j \,\exp\!\left(\frac{q_j - q_k}{b(\mathbf{q})}\right).
$$ $$
Interpretation: proportional deposits leave $P_L^{(k)}$ unchanged; swap fees retained in the pool increase $S$ relative to outstanding $L$, raising $P_L^{(k)}$ (implicit fee accrual). This expression helps LPs and integrators reason about share valuation and dilution across assets. Interpretation: proportional deposits leave $P_L^{(k)}$ unchanged; swap fees retained in the pool increase $S$ relative to outstanding $L$, raising $P_L^{(k)}$ (implicit fee accrual). This expression helps LPs and integrators reason about share valuation and dilution across assets.
## Numerical Methods and Safety Guarantees ## Numerical Methods and Safety Guarantees
@@ -120,13 +156,17 @@ We evaluate log-sum-exp with recentring, compute ratios like $r_0=\exp((q_i-q_j)
## Balanced Regime Optimization: Approximations, Dispatcher, and Stability ## Balanced Regime Optimization: Approximations, Dispatcher, and Stability
Since transcendental operations are gas-expensive on EVM chains, we use polynomial approximations in near-balanced regimes (e.g., stable-asset pairs) while preserving monotonicity and domain safety. Parameterize $\delta := (q_i - q_j)/b$ and $\tau := a/b$ for an $i\!\to\! j$ exact-in step. The exact mapping Since transcendental operations are gas-expensive on EVM chains, we use polynomial approximations in near-balanced regimes (e.g., stable-asset pairs) while preserving monotonicity and domain safety. Parameterize $\delta := (q_i - q_j)/b$ and $\tau := a/b$ for an $i\!\to\! j$ exact-in step. The exact mapping
$$ $$
y(a) \;=\; b \,\ln\!\Big(1 + e^{\delta}\,\big(1 - e^{-\tau}\big)\Big) y(a) \;=\; b \,\ln\!\Big(1 + e^{\delta}\,\big(1 - e^{-\tau}\big)\Big)
$$ $$
admits small-argument expansions for $|\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 admits small-argument expansions for $|\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 - \tfrac{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}, y(a) \;\approx\; b \left[ r_0 \tau - \tfrac{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$. and at $\delta=0$ the symmetry reduces to $y(a)\approx \tfrac{a}{2} - \tfrac{a^2}{4b} + \cdots$.
### Dispatcher preconditions and thresholds (approx path): ### Dispatcher preconditions and thresholds (approx path):
@@ -158,9 +198,11 @@ Under constant $b$, classical LMSR admits a worst-case loss bound of $b \ln n$ i
## Deployment and Parameter Fixity ## 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 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}), 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. 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 ## Conclusion

View File

@@ -230,19 +230,19 @@ interface IPartyPool is IERC20Metadata, IOwnable {
uint256 deadline uint256 deadline
) external payable returns (uint256 lpMinted); ) external payable returns (uint256 lpMinted);
/// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. /// @notice Burn LP tokens then swap the redeemed proportional basket into a single asset `outputTokenIndex` and send to receiver.
/// @dev The function burns LP tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state. /// @dev The function burns LP tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state.
/// @param payer who burns LP tokens /// @param payer who burns LP tokens
/// @param receiver who receives the single asset /// @param receiver who receives the single asset
/// @param lpAmount amount of LP tokens to burn /// @param lpAmount amount of LP tokens to burn
/// @param inputTokenIndex index of target asset to receive /// @param outputTokenIndex index of target asset to receive
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return amountOutUint uint amount of asset inputTokenIndex sent to receiver /// @return amountOutUint uint amount of asset outputTokenIndex sent to receiver
function burnSwap( function burnSwap(
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
uint256 inputTokenIndex, uint256 outputTokenIndex,
uint256 deadline, uint256 deadline,
bool unwrap bool unwrap
) external returns (uint256 amountOutUint); ) external returns (uint256 amountOutUint);

View File

@@ -52,8 +52,8 @@ interface IPartyPoolViewer {
/// @notice Calculate the amounts for a burn swap operation /// @notice Calculate the amounts for a burn swap operation
/// @dev This is a pure view function that computes burn swap amounts from provided state /// @dev This is a pure view function that computes burn swap amounts from provided state
/// @param lpAmount amount of LP _tokens to burn /// @param lpAmount amount of LP _tokens to burn
/// @param inputTokenIndex index of target asset to receive /// @param outputTokenIndex index of target asset to receive
function burnSwapAmounts(IPartyPool pool, uint256 lpAmount, uint256 inputTokenIndex) external view function burnSwapAmounts(IPartyPool pool, uint256 lpAmount, uint256 outputTokenIndex) external view
returns (uint256 amountOut); returns (uint256 amountOut);
/// @notice Compute repayment amounts (principal + flash fee) for a proposed flash loan. /// @notice Compute repayment amounts (principal + flash fee) for a proposed flash loan.

View File

@@ -407,14 +407,14 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
/// @param payer who burns LP _tokens /// @param payer who burns LP _tokens
/// @param receiver who receives the single asset /// @param receiver who receives the single asset
/// @param lpAmount amount of LP _tokens to burn /// @param lpAmount amount of LP _tokens to burn
/// @param inputTokenIndex index of target asset to receive /// @param outputTokenIndex index of target asset to receive
/// @param deadline optional deadline /// @param deadline optional deadline
/// @return amountOutUint uint amount of asset i sent to receiver /// @return amountOutUint uint amount of asset i sent to receiver
function burnSwap( function burnSwap(
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
uint256 inputTokenIndex, uint256 outputTokenIndex,
uint256 deadline, uint256 deadline,
bool unwrap bool unwrap
) external returns (uint256 amountOutUint) { ) external returns (uint256 amountOutUint) {
@@ -423,7 +423,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
payer, payer,
receiver, receiver,
lpAmount, lpAmount,
inputTokenIndex, outputTokenIndex,
deadline, deadline,
unwrap, unwrap,
SWAP_FEE_PPM, SWAP_FEE_PPM,

View File

@@ -441,7 +441,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @notice Calculate the amounts for a burn swap operation /// @notice Calculate the amounts for a burn swap operation
/// @dev This is a pure view function that computes burn swap amounts from provided state /// @dev This is a pure view function that computes burn swap amounts from provided state
/// @param lpAmount amount of LP _tokens to burn /// @param lpAmount amount of LP _tokens to burn
/// @param inputTokenIndex index of target asset to receive /// @param outputTokenIndex index of target asset to receive
/// @param swapFeePpm fee in parts-per-million /// @param swapFeePpm fee in parts-per-million
/// @param lmsrState current LMSR state /// @param lmsrState current LMSR state
/// @param bases_ scaling _bases for each token /// @param bases_ scaling _bases for each token
@@ -449,13 +449,13 @@ contract PartyPoolMintImpl is PartyPoolBase {
/// @return amountOut amount of target asset that would be received /// @return amountOut amount of target asset that would be received
function burnSwapAmounts( function burnSwapAmounts(
uint256 lpAmount, uint256 lpAmount,
uint256 inputTokenIndex, uint256 outputTokenIndex,
uint256 swapFeePpm, uint256 swapFeePpm,
LMSRStabilized.State memory lmsrState, LMSRStabilized.State memory lmsrState,
uint256[] memory bases_, uint256[] memory bases_,
uint256 totalSupply_ uint256 totalSupply_
) public pure returns (uint256 amountOut) { ) public pure returns (uint256 amountOut) {
require(inputTokenIndex < bases_.length, "burnSwapAmounts: idx"); require(outputTokenIndex < bases_.length, "burnSwapAmounts: idx");
require(lpAmount > 0, "burnSwapAmounts: zero lp"); require(lpAmount > 0, "burnSwapAmounts: zero lp");
require(totalSupply_ > 0, "burnSwapAmounts: empty supply"); require(totalSupply_ > 0, "burnSwapAmounts: empty supply");
@@ -465,21 +465,21 @@ contract PartyPoolMintImpl is PartyPoolBase {
// Use LMSR view to compute single-asset payout and burned size-metric // Use LMSR view to compute single-asset payout and burned size-metric
(int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal, (int128 payoutInternal, ) = LMSRStabilized.swapAmountsForBurn(lmsrState.nAssets, lmsrState.kappa, lmsrState.qInternal,
inputTokenIndex, alpha); outputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool // Convert payoutInternal -> uint (floor) to favor pool
amountOut = _internalToUintFloorPure(payoutInternal, bases_[inputTokenIndex]); amountOut = _internalToUintFloorPure(payoutInternal, bases_[outputTokenIndex]);
require(amountOut > 0, "burnSwapAmounts: output zero"); require(amountOut > 0, "burnSwapAmounts: output zero");
} }
/// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `inputTokenIndex` and send to receiver. /// @notice Burn LP _tokens then swap the redeemed proportional basket into a single asset `outputTokenIndex` and send to receiver.
/// This version of burn does not work if the vault has been killed, because it involves a swap. Use regular burn() /// This version of burn does not work if the vault has been killed, because it involves a swap. Use regular burn()
/// to recover funds if the pool has been killed. /// to recover funds if the pool has been killed.
/// @dev The function burns LP _tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state. /// @dev The function burns LP _tokens (authorization via allowance if needed), sends the single-asset payout and updates LMSR state.
/// @param payer who burns LP _tokens /// @param payer who burns LP _tokens
/// @param receiver who receives the single asset /// @param receiver who receives the single asset
/// @param lpAmount amount of LP _tokens to burn /// @param lpAmount amount of LP _tokens to burn
/// @param inputTokenIndex index of target asset to receive /// @param outputTokenIndex index of target asset to receive
/// @param deadline optional deadline /// @param deadline optional deadline
/// @param swapFeePpm fee in parts-per-million for this pool (may be used for future fee logic) /// @param swapFeePpm fee in parts-per-million for this pool (may be used for future fee logic)
/// @return amountOutUint uint amount of asset i sent to receiver /// @return amountOutUint uint amount of asset i sent to receiver
@@ -487,14 +487,14 @@ contract PartyPoolMintImpl is PartyPoolBase {
address payer, address payer,
address receiver, address receiver,
uint256 lpAmount, uint256 lpAmount,
uint256 inputTokenIndex, uint256 outputTokenIndex,
uint256 deadline, uint256 deadline,
bool unwrap, bool unwrap,
uint256 swapFeePpm, uint256 swapFeePpm,
uint256 protocolFeePpm uint256 protocolFeePpm
) external nonReentrant killable returns (uint256 amountOutUint) { ) external nonReentrant killable returns (uint256 amountOutUint) {
uint256 n = _tokens.length; uint256 n = _tokens.length;
require(inputTokenIndex < n, "burnSwap: idx"); require(outputTokenIndex < n, "burnSwap: idx");
require(lpAmount > 0, "burnSwap: zero lp"); require(lpAmount > 0, "burnSwap: zero lp");
require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline"); require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline");
@@ -506,16 +506,16 @@ contract PartyPoolMintImpl is PartyPoolBase {
.mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee .mul(ABDKMath64x64.divu(1000000-swapFeePpm, 1000000)); // adjusted for fee
// Use LMSR view to compute single-asset payout and burned size-metric // Use LMSR view to compute single-asset payout and burned size-metric
(int128 payoutInternal, ) = _lmsr.swapAmountsForBurn(inputTokenIndex, alpha); (int128 payoutInternal, ) = _lmsr.swapAmountsForBurn(outputTokenIndex, alpha);
// Convert payoutInternal -> uint (floor) to favor pool // Convert payoutInternal -> uint (floor) to favor pool
amountOutUint = _internalToUintFloor(payoutInternal, _bases[inputTokenIndex]); amountOutUint = _internalToUintFloor(payoutInternal, _bases[outputTokenIndex]);
require(amountOutUint > 0, "burnSwap: output zero"); require(amountOutUint > 0, "burnSwap: output zero");
// Compute gross payout (no swap fee) so we can determine token-side fee = gross - net // Compute gross payout (no swap fee) so we can determine token-side fee = gross - net
int128 alphaGross = ABDKMath64x64.divu(lpAmount, supply); // gross fraction (no swap fee) int128 alphaGross = ABDKMath64x64.divu(lpAmount, supply); // gross fraction (no swap fee)
(int128 payoutGrossInternal, ) = _lmsr.swapAmountsForBurn(inputTokenIndex, alphaGross); (int128 payoutGrossInternal, ) = _lmsr.swapAmountsForBurn(outputTokenIndex, alphaGross);
uint256 payoutGrossUint = _internalToUintFloor(payoutGrossInternal, _bases[inputTokenIndex]); uint256 payoutGrossUint = _internalToUintFloor(payoutGrossInternal, _bases[outputTokenIndex]);
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
@@ -523,13 +523,13 @@ contract PartyPoolMintImpl is PartyPoolBase {
if (protocolFeePpm > 0 && feeTokenUint > 0) { if (protocolFeePpm > 0 && feeTokenUint > 0) {
protoShare = (feeTokenUint * protocolFeePpm) / 1_000_000; protoShare = (feeTokenUint * protocolFeePpm) / 1_000_000;
if (protoShare > 0) { if (protoShare > 0) {
_protocolFeesOwed[inputTokenIndex] += protoShare; _protocolFeesOwed[outputTokenIndex] += protoShare;
} }
} }
// Transfer the payout to receiver via centralized helper // Transfer the payout to receiver via centralized helper
IERC20 inputToken = _tokens[inputTokenIndex]; IERC20 outputToken = _tokens[outputTokenIndex];
_sendTokenTo(inputToken, receiver, amountOutUint, unwrap); _sendTokenTo(outputToken, receiver, amountOutUint, unwrap);
// Burn LP _tokens from payer (authorization via allowance) // Burn LP _tokens from payer (authorization via allowance)
if (msg.sender != payer) { if (msg.sender != payer) {
@@ -542,7 +542,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
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 newBal = _cachedUintBalances[idx]; uint256 newBal = _cachedUintBalances[idx];
if (idx == inputTokenIndex) { if (idx == outputTokenIndex) {
// Effective LP balance decreases by net payout and increased protocol owed // Effective LP balance decreases by net payout and increased protocol owed
newBal = newBal - amountOutUint - protoShare; newBal = newBal - amountOutUint - protoShare;
} }
@@ -561,7 +561,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
_lmsr.updateForProportionalChange(newQInternal); _lmsr.updateForProportionalChange(newQInternal);
} }
emit IPartyPool.BurnSwap(payer, receiver, inputToken, lpAmount, amountOutUint, emit IPartyPool.BurnSwap(payer, receiver, outputToken, lpAmount, amountOutUint,
feeTokenUint-protoShare, protoShare); feeTokenUint-protoShare, protoShare);
return amountOutUint; return amountOutUint;

View File

@@ -123,12 +123,12 @@ contract PartyPoolViewer is PartyPoolHelpers, IPartyPoolViewer {
} }
function burnSwapAmounts(IPartyPool pool, uint256 lpAmount, uint256 inputTokenIndex) external view function burnSwapAmounts(IPartyPool pool, uint256 lpAmount, uint256 outputTokenIndex) external view
returns (uint256 amountOut) { returns (uint256 amountOut) {
LMSRStabilized.State memory lmsr = pool.LMSR(); LMSRStabilized.State memory lmsr = pool.LMSR();
return MINT_IMPL.burnSwapAmounts( return MINT_IMPL.burnSwapAmounts(
lpAmount, lpAmount,
inputTokenIndex, outputTokenIndex,
pool.swapFeePpm(), pool.swapFeePpm(),
lmsr, lmsr,
pool.denominators(), pool.denominators(),