-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* docs: improve module docs * docs: added oracle pool mechanism section and improve stable stake doc to describe (un)bond flows --------- Co-authored-by: Amit Yadav <[email protected]>
- Loading branch information
1 parent
9124b75
commit 9321c01
Showing
34 changed files
with
1,181 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<!-- | ||
order: 2 | ||
--> | ||
|
||
# Mechanism | ||
|
||
The Elys Network AMM module implements an Automated Market Maker (AMM) using various types of liquidity pools. It supports the following types of pools: | ||
|
||
1. **AMM Pools**: Non-oracle pools with liquidity centered around a specific spot price, designed for assets with significant price variation. | ||
2. **Oracle Pools**: Oracle pools with liquidity centered around an oracle price, designed for assets with stable prices. | ||
|
||
This section explains the fundamental mechanism of the AMM module and provides an overview of how the module's code is structured to support both types of pools. | ||
|
||
The section **[Oracle Pools](03_oracle_pools.md)** provides additional details on the mechanism of Oracle Pools. | ||
|
||
## Pool | ||
|
||
### Creation of Pool | ||
|
||
When a new pool is created, a fixed amount of 100 share tokens is minted and sent to the pool creator's account. The pool share token denomination follows the format `amm/pool/{poolID}`. By default, pool assets are sorted in alphabetical order. Pool creation requires a minimum of 2 and a maximum of 8 asset denominations. | ||
|
||
A `PoolCreationFee` must be paid to create a pool, which helps prevent the creation of unnecessary or malicious pools. | ||
|
||
### Joining Pool | ||
|
||
Users can join a pool without swapping assets by using the `JoinPool` function. In this case, they provide the maximum amount of tokens (`TokenInMaxs`) they are willing to deposit. This parameter must include all the asset denominations in the pool, or none at all, otherwise, the transaction will be aborted. If no tokens are specified, the user's balance is used as the constraint. | ||
|
||
The front end calculates the number of share tokens the user is eligible for at the moment the transaction is sent. The exact calculation of tokens required for the designated share is performed during transaction processing, ensuring the deposit does not exceed the maximum specified. Once validated, the appropriate number of pool share tokens are minted and sent to the user's account. | ||
|
||
#### Existing Join Types | ||
|
||
- `JoinPool` | ||
|
||
### Exiting Pool | ||
|
||
To exit a pool, users specify the minimum amount of tokens they are willing to receive in return for their pool shares. Unlike joining, exiting a pool incurs an exit fee set as a pool parameter. The user's share tokens are burned in the process. Exiting with a single asset is also possible. | ||
|
||
Exiting is only allowed if the user will leave a positive balance for a certain denomination or a positive number of LP shares. Transactions that would completely drain a pool are aborted. | ||
|
||
Exiting with a swap requires paying both exit and swap fees. | ||
|
||
#### Existing Exit Types | ||
|
||
- `ExitPool` | ||
|
||
### Swap | ||
|
||
When swapping assets within the pool, the input token is referred to as `tokenIn` and the output token as `tokenOut`. The module uses the following formula to calculate the number of tokens exchanged: | ||
|
||
``` | ||
tokenBalanceOut * [1 - { tokenBalanceIn / (tokenBalanceIn + (1 - swapFee) * tokenAmountIn)} ^ (tokenWeightIn / tokenWeightOut)] | ||
``` | ||
|
||
To reverse the calculation (i.e., given `tokenOut`), the following formula is used: | ||
|
||
``` | ||
tokenBalanceIn * [{tokenBalanceOut / (tokenBalanceOut - tokenAmountOut)} ^ (tokenWeightOut / tokenWeightIn) - 1] / tokenAmountIn | ||
``` | ||
|
||
#### Existing Swap Types | ||
|
||
- `SwapExactAmountIn` | ||
- `SwapExactAmountOut` | ||
|
||
### Spot Price | ||
|
||
The spot price, inclusive of the swap fee, is calculated using the formula: | ||
|
||
``` | ||
spotPrice / (1 - swapFee) | ||
``` | ||
|
||
Where `spotPrice` is defined as: | ||
|
||
``` | ||
(tokenBalanceIn / tokenWeightIn) / (tokenBalanceOut / tokenWeightOut) | ||
``` | ||
|
||
### Multi-Hop | ||
|
||
Multi-hop logic is supported by the AMM module, allowing users to swap between multiple pools in a single transaction. The module calculates the optimal path for the swap, taking into account the swap fee and the spot price of each pool. | ||
|
||
## Weights | ||
|
||
Weights determine the distribution of assets within a pool. They are often expressed as ratios. For example, a 1:1 pool between "ETH" and "BTC" has a spot price of `#ETH in pool / #BTC in pool`. A 2:1 pool has a spot price of `2 * (#ETH in pool) / #BTC in pool`, which means fewer ETH reserves are required to maintain the same spot price, though it increases slippage. | ||
|
||
Internally, weights are represented as numbers, with ratios computed as needed. Pools can be defined with weights up to `2^20`, and weight changes are managed with 30 bits of precision for smooth transitions. | ||
|
||
## Network Parameters | ||
|
||
Pools have the following configurable parameters: | ||
|
||
| Key | Type | | ||
| ------------------------ | -------------------------- | | ||
| SwapFee | sdk.Dec | | ||
| ExitFee | sdk.Dec | | ||
| Weights | \*Weights | | ||
| SmoothWeightChangeParams | \*SmoothWeightChangeParams | | ||
| PoolCreationFee | sdk.Coins | | ||
|
||
### Parameter Definitions | ||
|
||
- **SwapFee**: A percentage cut of all swaps that goes to the liquidity providers (LPs) of a pool. | ||
- **ExitFee**: A fee applied when LPs remove their liquidity from the pool. | ||
- **Weights**: Defines the asset weight ratios within the pool. | ||
- **SmoothWeightChangeParams**: Allows for gradual weight adjustments over time. | ||
- **PoolCreationFee**: The fee required to create a new pool, currently set to 20 USDC. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
<!-- | ||
order: 3 | ||
--> | ||
|
||
# Oracle Pools | ||
|
||
Oracle Pools are liquidity pools designed to provide low slippage for two assets that are expected to have a tightly correlated price ratio. The AMM (Automated Market Maker) achieves this low slippage around the expected price ratio, though price impact and slippage increase significantly as liquidity becomes more unbalanced. | ||
|
||
This documentation details the implementation of the Solidly stableswap curve, a type of Constant Function Market Maker (CFMM) with the invariant equation: | ||
|
||
$f(x, y) = xy(x^2 + y^2) = k$ | ||
|
||
For multiple assets, this is generalized to: | ||
|
||
$f(a_1, ..., a_n) = a_1 \cdot ... \cdot a_n (a_1^2 + ... + a_n^2)$ | ||
|
||
## Pool Configuration | ||
|
||
### Scaling Factor Handling | ||
|
||
Scaling factors in Oracle pools are analogous to asset weights, affecting the valuation of assets within the pool. A governor can adjust these scaling factors, particularly when assets do not maintain a fixed ratio but instead vary predictably (e.g., non-rebasing LSTs). | ||
|
||
By default, pools do not have a governor, as fixed ratios are sufficient for most stablecoin pools (e.g., 1:1 ratios). Scaling factors should typically be adjusted infrequently and with LPs (Liquidity Providers) being informed of the associated risks. | ||
|
||
### Practical Considerations | ||
|
||
1. **Precision Differences**: Different pegged coins may have varying precision levels. | ||
2. **Token Variants**: Tokens like `TwoFoo` might need to trade around a specific multiple (e.g., `1 TwoFoo = 2 Foo`). | ||
3. **Staking Derivatives**: Value accrual within the token can dynamically change the expected price concentration. | ||
|
||
Scaling factors map "raw coin units" to "AMM math units" by dividing the raw coin units by the scaling factor. For example, if `Foo` has a scaling factor of $10^6$ and `WrappedFoo` has a factor of 1, then the mapping is `raw coin units / scaling factor`. | ||
|
||
Rounding is handled by an enum `rounding mode` with three options: `RoundUp`, `RoundDown`, `RoundBankers`. These ensure precise rounding when dealing with scaled reserves. | ||
|
||
### Implementation | ||
|
||
#### Python Example for Scaling | ||
|
||
```python | ||
scaled_Foo_reserves = decimal_round(pool.Foo_liquidity / 10^6, RoundingMode) | ||
descaled_Foo_reserves = scaled_Foo_reserves * 10^6 | ||
``` | ||
|
||
## Algorithm Details | ||
|
||
The AMM pool interface requires the implementation of several stateful methods: | ||
|
||
```golang | ||
SwapOutAmtGivenIn(tokenIn sdk.Coins, tokenOutDenom string, spreadFactor osmomath.Dec) (tokenOut sdk.Coin, err error) | ||
SwapInAmtGivenOut(tokenOut sdk.Coins, tokenInDenom string, spreadFactor osmomath.Dec) (tokenIn sdk.Coin, err error) | ||
SpotPrice(baseAssetDenom string, quoteAssetDenom string) (osmomath.Dec, error) | ||
JoinPool(tokensIn sdk.Coins, spreadFactor osmomath.Dec) (numShares osmomath.Int, err error) | ||
JoinPoolNoSwap(tokensIn sdk.Coins, spreadFactor osmomath.Dec) (numShares osmomath.Int, err error) | ||
ExitPool(numShares osmomath.Int, exitFee osmomath.Dec) (exitedCoins sdk.Coins, err error) | ||
``` | ||
|
||
### CFMM Function | ||
|
||
The CFMM equation is symmetric, allowing us to reorder the arguments without loss of generality. We simplify the CFMM function for specific assets by using variable substitution: | ||
|
||
$$ | ||
\begin{equation} | ||
v = | ||
\begin{cases} | ||
1, & \text{if } n=2 \\ | ||
\prod\negthinspace \negthinspace \thinspace^{n}_{i=3} \space a_i, & \text{otherwise} | ||
\end{cases} | ||
\end{equation} | ||
$$ | ||
|
||
$$ | ||
\begin{equation} | ||
w = | ||
\begin{cases} | ||
0, & \text{if}\ n=2 \\ | ||
\sum\negthinspace \negthinspace \thinspace^{n}_{i=3} \space {a_i^2}, & \text{otherwise} | ||
\end{cases} | ||
\end{equation} | ||
$$ | ||
|
||
Thus, we define $g(x,y,v,w) = xyv(x^2 + y^2 + w) = f(x,y, a_3, ... a_n)$. | ||
|
||
### Swaps | ||
|
||
#### Direct Swap Solution | ||
|
||
For a swap involving $a$ units of $x$, yielding $b$ units of $y$: | ||
|
||
$g(x_0, y_0, v, w) = k = g(x_0 + a, y_0 - b, v, w)$ | ||
|
||
We solve for $y$ using the CFMM equation, which can be complex but is provable and can be approximated through iterative methods like binary search. | ||
|
||
#### Iterative Search Solution | ||
|
||
We perform a binary search to find $y$ such that $ h(x, y, w) = k' $, adjusting bounds iteratively for efficient computation. | ||
|
||
#### Pseudocode for Binary Search | ||
|
||
```python | ||
def solve_y(x_0, y_0, w, x_in): | ||
x_f = x_0 + x_in | ||
err_tolerance = {"within factor of 10^-12", RoundUp} | ||
y_f = iterative_search(x_0, x_f, y_0, w, err_tolerance) | ||
y_out = y_0 - y_f | ||
return y_out | ||
|
||
def iter_k_fn(x_f, y_0, w): | ||
def f(y_f): | ||
y_out = y_0 - y_f | ||
return -(y_out)**3 + 3 y_0 * y_out^2 - (x_f**2 + w + 3y_0**2) * y_out | ||
|
||
def iterative_search(x_0, x_f, y_0, w, err_tolerance): | ||
target_k = target_k_fn(x_0, y_0, w, x_f) | ||
iter_k_calculator = iter_k_fn(x_f, y_0, w) | ||
lowerbound, upperbound = y_0, y_0 | ||
k_ratio = bound_estimation_k0 / bound_estimation_target_k | ||
if k_ratio < 1: | ||
upperbound = ceil(y_0 / k_ratio) | ||
elif k_ratio > 1: | ||
lowerbound = 0 | ||
else: | ||
return y_0 | ||
max_iteration_count = 100 | ||
return binary_search(lowerbound, upperbound, iter_k_calculator, target_k, err_tolerance) | ||
|
||
def binary_search(lowerbound, upperbound, approximation_fn, target, max_iteration_count, err_tolerance): | ||
iter_count = 0 | ||
cur_k_guess = 0 | ||
while (not satisfies_bounds(cur_k_guess, target, err_tolerance)) and iter_count < max_iteration_count: | ||
iter_count += 1 | ||
cur_y_guess = (lowerbound + upperbound) / 2 | ||
cur_k_guess = approximation_fn(cur_y_guess) | ||
if cur_k_guess > target: | ||
upperbound = cur_y_guess | ||
else if cur_k_guess < target: | ||
lowerbound = cur_y_guess | ||
if iter_count == max_iteration_count: | ||
return Error("max iteration count reached") | ||
return cur_y_guess | ||
``` | ||
|
||
### Spot Price | ||
|
||
The spot price is approximated by a small swap amount $\epsilon$: | ||
|
||
$\text{spot price} = \frac{\text{CalculateOutAmountGivenIn}(\epsilon)}{\epsilon}$ | ||
|
||
### LP Equations | ||
|
||
#### JoinPoolNoSwap and ExitPool | ||
|
||
These methods follow generic AMM techniques, maintaining consistency with the CFMM properties. | ||
|
||
#### JoinPoolSingleAssetIn | ||
|
||
For single asset joins: | ||
|
||
```python | ||
def JoinPoolSingleAssetIn(pool, tokenIn): | ||
spreadFactorApplicableFraction = 1 - (pool.ScaledLiquidityOf(tokenIn.Denom) / pool.SumOfAllScaledLiquidity()) | ||
effectiveSpreadFactor = pool.SwapFee * spreadFactorApplicableFraction | ||
effectiveTokenIn = RoundDown(tokenIn * (1 - effectiveSpreadFactor)) | ||
return BinarySearchSingleJoinLpShares(pool, effectiveTokenIn) | ||
``` | ||
|
||
## Code Structure | ||
|
||
The code is structured to facilitate unit testing for each pool interface method, custom message testing, and simulator integrations. | ||
|
||
## Testing Strategy | ||
|
||
- **Unit Tests**: For each pool interface method. | ||
- **Msg Tests**: For custom messages like `CreatePool` and `SetScalingFactors`. | ||
- **Simulator Integrations**: Testing pool creation, join and exit pool operations, single token join/exit, and CFMM adjustments. | ||
- **Fuzz Testing**: To ensure the binary search algorithm and iterative approximation swap algorithm work correctly across various scales. | ||
|
||
By following this documentation, developers can understand and implement the Solidly stableswap curve efficiently, ensuring low slippage and robust functionality for tightly correlated asset pairs. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<!-- | ||
order: 2 | ||
order: 4 | ||
--> | ||
|
||
# State | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<!-- | ||
order: 3 | ||
order: 5 | ||
--> | ||
|
||
# Keepers | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<!-- | ||
order: 4 | ||
order: 6 | ||
--> | ||
|
||
# Endpoints | ||
|
2 changes: 1 addition & 1 deletion
2
x/amm/spec/05_risk_management.md → x/amm/spec/07_risk_management.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<!-- | ||
order: 5 | ||
order: 7 | ||
--> | ||
|
||
# Risk management | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<!-- | ||
order: 6 | ||
order: 8 | ||
--> | ||
|
||
# Slippage Model | ||
|
2 changes: 1 addition & 1 deletion
2
x/amm/spec/07_swap_txs_reordering.md → x/amm/spec/09_swap_txs_reordering.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
<!-- | ||
order: 7 | ||
order: 9 | ||
--> | ||
|
||
# Swap Transactions Reordering | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,28 @@ | ||
# Oracle based pool | ||
# Elys AMM Module | ||
|
||
## Contents | ||
|
||
1. **[Concepts](01_concepts.md)** | ||
2. **[State](02_state.md)** | ||
3. **[Keeper](03_keeper.md)** | ||
4. **[Endpoints](04_endpoints.md)** | ||
5. **[Risk Management](05_risk_management.md)** | ||
6. **[Slippage Model](06_slippage.md)** | ||
7. **[Swap Transactions Reordering](07_swap_txs_reordering.md)** | ||
2. **[Mechanism](02_mechanism.md)** | ||
3. **[Oracle Pools](03_oracle_pools.md)** | ||
4. **[State](04_state.md)** | ||
5. **[Keeper](05_keeper.md)** | ||
6. **[Endpoints](06_endpoints.md)** | ||
7. **[Risk Management](07_risk_management.md)** | ||
8. **[Slippage Model](08_slippage.md)** | ||
9. **[Swap Transactions Reordering](09_swap_txs_reordering.md)** | ||
|
||
## References | ||
|
||
Resources: | ||
|
||
- [Elys Network Documentation](https://docs.elys.network) | ||
- [Cosmos SDK Documentation](https://docs.cosmos.network) | ||
- [GitHub Repository for Elys Network](https://github.com/elys-network/elys) | ||
|
||
## Overview | ||
|
||
The Elys Network AMM module implements an Automated Market Maker (AMM) using various types of liquidity pools. It supports the following types of pools: | ||
|
||
1. **AMM Pools**: Non-oracle pools with liquidity centered around a specific spot price, designed for assets with significant price variation. | ||
2. **Oracle Pools**: Oracle pools with liquidity centered around an oracle price, designed for assets with stable prices. |
Oops, something went wrong.