AMM Technical Design
High Level
Perena is a stableswap AMM which pools a number of stablecoins together into a single multi-asset pool. This pool has properties of both a weighted pool and a StableSwap pool.
The multi-asset pool is the composition of multiple two asset pools, one per stablecoin, where each two asset pool has liquidity in the respective stablecoin, and liquidity in another asset *. * can be thought of as value, or as a numéraire, and codifies the concept of a single US dollar worth of value.
Initially each two asset pool is seeded with an equal amount of its stablecoin and of *, representative of the assumption that every stable trades 1:1 with *. Therefore initially all stables trade 1:1 with each other, through *. We only require that each pool has the same amount of * and its stable, but different pools can have different amount of stables. Hence the proportion of the multi-asset pool that is made up of each stable gives an equilibrium weight $w_i$ for each stablecoin, where $\sum_i w_i = 1$.
Consider our Seed Pool, containing 45% USDC, 35% USDT, and 20% PYUSD. Swaps between any pair of stables which pair to * can be implemented as two-hop swaps, first into *, then out of it. Thus the star graph of LPs this setup creates can be thought of itself as a single multi-asset pool. By restricting swaps to only be two hops (never terminate at *), the exchange rate of * to each token is in a way an oracle to the free market value of these stablecoins, without * itself being a holdable asset.
Properties of The AMM
Open market arbitrage
The exchange rate offered by our multi-asset pool (amount of token $y$ out over amount of token $x$ in) is$j$$\frac{a_x}{a_y} = \frac{a_x^}{a_y^}$, equaling the open market exchange rate, assuming rational arbitragers.
**Swaps preserve constant value of ***
The amount of * across the multi-asset pool is constant across swaps. * is only being moved across two asset pools, but never exiting the system.
Achieves value weights only at equilibrium
Let $\pi_x$ be the proportion of the multi-asset pool (ignoring *) that is made up of stablecoin $x$. The multi-asset pool has token proportions equal to the initial seeding weights ($\pi_x = w_x, \forall \text{ }x$), if and only if $\frac{a_x}{a_y} = 1, \forall\text{ }x, y$. Otherwise there is an arbitrage incentive where some stablecoin can be traded to get more than 1 of some other stablecoin.
💡 Intuitively the multi-asset pool is most capital efficient when the liquidity is distributed according to the initial weighting (the initial weighting is an anchor point for our CFMM). When this is not the case, the pool offers swaps towards equilibrium a premium.
Low Level
Two Asset Pool
Bonding Curve Invariant
Recall that good stableswap invariants balance a tradeoff between constant product $xy = k$ and constant sum $x+y=k$, trying to behave like the latter as much as possible while still charging a price impact as reserves deplete.
Our two asset pool bonding curve is based on the Wombat invariant (see page 5). Specifically given token amounts $x$ and $y$, we start with the invariant
where $A$ is an amplitude parameter to control how much we behave like constant product vs. constant sum. Note that we do not adopt the coverage ratio formulation of Wombat.
To seek more capital efficiency out of our invariant, we seek to bound the prices at which liquidity is concentrated. Let $p_x$ be the price of token $x$ in our pool, in terms of token $y$ ($p_x = \lim_{a_x \to 0}\frac{a_x}{a_y}$), where $a_y$ is the amount of token $y$ we get out for swapping in an infinitesimal amount $a_x$ of token $x$). We want
at all valid states under our invariant, where these price bounds are decided at pool creation time (ie. $\alpha=0.99, \beta=\frac1 {0.99})$.
To achieve this bounded StableSwap property, we borrow the virtual reserves concept of Gyroscope Finance’s CLP. We modify the invariant to be
where $a$ is a function of $A, k, \alpha$ and $b$ is a function of $A, k, \beta$. Unlike most CFMMs which satisfy output-boundedness (page 11) (no matter how much you swap, there is some of each asset left), we have removed this property, so at the price points $\alpha$ and $\beta$ the pool will be at max extent, containing all token $x$ and no token $y$, or all token $y$ and no token $x$, respectively. In these states the pool only supports swapping in one direction.
As it stands the amplitude parameter $A$ is not dimensionless, meaning it cannot be used to define bonding curve shape independent of the state of the pool liquidity ($A$ is different to give a pool with $x_0 = y_0 = 1000$ the same bonding curve shape as $x_0 = y_0 = 1000000$). To remedy this, we settle on our final invariant
where $L$ is the pool liquidity constant, thereby making $A$ dimensionless. By setting the liquidity constant of the right hand side to $2 - \frac{A}{1-a} - \frac{A}{1-b}$, we get the intuitive meaning of $L$ that it is the total value of each token in the pool. This is because initially $x=y=L$, making the left hand side equal the right hand side.
Multi-Asset Pool
Swaps
Consider a multi-token pool with $i$ pairs in states $x_i, y_i, L_i$. Assume token $y$ in two asset pool is always *, and token $x$ is always the stablecoin, as is the case in our smart contract implementation. Let $T=\sum_i y_i$ be the total number of * tokens in the multi-asset pool, which can be thought of as the pool TVL. Across swaps, $T$ is constant.
💡 Note that if the amount out is more than the reserve of the output asset, the swap is invalid. The maximum extent the pool can reach is all of one asset, none of another, at which point in only supports swapping in one direction.
Multi-Asset Pool Invariant
The pool was initially seeded in equilibrium state where $\frac {x_i}{T}= \frac{y_i}T = \frac{L_i}T = w_i, \forall i$. Call this state $E$, where all tokens trade 1:1 and all reserves are proportionate to the asset weighting of the pool. It serves as a constant function anchor point. Let $S$ be the state of the multi-asset pool (a set of two-asset pools, each between a different stablecoin and *). Then in all valid multi-asset states:
So there is always a sequence of two asset swaps, one per pool (some of which may be empty swaps) bringing the pool to the equilibrium state. Specifically the state where all tokens trade 1:1:…:1 (each two asset pair has equal amounts of token and star, $x_i = y_i = L_i$), and the pool has token balances proportionate to the token weights. Thinking of the composition of the two asset invariants under our *-preserving swap rule as itself a CFMM, the multi-asset pool invariant is that $E$ is an anchor point to our current state $S$.
We know that this invariant trivially holds initially, as we start in state $E$. Furthermore any sequence of swaps is reversible (each swap has an inverse swap undoing its action) and therefore the invariant holds across swaps.
Add Liquidity (Mint)
For ease of use, we allow for users to deposit liquidity in unbalanced amounts. Adding liquidity will inevitably change $T$, but should preserve the invariant of there existing a new equilibrium anchor point state $E'$ which we can get to under two asset swaps and which has $\frac {a'_x}{T'}= \frac{*'_x}{T'} = w_x, \forall x$. Liquidity add should also not change market price. To maintain this, we require the user add proportionate to the current state of the market. To support one sided adds, the user may provide a sequence of swaps they have precomputed to get them to hold balances proportionate to the market rates.
💡 An interesting and desirable property of the liquidity add is that it requires swapping the added asset into the others. Hence if the added asset is near the low end of its two-asset bonding curve (trading below peg), there may not be enough * in its pool to execute this swap (in which case the add fails), or the add may translate to a lesser addition *. Intuitively the pool by design disincentivizes (will give you less LP tokens) adding assets which are worth less.
Remove Liquidity (Redeem)
Redeeming liquidity entails computing the fraction of the pool that the user owns, removing it from the pool, and optionally swapping it into a single token.
Last updated
