Skip to content

Commit

Permalink
chore: add docs for new CL logic (#4918)
Browse files Browse the repository at this point in the history
* add docs for cl sf

* Update x/concentrated-liquidity/README.md

Co-authored-by: Roman <[email protected]>

* update readme

* clarify subset comment

* add single message for each path

---------

Co-authored-by: Roman <[email protected]>
  • Loading branch information
czarcas7ic and p0mvn authored Apr 16, 2023
1 parent 4f6f95f commit d93d153
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 31 deletions.
110 changes: 79 additions & 31 deletions x/concentrated-liquidity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,13 @@ b) Having the front end round the tick's actual price to the nearest human reada

One draw back of this implementation is the requirement to create many ticks that will likely never be used. For example, in order to create ticks at 10 cent increments for spot prices greater than _$10000_, a $exponentAtPriceOne$ value of -5 must be set, requiring us to traverse ticks 1-3600000 before reaching _$10,000_. This should simply be an inconvenience and should not present any valid DOS vector for the chain.

### Scope of Concentrated Liquidity
## Scope of Concentrated Liquidity

#### Concentrated Liquidity Module
### Concentrated Liquidity Module

> As an engineer, I would like the concentrated liquidity logic to exist in its own module so that I can easily reason about the concentrated liquidity abstraction that is different from the existing pools.
##### `MsgCreatePosition`
#### `MsgCreatePosition`

- **Request**

Expand Down Expand Up @@ -282,7 +282,7 @@ type MsgCreatePositionResponse struct {

This message should call the `createPosition` keeper method that is introduced in the `"Liquidity Provision"` section of this document.

##### `MsgWithdrawPosition`
#### `MsgWithdrawPosition`

- **Request**

Expand Down Expand Up @@ -314,7 +314,7 @@ type MsgWithdrawPositionResponse struct {

This message should call the `withdrawPosition` keeper method that is introduced in the `"Liquidity Provision"` section of this document.

##### `MsgCreatePool`
#### `MsgCreatePool`

This message is responsible for creating a concentrated-liquidity pool.
It propagates the execution flow to the `x/poolmanager` module for pool id
Expand All @@ -341,7 +341,7 @@ type MsgCreateConcentratedPoolResponse struct {
}
```

##### `MsgCollectFees`
#### `MsgCollectFees`

This message allows collecting fee from a position that is defined by the given
pool id, sender's address, lower tick and upper tick.
Expand Down Expand Up @@ -369,9 +369,9 @@ type MsgCollectFeesResponse struct {
}
```

#### Relationship to Pool Manager Module
## Relationship to Pool Manager Module

##### Pool Creation
### Pool Creation

As previously mentioned, the `x/poolmanager` is responsible for creating the
pool upon being called from the `x/concentrated-liquidity` module's message server.
Expand All @@ -390,7 +390,7 @@ Note, that `InitializePool` is a method defined on the `SwapI` interface that is
implemented by all swap modules. For example, `x/gamm` also implements it so that
`x/pool-manager` can route pool initialization there as well.

##### Swaps
### Swaps

We rely on the swap messages located in `x/poolmanager`:

Expand All @@ -402,7 +402,7 @@ is associated with the `concentrated-liquidity` pool, the swap is routed
into the relevant module. The routing is done via the mapping from state that was
discussed in the "Pool Creation" section.

#### Liquidity Provision
### Liquidity Provision

> As an LP, I want to provide liquidity in ranges so that I can achieve greater capital efficiency
Expand All @@ -411,7 +411,7 @@ to a pool.

A pool's liquidity is consisted of two assets: asset0 and asset1. In all pools, asset0 will be the lexicographically smaller of the two assets. At the current tick, the bucket at this tick consists of a mix of both asset0 and asset1 and is called the virtual liquidity of the pool (or "L" for short). Any positions set below the current price are consisted solely of asset0 while positions above the current price only contain asset1.

##### Adding Liquidity
### Adding Liquidity

We can either provide liquidity above or below the current price, which would act as a range order, or decide to provide liquidity at the current price.

Expand Down Expand Up @@ -457,7 +457,7 @@ func createPosition(
}
```

##### Removing Liquidity
### Removing Liquidity

Removing liquidity is achieved via method `withdrawPosition` which is the inverse of previously discussed `createPosition`. In fact,
the two methods share the same underlying logic, having the only difference being the sign of the liquidity. Plus signifying addition
Expand All @@ -479,7 +479,7 @@ func (k Keeper) withdrawPosition(
}
```

#### Swapping
### Swapping

> As a trader, I want to be able to swap over a concentrated liquidity pool so that my trades incur lower slippage
Expand Down Expand Up @@ -519,7 +519,7 @@ State updates only occur upon successful execution of the swap inside the calc m
We ensure that calc does not update state by injecting `sdk.CacheContext` as its context parameter.
The cache context is dropped on failure and committed on success.

##### Calculating Swap Amounts
### Calculating Swap Amounts

Let's now focus on the core logic of calculating swap amounts.
We mainly focus on `calcOutAmtGivenIn` as the high-level steps of `calcInAmtGivenOut`
Expand Down Expand Up @@ -660,7 +660,55 @@ Then, we either proceed to the next swap step or finalize the swap.
Once the swap is completed, we persiste the swap state to the global state (if mutative action is performed)
and return the `amountCalculated` to the user.

##### Swapping. Appendix A: Example
## Migration

Users can migrate their Balancer positions to a Concentrated Liquidity full range position provided the underlying Balancer pool has a governance-selected
canonical Concentrated Liquidity pool. The migration follows two distinct flows depending on the state of the underlying Balancer position:

1. Balancer position is:
* Superfluid delegated
* Superfluid undelegating
* Locked
* Unlocked

2. Balancer position has no underlying lock whatsoever

Regardless of the path taken, a single message executes all of the below logic:

`UnlockAndMigrateSharesToFullRangeConcentratedPosition` in superfluid for path 1, and `MigrateSharesToFullRangeConcentratedPosition` in gamm for path 2.

### Superfluid Delegated Balancer to Concentrated

The following diagram illustrates the migration flow for a Superfluid delegated Balancer position to a Superfluid delegated Concentrated Liquidity position.

![Migrate Superfluid Delegate Balancer to Concentrated](./img/MigrateSuperfluidDelegated.png)

The migration process starts by removing the connection between the GAMM lock and the GAMM intermediary account. The synthetic OSMO that was previously minted by the GAMM intermediary account is immediately undelegated (skipping the two-week unbonding period) and sent to the Superfluid module account where it is burned.

Next, the Lockup module account holding the original GAMM shares sends them back to the user, deleting the GAMM lock in the process. These shares are used to claim the underlying two assets from the GAMM pool, which are then immediately put into a full range Concentrated Liquidity position in the canonical Concentrated Liquidity pool.

The underlying liquidity this creates is tokenized (similar to GAMM shares) and is put into a new lock, which is then routed to the Lockup module account. A new intermediary account is created based on this new CL share denom. The new intermediary account mints synthetic OSMO and delegates it to the validator the user originally delegated to. Finally, a new synthetic lock in a bonded status is created based on the new CL lock ID, the new CL intermediary account, and the new CL synthetic denom.

### Superfluid Undelegating Balancer to Concentrated

The following diagram illustrates the migration flow for a superfluid undelegating balancer position to a superfluid undelegating concentrated liquidity position. The reason we must account for this situation is to respect the two week unbonding period that is required for superfluid undelegating, and be capable of slashing a position that was migrated.

![Migrate Superfluid Undelegating Balancer to Concentrated](./img/MigrateSuperfluidUndelegating.png)

The process is identical to the Superfluid delegated migration, with three exceptions. First, the connection between the GAMM intermediary account and the GAMM lock is already removed when a user started undelegation, so it does not need to be done again. Second, no synthetic OSMO needs to be burned or created. Lastly, instead of creating a new CL synthetic lock in a bonded status, we create a new CL synthetic lock in an unlocking status. This lock will be unlocked once the two-week unbonding period is over.

### Locked and Unlocked Balancer to Concentrated

The locked<>locked and unlocked<>unlocked migration utilizes a subset of actions that were taken in the superfluid migration. The Lockup module account that was holding the original GAMM shares sends them back to the user, deleting the GAMM lock in the process. These shares are used to claim the underlying two assets from the GAMM pool, which are then immediately put into a full range Concentrated Liquidity position in the canonical Concentrated Liquidity pool.

If it was previously locked, we keep the concentrated locked for the same period of time. If it was previously unlocking, we begin unlocking the concentrated lock from where the GAMM lock left off.

### Balancer to Concentrated with No Lock

When GAMM shares are not locked, they are simply claimed for the underlying two assets, which are then immediately put into a full range concentrated liquidity position in the canonical concentrated liquidity pool. No locks are involved in this migration.


## Swapping. Appendix A: Example

Note, that the numbers used in this example are not realistic. They are used to illustrate the concepts
on the high level.
Expand Down Expand Up @@ -756,13 +804,13 @@ See more details about the fee growth in the "Fees" section.

TODO: Swapping, Appendix B: Compute Swap Step Internals and Math

#### Range Orders
## Range Orders

> As a trader, I want to be able to execute ranger orders so that I have better control of the price at which I trade
TODO

#### Fees
## Fees

> As a an LP, I want to earn fees on my capital so that I am incentivized to participate in the market making actively.
Expand Down Expand Up @@ -951,7 +999,7 @@ if !isPositionNew {
position.FeeGrowthInsideLast.Token0 = feeGrowthInside.Token0
```

##### Collecting Fees
## Collecting Fees

Collecting fees is as simple as transferring the requested amount
from the pool address to the position's owner.
Expand All @@ -975,7 +1023,7 @@ func (k Keeper) collectFees(
}
```

##### Swaps
## Swaps

Swapping within a single tick works as the regular `xy = k` curve. For swaps
across ticks to work, we simply apply the same fee calculation logic for every swap step.
Expand Down Expand Up @@ -1016,7 +1064,7 @@ Here, `tokenInAmtAfterFee` is delta x.
Once we have the updated square root price, we can calculate the amount of `tokenOut` to be returned.
The returned `tokenOut` is computed with fees accounted for given that we used `tokenInAmtAfterFee`.

##### Swap Step Fees
## Swap Step Fees

We have a notion of `swapState.amountSpecifiedRemaining` which is the amount of token in
remaining over all swap steps.
Expand Down Expand Up @@ -1049,11 +1097,11 @@ protection got trigerred.
feeChargeTotal = amountIn.Mul(swapFee)
```

#### Liquidity Rewards
## Liquidity Rewards

TODO

#### TWAP Integration
## TWAP Integration

In the context of twap, concentrated liquidity pools function differently from
CFMM pools.
Expand Down Expand Up @@ -1107,9 +1155,9 @@ x/twap module).
Lastly, see the "Listeners" section for more details on how twap is enabled by the use of
these hooks.

#### Listeners
## Listeners

##### `AfterConcentratedPoolCreated`
### `AfterConcentratedPoolCreated`

This listener executes after the pool is created.

Expand All @@ -1118,38 +1166,38 @@ The twap module is expected to create twap records where the last error time
is set to the block time of when the pool was created. This is because there
is no liquidity in the pool at creation time.

##### `AfterInitialPoolPositionCreated`
### `AfterInitialPoolPositionCreated`

This listener executes after the first position is created in a concentrated
liquidity pool.

At the time of this writing, it is only utilized by the `x/twap` module.

##### `AfterLastPoolPositionRemoved`
### `AfterLastPoolPositionRemoved`

This listener executes after the last position is removed in a concentrated
liquidity pool.

At the time of this writing, it is only utilized by the `x/twap` module.

##### `AfterConcentratedPoolSwap`
### `AfterConcentratedPoolSwap`


This listener executes after a swap in a concentrated liquidity pool.

At the time of this writing, it is only utilized by the `x/twap` module.

##### State
### State

- global (per-pool)

- per-tick

- per-position

#### Placeholder
### Placeholder

### Terminology
## Terminology

We will use the following terms throughout the document:

Expand All @@ -1168,7 +1216,7 @@ We will use the following terms throughout the document:

- `Range` - TODO

### External Sources
## External Sources

- [Uniswap V3 Whitepaper](https://uniswap.org/whitepaper-v3.pdf)
- [Technical Note on Liquidity Math](https://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions x/lockup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,8 @@ coins are sent from lockup `ModuleAccount`.
- Transfer the tokens from lockup `ModuleAccount` to the
`MsgUnlockTokens.Owner`.
Note: If the token in the lock contains the ClTokenPrefix ("cl/pool/{poolId}), this token is burned instead of being sent to the owner.
### Remove synthetic locks after removal time mature
For synthetic lockups, no coin movement is made, but lockup record and
Expand Down
7 changes: 7 additions & 0 deletions x/superfluid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,13 @@ We do this by:
- It removes these from the underlying lock and the synthetic lock.
- These coins are moved to the community pool.

Slashing a concentrated liquidity superfluid lockup happens in the same way, however
instead of sending the concentrated full range position shares from the lockup
module account to the community pool, we determine the underlying assets
that the slashed shares represent and send those from the respective pool
account to the community pool. The shares residing in the lockup module
account that represented the funds that got sent to the community pool are then burned.

### Nuances

- Slashed tokens go to the community pool, rather than being burned as
Expand Down

0 comments on commit d93d153

Please sign in to comment.