Skip to content

Commit

Permalink
docs: improve module docs (#640)
Browse files Browse the repository at this point in the history
* 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
cosmic-vagabond and amityadav0 authored Jul 9, 2024
1 parent 9124b75 commit 9321c01
Show file tree
Hide file tree
Showing 34 changed files with 1,181 additions and 74 deletions.
107 changes: 107 additions & 0 deletions x/amm/spec/02_mechanism.md
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.
177 changes: 177 additions & 0 deletions x/amm/spec/03_oracle_pools.md
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.
2 changes: 1 addition & 1 deletion x/amm/spec/02_state.md → x/amm/spec/04_state.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 2
order: 4
-->

# State
Expand Down
2 changes: 1 addition & 1 deletion x/amm/spec/03_keeper.md → x/amm/spec/05_keeper.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 3
order: 5
-->

# Keepers
Expand Down
2 changes: 1 addition & 1 deletion x/amm/spec/04_endpoints.md → x/amm/spec/06_endpoints.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 4
order: 6
-->

# Endpoints
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 5
order: 7
-->

# Risk management
Expand Down
2 changes: 1 addition & 1 deletion x/amm/spec/06_slippage.md → x/amm/spec/08_slippage.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 6
order: 8
-->

# Slippage Model
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 7
order: 9
-->

# Swap Transactions Reordering
Expand Down
31 changes: 24 additions & 7 deletions x/amm/spec/README.md
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.
Loading

0 comments on commit 9321c01

Please sign in to comment.