Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
jcompagni10 committed Nov 5, 2024
1 parent 3f15802 commit 278e242
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 49 deletions.
57 changes: 38 additions & 19 deletions docs/neutron/modules/dex/overview/concepts/liquidity-iteration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Liquidity Iteration

When swapping through liquidity via a Multi-Hop Swap or a Taker Limit Order, we iterate through the available `TickLiquidity` to fill the swap order. Liquidity is always iterated through in order of best to worst price (from the taker's perspective.) For each swap, we completely exhaust the available reserves before moving on to the next tick. For `TickLiquidity` instances at the same `TickIndex` they are iterated through in a deterministic order as follows:
When swapping through liquidity via a Multi-Hop Swap or a Limit Order, we iterate through the available `TickLiquidity` to fill the swap order. Liquidity is always iterated through in order of best to worst price (from the taker's perspective.) For each swap, we completely exhaust the available reserves before moving on to the next tick. For `TickLiquidity` instances at the same `TickIndex` they are iterated through in a deterministic order as follows:
- `PoolReserves` take priority
- `LimitOrderTranches` are iterated through when Pool Reserve liquidity is depleted
- `LimitOrderTranches` are iterated through in alphabetical order of their `TrancheKey` (`LimitOrderTranche.Key.TrancheKey`.)
Expand All @@ -14,39 +14,58 @@ $$
\text{Tick Index Formula}
$$
$$
\text{Price} = 1.0001 ^ \text{ -TickIndex}\\
\text{TickIndex} = -log_{1.0001}(price)
\text{Price} = 1.0001 ^ \text{TickIndex}\\
\text{TickIndex} = log_{1.0001}(price)
$$
* **Deposit 1:**
**Amounts:** 10 `ATOM` 10 `USDC`. **Price:** 10 `USDC` per `ATOM`. **Tick index** $-23027$.
* **Deposit 2:**
**Amounts:** 10 `ATOM` 10 `USDC`. **Price:** 9 `USDC` per `ATOM`. **Tick index** $-21973$.
* **Deposit 3:**
**Amounts:** 10 `ATOM` 10 `USDC`. **Price:** 8 `USDC` per `ATOM`. **Tick index** $-20795$.
* **Deposit 1:**<br />
**Amounts:** 10 `ATOM` 0 `USDC`. **Price:** 8 `USDC` per `ATOM`. **Tick index** $20795$.
* **Deposit 2:**<br />
**Amounts: ** 10 `ATOM` 0 `USDC`. **Price:** 9 `USDC` per `ATOM`. **Tick index** $21973$.
* **Deposit 3:**<br />
**Amounts:** 10 `ATOM` 0 `USDC`. **Price:** 10 `USDC` per `ATOM`. **Tick index** $23027$.

**DEX Limit Orders:**
* **Limit Order 1:**
**Amount:** 10 `ATOM`. **Price:** $0.14$ `ATOM` per `USDC`: **Tick index** $-19460$
* **Limit Order 1:**<br />
**Amount:** 10 `ATOM`. **Price:** $7$ `USDC` per `ATOM`: **Tick index** $19640$
* **Limit Order 2:**<br />
**Amount:** 10 `USDC`. **Price:** $0.14$ `ATOM` per `USDC`: **Tick index** $-19640$

![Example liquidity iteration ](/img/duality-dex-deposit-1.png)

- Tick Index $-23027$ offers the cheapest `USDC` per `ATOM` spent. it yields $10$ `USDC` per `ATOM`
- Tick Index $19640$ offers the cheapest `ATOM` per `USDC` spent. it yields $0.14$ `ATOM` per `USDC`
- Tick Index $19640$ offers the cheapest `ATOM` per `USDC` spent. it yields $1$ `ATOM` per $7$ `USDC`
- Tick Index $-19640$ offers the cheapest `USDC` per `ATOM` spent. it yields $1$ `USDC` per $0.14$ `ATOM`
- It is visible that iterating left to right will always yield the best price regardless of the token being swapped.

### Example Liquidity Iteration: Swap

Alice Performs a Swap using a `Taker Limit Order`. She wants to swap $$100$$ `USDC` for `ATOM` at the best possible price.

1. The first available `TickLiquidity` holding `ATOM` is a `LimitOrderTranche` at tick $19640$. Since this is a limit order, when swapped through and depleted the liquidity is removed from state and the `USDC` Alice paid is credited to the limit order `Receiver`
2. Alice swaps up to $71.428$ `USDC` using this pool before depleting the reserves. This will net her $10$ `ATOM` for the swap:
1. The first available `TickLiquidity` holding `ATOM` is a `LimitOrderTranche` at tick $19640$. Since this is a limit order, when swapped through and depleted the liquidity is removed from state and the `USDC` Alice paid can be later withdrawn by the limit order `Receiver`
2. Alice swaps up to $71.27$ `USDC` using this pool before depleting the reserves. This will net her $10$ `ATOM` for the swap:

$\text{ATOM available} / \text{exchange rate} = \text{USDC needed}$
$10 / 0.14 = 71.428$
$$
\begin{aligned}
AmountSwapped &= ATOMAvailable \cdot Price\\
&= 10 * 1.0001^{19640}\\
&= 71.27
\end{aligned}
$$


3. Alice still has $28.73$ `USDC` she needs to swap, so we move to the next available tick: $20795$
This tick offers `ATOM` at a price of $8$ `USDC` per `ATOM` and is of type `PoolReserve`. Any USDC Alice pays for this swap will be placed in the corresponding poolReserves of the Pool (USDC @ Tick $$-20795$$ .) Alice swaps the remainder of her `USDC` here, resulting in an additional $3.5$ `ATOM`:

$$
\begin{aligned}
AmountOut &= \frac{AmountIn}{Price}\\
\\
&= \frac{28.73}{1.0001^{20795}}\\
\\
&= 3.59
\end{aligned}
$$


3. Alice still has $28.527$ `USDC` she needs to swap, so we move to the next available tick: $-20795$
This tick offers `ATOM` at a price of $0.123$ `ATOM` per `USDC` and is of type `PoolReserve`. Any USDC Alice pays for this swap will be placed in the corresponding poolReserves of the Pool (USDC @ Tick $$-20795$$ .) Alice swaps the remainder of her `USDC` here, resulting in an additional $3.5$ `ATOM`:

$\text{USDC in} * \text{exchange rate} = \text{ATOM out}$
$28.527 * 0.123 = 3.5$
Expand Down
4 changes: 2 additions & 2 deletions docs/neutron/modules/dex/overview/concepts/liquidity-pools.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ There are a number of benefits to using constant priced pools:

**Constant Priced Pools = Constant Sum Market Makers**

Constant priced pools are also known as _constant sum market makers._ This is in reference to a trading function which is the sum of the two reserves multiplied by a constant price i.e., $$\psi(R_1, R_2) = R_1 + p R_2$$.
Constant priced pools are also known as _constant sum market makers._ This is in reference to a trading function which is the sum of the two reserves multiplied by a constant price i.e., $$\psi(R_0, R_1) = R_0 + p R_1$$.

Here, ​$$p$$ is a constant price that the pool trades at regardless of the reserves.

For example if $$p=2$$ than we can exchange 2 ​of $$R_2$$for 4 of $$R_1$$as long as there is enough of $$R_1$$left to complete the trade i.e., $$R_2 \geq 4$$. We can see this is a viable trade because ​$$\psi(R_1, R_2) = R_1 + pR_2 = (R_1 - 4) + p(R_1 + 2)$$ and so the pool's "invariant" ​holds.
For example if $$p=2$$ than we can exchange 2 ​of $$R_1$$for 4 of $$R_0$$as long as there is enough of $$R_0$$left to complete the trade i.e., $$R_1 \geq 4$$. We can see this is a viable trade because ​$$\psi(R_0, R_1) = R_0 + pR_1 = (R_0 - 4) + p(R_0 + 2)$$ and so the pool's "invariant" ​holds.
21 changes: 11 additions & 10 deletions docs/neutron/modules/dex/overview/concepts/tick-liquidity.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Tick Liquidity

`TickLiquidity` structs are used to store liquidity within the Dex. Each tick has a specific price and holds liquidity for a single token. `TickLiquidity` come in two general types --`PoolReserves` for storing LP positions and `LimitOrderTranche`s for storing limit orders. Both `TickLiquidity` types are indexed by a key which shares a number of feilds in common. These two types make up the fundemental building blocks of the DEX orderbook, and are critical to the [liquidity iteration mechanism](docs/neutron/modules/dex/overview/concepts/liquidity-iteration.md). `TickLiquidity`s contain all of the neccesary information to perform a swap, namely price and available reserves. Each `TickLiquidity` instance has a `TradePairID`, `TickIndexTakerToMaker`. A `TradePairID` containing the feilds `MakerDenom` and `TakerDenom` describes what denom is held in the `TickLiquidity` (`MakerDenom`) and what denom it can be traded with (`TakerDenom`). `TickIndexTakerToMaker` describes the tick at which a certain liquidity is stored and by extension its `PriceTakerToMaker`.
`TickLiquidity` structs are used to store liquidity within the Dex. Each tick has a specific price and holds liquidity for a single token. `TickLiquidity` come in two general types:`PoolReserves` for storing LP positions and `LimitOrderTranche`s for storing limit orders. Both `TickLiquidity` types are indexed by a key which shares a number of feilds in common. These two types make up the fundamental building blocks of the DEX orderbook, and are critical to the [liquidity iteration mechanism](docs/neutron/modules/dex/overview/concepts/liquidity-iteration.md). `TickLiquidity`s contain all of the neccesary information to perform a swap, namely price and available reserves. Each `TickLiquidity` instance has a `TradePairID`, `TickIndexTakerToMaker`. A `TradePairID` containing the feilds `MakerDenom` and `TakerDenom` describes what denom is held in the `TickLiquidity` (`MakerDenom`) and what denom it can be traded with (`TakerDenom`). `TickIndexTakerToMaker` describes the tick at which a certain liquidity is stored and by extension its `MakerPrice`.

## PairID

`PairID`s are the canonical way which we refer to the two tokens in a pair. In order to ensure uniqueness the denoms are sorted alphabetically, with the first denom stored as `Token0` and the second as `Token`.
`PairID`s are the canonical way which we refer to the two tokens in a pair. In order to ensure uniqueness the denoms are sorted alphabetically, with the first denom stored as `Token0` and the second as `Token1`.

```go
type PairID struct {
Expand All @@ -15,11 +15,11 @@ type PairID struct {

For example, in an ATOM<\>USDC pair, ATOM would be `Token0` and `USDC` would be `Token1`

We stringify `PairID`s in the form "[Token0]<\>[Token1] ie. "ATOM<\>USDC".
We stringify `PairID`s in the form `[Token0]<>[Token1]` ie. _'ATOM<\>USDC'_.


## Pool
A `Pool` contains all the information required for a single constant-priced AMM to exist. It holds liquidity for both sides of a pair, `Token0` and `Token1` named `LowerTick` and `UpperTick` respectively. Pools are not explicitly stored anywhere, but their `PoolReserves` content is.
A `Pool` contains all the information required for a single constant-priced AMM to exist. It holds liquidity for both sides of a pair, `Token0` and `Token1` named `LowerTick0` and `UpperTick1` respectively. Pools are not explicitly stored anywhere, but their `PoolReserves` content is.
```go
type Pool struct {
Id uint64
Expand All @@ -36,8 +36,7 @@ type Pool struct {
type PoolReserves struct {
Key *PoolReservesKey
ReservesMakerDenom Int
PriceTakerToMaker PrecDec
PriceOppositeTakerToMaker PrecDec
MakerPrice PrecDec
}
type PoolReservesKey struct {
TradePairId *TradePairID
Expand All @@ -52,12 +51,14 @@ type TradePairID struct {

`ReservesMakerDenom` is used to store the total amount of `MakerDenom` within a given `PoolReserves` instance.

In the context of LP liquidity, `PoolReserves` exist in reciprocal pairs with one side (`LowerTick`) holding Token0 and the other side (`UpperTick`) holding token1. Each of these pairs makes up a single constant price liquidity pool. Within each liquidity pool, the following invariants will always hold True:
In the context of LP liquidity, `PoolReserves` exist in reciprocal pairs with one side (`LowerTick0`) holding Token0 and the other side (`UpperTick1`) holding token1. Each of these pairs makes up a single constant price liquidity pool. Within each liquidity pool, the following invariants will always hold True:

* Both PoolsReserves within a pair will have the same fee: $$LowerTick.Key.Fee == UpperTick.Key.Fee$$
* Both PoolsReserves within a pair will have the same fee: $$LowerTick0.Key.Fee == UpperTick1.Key.Fee$$
* When swaps occur the tokens will always be added to one side of the liquidity pool and deducted from the other side.

When LP liquidity is deposited with a given fee and price it is added to the `TickLiquidity` instances such that the given fee is already included in the price. For example, if Alice deposits 100 TokenA and 100TokenB at price 1 (tick 0) with a fee of 1 then both `PoolReserves` representing the `Pool` will be placed at tick 1 with a `PriceTakerToMaker` of 0.999 each. If Bob were to swap 50Token0 for Token1 using Alice’s liquidity his exchange rate would be \~ .999. His 50 Token0 would be deposited into the `Pool`'s `LowerTick0 PoolReserves`at tick 1 and fee 1. and he would receive 49 Token1 which would be deducted from pool's `LowerTick1` `PoolReserves`.
When LP liquidity is deposited with a given fee and price it is added to the `TickLiquidity` instances such that the given fee is already included in the price.

For example, if Alice deposits 100 TokenA and 100TokenB at price 1 (tick 0) with a fee of 1 then both `PoolReserves` representing the `Pool` will be placed at tick 1 with a `MakerPrice` of 1.0001 each. If Bob were to swap 50Token0 for Token1 using Alice’s liquidity he would pay 1.0001 Token0 per Token1 (or an exchange rate of \~ .999 Token1 per Token0). His 50 Token0 would be deposited into the `Pool`'s `LowerTick0 PoolReserves`at tick 1 and fee 1. and he would receive 49 Token1 which would be deducted from pool's `LowerTick1` `PoolReserves`.


It is important to note that multiple `PoolReserves` can exist with the same TickIndex but each one will have a unique fee.
Expand All @@ -74,7 +75,7 @@ type LimitOrderTranche struct {
TotalMakerDenom cosmossdk_io_math.Int
TotalTakerDenom cosmossdk_io_math.Int
ExpirationTime *time.Time
PriceTakerToMaker github_com_neutron_org_neutron_v4_utils_math.PrecDec
MakerPrice github_com_neutron_org_neutron_v4_utils_math.PrecDec
}
```

Expand Down
35 changes: 17 additions & 18 deletions docs/neutron/modules/dex/overview/concepts/ticks.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,42 @@
# Ticks

Ticks are the fundamental unit of accounting within the dex. Each tick is an integer between 559,680 and -559,680. All tradeable liquidity, in the form of either `PoolReserves` or `LimitOrderTranche`s is stored at a specific tick. Price for liquidity held at a tick is stored in the form of `PriceMakerToTaker` -- the rate at which the `MakerDenom` (the liquidity stored at the tick) can be coverted to the `TakerDenom`. The `PriceMakerToTaker` of liquidity being stored at tick `i` is determined by the function:
Ticks are the fundamental unit of accounting within the dex. Each tick is an integer between 559,680 and -559,680. All tradeable liquidity, in the form of either `PoolReserves` or `LimitOrderTranche`s is stored at a specific tick. Price for liquidity held at a tick is stored in the form of `MakerPrice` -- the price at which the `MakerDenom` is being sold denominated in terms of the `TakerDenom`. The `MakerPrice` of liquidity being stored at tick `i` is determined by the function:


$$
p(i) = 1.0001^{-i}
p(i) = 1.0001^{i}
$$

Some examples:

* Price of tick 0: $$p(0) = 1.0001^0 = 1$$
* Price of tick 1: $$p(1) = 1.0001^{-1} \approx 0.9999$$
* Price of tick -1: $$p(-1) = 1.0001^{1} = ​1.0001$$
* Price of tick 4000: ​$$p(4000) = 1.0001^{-4000} \approx ​0.6703$$
* Price of tick -4000: ​$$p(-4000) = 1.0001^{4000} \approx 1.4918$$
* Price of tick 1: $$p(1) = 1.0001^{1} = 1.0001$$
* Price of tick -1: $$p(-1) = 1.0001^{-1} = \approx 0.9999$$
* Price of tick 4000: ​$$p(4000) = 1.0001^{4000} \approx 1.4918$$
* Price of tick -4000: ​$$p(-4000) = 1.0001^{-4000} \approx ​0.6703$$


In this way, liquidity held at a tick can also be thought of as an offer to buy the opposing a token in the pair at a rate of `PriceMakeToTaker` `MakerDenom`s per `TakerDenom`.
`MakerPrice` can also be seen as the rate for converting `MakerDenom` to `TakerDenom`:

Conversely, price can also be thought of as the price of the `MakerDenom` as denominated in the `TakerDenom`, or the sell price for the `MakerDenom`. This would be expressed as follows:
$$MakerDenom \cdot MakerPrice == TakerDenom$$.

$$
price(i) = 1/1.0001^{i}
$$
Conversely, we can convert `TakerDenom` to `MakerDenom` as follows:

$$\frac{TakerDenom}{MakerPrice} == MakerDenom$$.

Both versions yeild the same result.

Given an intent to deposit `ATOM` and `USDC` into a pool (assuming no fee) with the Price of `ATOM` set at 10 `USDC`per`ATOM` and implying a reciprocal `USDC` price of 0.1 `ATOM`per`USDC`, the best tick index to deposit to will be `+/-23027` given that the target price is 10. (1 `ATOM` yields 10 `USDC`)
Given an intent to deposit `ATOM` and `USDC` into a pool (assuming no fee) with the Price of `ATOM` set at 10 `USDC`per`ATOM` and implying a reciprocal `USDC` price of 0.1 `ATOM`per`USDC`, the best tick index to deposit to will be `+/-23027` given that the target price is 10. (we are selling 1 `ATOM` for 10 `USDC`)


`USDC` liquidity will be placed at tick `-23027`, which implies a price of $$1.0001^{+23027}$$ = 10
`USDC` liquidity will be placed at tick `-23027`, which implies a price of $$1.0001^{-23027}$$ = 0.1ATOM

`ATOM` liquidity will be placed at tick `+23027`, which implies a price of $$1.0001^{-23027}$$ = 0.1
`ATOM` liquidity will be placed at tick `+23027`, which implies a price of $$1.0001^{23027}$$ = 10USDC


A good way to think about price in this context is how much of `MakerDenom` Alice would receive if she swapped a single `TakerDenom` and vice versa. In this case, she would receive 10 `USDC` given an input of 1 `ATOM`, and 0.1 `ATOM` given an input of 1 `USDC`.
A good way to think about price in this context is how much of `TakerDenom` Alice would receive if she sold a single `MakerDenom` and vice versa. In this case, she would receive 10 `USDC` given an input of 1 `ATOM`, and 0.1 `ATOM` given an input of 1 `USDC`.

From this, the simple price heuristic is _How much output given unit input_.

swapping 100 USDC for Atom: $$100\cdot PriceMakerToTaker(ATOM) = 100*0.1 = 10 ATOM$$
swapping 100 USDC for Atom: $$100\cdot MakerPrice(USDC) = 100*0.1 = 10 ATOM$$

swapping 100 Atom for USDC: $$100\cdot PriceMakerToTaker(USDC) = 100*10 = 1000 USDC$$
swapping 100 Atom for USDC: $$100\cdot MakerPrice(ATOM) = 100*10 = 1000 USDC$$
Binary file modified static/img/duality-dex-deposit-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 278e242

Please sign in to comment.