Skip to content

Commit

Permalink
docs: Flesh out token swap README (#1133)
Browse files Browse the repository at this point in the history
* Flesh out token swap README

* Add testing info

* Update opening paragraphs and add link to dapp

* Wrap

* Update title

* Update docs/src/token-swap.md

Co-authored-by: Tyera Eulberg <[email protected]>

Co-authored-by: B <[email protected]>
Co-authored-by: Tyera Eulberg <[email protected]>
  • Loading branch information
3 people authored Feb 5, 2021
1 parent f402e39 commit 6c09992
Showing 1 changed file with 287 additions and 3 deletions.
290 changes: 287 additions & 3 deletions docs/src/token-swap.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,291 @@
---
title: Token-Swap Program
title: Token Swap Program
---

A Uniswap-like exchange for the Token program on the Solana blockchain.
A Uniswap-like exchange for the Token program on the Solana blockchain,
implementing multiple automated market maker (AMM) curves.

The project is under construction.
## Overview

The Token Swap Program allows simple trading of token pairs without a
centralized limit order book. The program uses a mathematical formula called
"curve" to calculate the price of all trades. Curves aim to mimic normal market
dynamics: for example, as traders buy a lot of one token type, the value of the
other token type goes up.

Depositors in the token swap pool provide liquidity for the token pair. That
liquidity enables trade execution at spot price. In exchange for their
liquidity, depositors receive pool tokens, representing their fractional
ownership in the pool. During each trade, a program withholds a portion of the
input token as a fee. That fee increases the value of pool tokens by being
stored in the pool.

This program was heavily inspired by [Uniswap](https://uniswap.org/) and
[Balancer](https://balancer.finance/). More information is available in their
excellent documentation and whitepapers.

## Background

Solana's programming model and the definitions of the Solana terms used in this
document are available at:

- https://docs.solana.com/apps
- https://docs.solana.com/terminology

## Source

The Token Swap Program's source is available on
[github](https://github.com/solana-labs/solana-program-library).

## Interface

[JavaScript
bindings](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/js/client/token-swap.js)
are available that support loading the Token Swap Program on to a chain and
issuing instructions.

Example user interface built and maintained by Serum team is available
[here](https://github.com/project-serum/oyster-swap)

## Operational overview

The following explains the instructions available in the Token Swap Program.
Note that each instruction has a simple code example that can be found in the
[end-to-end tests](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/js/cli/token-swap-test.js).

### Creating a new token swap

The creation of a token swap showcases the account, instruction, and authorization
models on Solana, which can be very different compared to other blockchains.

Initialization of a token swap between two token types, which we'll call "A"
and "B" for simplicity, requires the following accounts:

* empty token swap state account
* token swap authority
* token A account
* token B account
* pool token mint
* pool token fee account
* pool token recipient account
* token program

The token swap state account simply needs to be created using
`system_instruction::create_account` with the correct size and enough lamports
to be rent-free.

The token swap authority is a
[program derived address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses)
that can "sign" instructions towards other programs. This is
required for the Token Swap Program to mint pool tokens and transfer tokens from
its token A and B accounts.

The token A / B accounts, pool token mint, and pool token accounts must all be
created (using `system_instruction::create_account`) and initialized (using
`spl_token::instruction::initialize_mint` or
`spl_token::instruction::initialize_account`). The token A and B accounts must
be funded with tokens, and their owner set to the swap authority, and the mint
must also be owned by the swap authority.

Once all of these accounts are created, the Token Swap `initialize` instruction
will properly set everything up and allow for immediate trading. Note
that the token swap state account is not required to be a signer on `initialize`,
so it's important to perform the `initialize` instruction in the same transaction
as its `system_instruction::create_account`.

### Swapping

Once a token swap is created, users can immediately begin trading on it using
the `swap` instruction. The swap instruction transfers tokens from a user's source
account into the swap's source token account, and then transfers tokens from
its destination token account into the user's destination token account.

Since Solana programs require all accounts to be declared in the instruction,
users need to gather all account information from the token swap state account:
the token A and B accounts, pool token mint, and fee account.

Additionally, the user must allow for tokens to be transferred from their source
token account. The best practice is to `spl_token::instruction::approve` a
precise amount to a new throwaway Keypair, and then have that new Keypair sign
the swap transaction. This limits the amount of tokens that can be taken
from the user's account by the program.

### Depositing liquidity

To allow any trading, the token swap needs liquidity provided from the
outside. Using the `deposit_all_token_types` or
`deposit_single_token_type_exact_amount_in` instructions, anyone can provide
liquidity for others to trade, and in exchange, depositors receive a pool token
representing fractional ownership of all A and B tokens in the token swap.

Additionally, the user will need to approve a delegate to transfer tokens from
their A and B token accounts. This limits the amount of tokens that can be taken
from the user's account by the program.

### Withdrawing liquidity

At any time, pool token holders may redeem their pool tokens in exchange for
tokens A and B, returned at the current "fair" rate as determined by the curve.
In the `withdraw_all_token_types` and
`withdraw_single_token_type_exact_amount_out` instructions, pool tokens are
burned, and tokens A and B are transferred into the user's accounts.

Additionally, the user will need to approve a delegate to transfer tokens from
their pool token account. This limits the amount of tokens that can be taken
from the user's account by the program.

## Curves

The Token Swap Program is completely customizable for any possible trading curve
that implements the
[CurveCalculator](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/calculator.rs)
trait. If you would like to implement a new automated market maker, it may be
as easy as forking the Token Swap Program and implementing a new curve. The
following curves are all provided out of the box for reference.

### Constant product

The [constant product
curve](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/constant_product.rs)
is the well-known Uniswap and Balancer style curve that preserves an invariant
on all swaps, expressed as the product of the quantity of token A and token B
in the swap.

```
A_total * B_total = invariant
```

If a trader wishes to put in token A for some amount of token B, the calculation
for token B becomes:

```
(A_total + A_in) * (B_total - B_out) = invariant
```

For example, if the swap has 100 token A and 5,000 token B, and a trader wishes
to put in 10 token A, we can solve for the `invariant` and then `B_out`:

```
A_total * B_total = 100 * 5,000 = 500,000 = invariant
```

And

```
(A_total + A_in) * (B_total - B_out) = invariant
(100 + 10) * (5,000 - B_out) = 500,000
5,000 - B_out = 500,000 / 110
5,000 - (500,000 / 110) = B_out
B_out = 454.5454...
```

More information can be found on the [Uniswap
whitepaper](https://uniswap.org/whitepaper.pdf) and the [Balancer
whitepaper](https://balancer.finance/whitepaper/).

### Constant price

The [constant price curve](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/constant_price.rs)
is a simple curve that always maintains the price of token A with respect to
token B. At initialization, the swap creator sets the cost for 1 token B in
terms of token A. For example, if the price is set to 17, 17 token A will always
be required to receive 1 token B, and 1 token B will always be required to
receive 17 token A.

Note that this curve does not follow traditional market dynamics, since the
price is always the same.

Constant price curves are most useful for fixed offerings of new tokens that
explicitly should not have market dynamics. For example, a decentralized
game creator wants to sell new "SOLGAME" tokens to be used in their
game, so they create a constant price swap of 2 USDC per SOLGAME, and supply all
of the SOLGAME tokens at swap creation. Users can go to the swap and purchase all
of the tokens they want and not worry about the market making SOLGAME tokens too
expensive.

### Stable (under construction)

The [stable curve](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/stable.rs)
from [curve.fi](https://www.curve.fi/), has a different shape to prioritize
"stable" trading, meaning prices that stay constant through trading. Most
importantly, prices don't change as quickly as the constant product curve, so a
stable swap between two coins that represent the same value should be as close
to 1:1 as possible. For example, stablecoins that represent a value in USD (USDC,
TUSD, USDT, DAI), should not have big price discrepancies due to the amount of
tokens in the swap.

The curve mirrors the dynamics of the curve
More information can be found on their [whitepaper](https://www.curve.fi/stableswap-paper.pdf).

The Token Swap Program implementation of the stable curve is under construction,
and a more complete version can be found at the
[stable-swap-program](https://github.com/michaelhly/stable-swap-program/).

### Offset

The [offset curve](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/offset.rs)
can be seen as a combination of the constant price and constant product curve.
It follows the constant product curve dynamics, but allows for the token swap
creator to set an "offset" on one side. The invariant for the curve is:

```
(A_total) * (B_total + B_offset) = invariant
```

This is useful for initial token
offerings, where the token creator wants to sell some new token as a swap without
putting up the capital to fund the other side of the swap. This is similar to the
constant price curve, but the key difference is that the offset curve captures
normal market dynamics, in that the offered token price will increase as it is
bought.

For example, a decentralized betting application creator wants to sell new "SOLBET"
tokens on the market in exchange for USDC, and they believe each token is worth
at least 4 USDC. They create a token swap between SOLBET and USDC, funding
one side with 1,000 SOLBET, and the other side with 0 USDC, but an offset
of 4,000 USDC.

If a trader tries to buy SOLBET with 40 USDC, the invariant is calculated
with the offset:

```
(SOLBET_total) * (USDC_total + USDC_offset) = invariant
1,000 * (0 + 4,000) = 4,000,000
(SOLBET_total - SOLBET_out) * (USDC_total + USDC_offset + USDC_in) = invariant
SOLBET_out = 9.901
```

The trader received 9.901 SOLBET for 40 USDC, so the price per SOLBET was
roughly 4.04, slightly higher than the minimum of 4 USDC per SOLBET.

Conversely, if a trader tries to buy USDC with SOLBET immediately after creation,
it will fail because there is no USDC actually present in the pool.

## Testing

The token-swap program is tested using various strategies, including unit tests,
integration tests, property tests, and fuzzing. Since unit tests and integration
tests are well-known, we highlight property tests and fuzzing here.

### Property testing

Using the [proptest](https://altsysrq.github.io/proptest-book/intro.html)
crate, we test specific mathematical properties of curves, specifically to avoid
leaking value on any trades, deposits, or withdrawals. It is out of scope of
this document to explain property testing, but the specific property tests for
the Token Swap Program can be found in the
[curves](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/constant_product.rs)
and
[math](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/math.rs)
portions of the repo.

### Fuzzing

Using [honggfuzz](https://github.com/rust-fuzz/honggfuzz-rs), we regularly
test all possible inputs to the Token Swap Program, ensuring that the program
does not crash unexpectedly or leak tokens. It is out of scope of this document
to explain fuzzing, but the specific implementation for the program can be found
in the [instruction fuzz
tests](https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/fuzz/src/instructions.rs)
of the repo.

0 comments on commit 6c09992

Please sign in to comment.