> ## Documentation Index
> Fetch the complete documentation index at: https://developers.stellar.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Liquidity Pool

> Write a constant-product liquidity pool contract.

The [liquidity pool example] demonstrates how to write a constant product liquidity pool contract. A liquidity pool is an automated way to add liquidity for a set of tokens that will facilitate asset conversion between them. Users can deposit some amount of each token into the pool, receiving a proportional number of "token shares." The user will then receive a portion of the accrued conversion fees when they ultimately "trade in" their token shares to receive their original tokens back.

Soroban liquidity pools are exclusive to Soroban and cannot interact with built-in Stellar AMM liquidity pools.

> [!WARNING]
>
> Implementing a liquidity pool contract should be done cautiously. User funds are involved, so great care should be taken to ensure safety and transparency. The example here should *not* be considered a ready-to-go contract. Please use it as a reference only.
>
> The Stellar network already has liquidity pool functionality built right in to the core protocol. [Learn more here](https://developers.stellar.org/docs/learn/fundamentals/liquidity-on-stellar-sdex-liquidity-pools.md).

[![Open in Codespaces](https://github.com/codespaces/badge.svg)][open-in-github-codespaces]

[![Open in Codeanywhere](https://codeanywhere.com/img/open-in-codeanywhere-btn.svg)][open-in-code-anywhere]

[open-in-github-codespaces]: https://github.com/codespaces/new?repo=stellar/soroban-examples&editor=web

[open-in-code-anywhere]: https://app.codeanywhere.com/#https://github.com/stellar/soroban-examples

[liquidity pool example]: https://github.com/stellar/soroban-examples/tree/v23.0.0/liquidity_pool

[source code]: https://github.com/stellar/soroban-examples/tree/v23.0.0/liquidity_pool/src/lib.rs#L143

## Run the Example

First go through the [Setup] process to get your development environment configured, then clone the `v23.0.0` tag of `soroban-examples` repository:

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
git clone -b v23.0.0 https://github.com/stellar/soroban-examples
```

Or, skip the development environment setup and open this example in [GitHub Codespaces][open-in-github-codespaces] or [Code Anywhere][open-in-code-anywhere].

To run the tests for the example, navigate to the `liquidity_pool` directory, and use `cargo test`.

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
cd liquidity_pool
cargo test
```

You should see the output:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
running 3 tests
test test::deposit_amount_zero_should_panic - should panic ... ok
test test::swap_reserve_one_nonzero_other_zero - should panic ... ok
test test::test ... ok
```

[setup]: https://developers.stellar.org/docs/build/smart-contracts/getting-started/setup.md

## Code

```rust title="liquidity_pool/src/lib.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
#![no_std]

mod test;

use num_integer::Roots;
use soroban_sdk::{contract, contractimpl, contractmeta, contracttype, token, Address, Env};

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
    TokenA,
    TokenB,
    TotalShares,
    ReserveA,
    ReserveB,
    Shares(Address),
}

fn get_token_a(e: &Env) -> Address {
    e.storage().instance().get(&DataKey::TokenA).unwrap()
}

fn get_token_b(e: &Env) -> Address {
    e.storage().instance().get(&DataKey::TokenB).unwrap()
}

fn get_total_shares(e: &Env) -> i128 {
    e.storage().instance().get(&DataKey::TotalShares).unwrap()
}

fn get_reserve_a(e: &Env) -> i128 {
    e.storage().instance().get(&DataKey::ReserveA).unwrap()
}

fn get_reserve_b(e: &Env) -> i128 {
    e.storage().instance().get(&DataKey::ReserveB).unwrap()
}

fn get_balance(e: &Env, contract: Address) -> i128 {
    token::Client::new(e, &contract).balance(&e.current_contract_address())
}

fn get_balance_a(e: &Env) -> i128 {
    get_balance(e, get_token_a(e))
}

fn get_balance_b(e: &Env) -> i128 {
    get_balance(e, get_token_b(e))
}

fn get_shares(e: &Env, user: &Address) -> i128 {
    e.storage()
        .persistent()
        .get(&DataKey::Shares(user.clone()))
        .unwrap_or(0)
}

fn put_shares(e: &Env, user: &Address, amount: i128) {
    e.storage()
        .persistent()
        .set(&DataKey::Shares(user.clone()), &amount);
}

fn put_token_a(e: &Env, contract: Address) {
    e.storage().instance().set(&DataKey::TokenA, &contract);
}

fn put_token_b(e: &Env, contract: Address) {
    e.storage().instance().set(&DataKey::TokenB, &contract);
}

fn put_total_shares(e: &Env, amount: i128) {
    e.storage().instance().set(&DataKey::TotalShares, &amount)
}

fn put_reserve_a(e: &Env, amount: i128) {
    e.storage().instance().set(&DataKey::ReserveA, &amount)
}

fn put_reserve_b(e: &Env, amount: i128) {
    e.storage().instance().set(&DataKey::ReserveB, &amount)
}

fn burn_shares(e: &Env, from: &Address, amount: i128) {
    let current_shares = get_shares(e, from);
    if current_shares < amount {
        panic!("insufficient shares");
    }
    let total = get_total_shares(e);
    put_shares(e, from, current_shares - amount);
    put_total_shares(e, total - amount);
}

fn mint_shares(e: &Env, to: &Address, amount: i128) {
    let current_shares = get_shares(e, to);
    let total = get_total_shares(e);
    put_shares(e, to, current_shares + amount);
    put_total_shares(e, total + amount);
}

fn transfer(e: &Env, token: Address, to: Address, amount: i128) {
    token::Client::new(e, &token).transfer(&e.current_contract_address(), &to, &amount);
}

fn transfer_a(e: &Env, to: Address, amount: i128) {
    transfer(e, get_token_a(e), to, amount);
}

fn transfer_b(e: &Env, to: Address, amount: i128) {
    transfer(e, get_token_b(e), to, amount);
}

fn get_deposit_amounts(
    desired_a: i128,
    min_a: i128,
    desired_b: i128,
    min_b: i128,
    reserve_a: i128,
    reserve_b: i128,
) -> (i128, i128) {
    if reserve_a == 0 && reserve_b == 0 {
        return (desired_a, desired_b);
    }

    let amount_b = desired_a * reserve_b / reserve_a;
    if amount_b <= desired_b {
        if amount_b < min_b {
            panic!("amount_b less than min")
        }
        (desired_a, amount_b)
    } else {
        let amount_a = desired_b * reserve_a / reserve_b;
        if amount_a > desired_a || amount_a < min_a {
            panic!("amount_a invalid")
        }
        (amount_a, desired_b)
    }
}

// Metadata that is added on to the WASM custom section
contractmeta!(
    key = "Description",
    val = "Constant product AMM with a .3% swap fee"
);

#[contract]
struct LiquidityPool;

#[contractimpl]
impl LiquidityPool {
    pub fn __constructor(e: Env, token_a: Address, token_b: Address) {
        if token_a >= token_b {
            panic!("token_a must be less than token_b");
        }

        put_token_a(&e, token_a);
        put_token_b(&e, token_b);
        put_total_shares(&e, 0);
        put_reserve_a(&e, 0);
        put_reserve_b(&e, 0);
    }

    pub fn balance_shares(e: Env, user: Address) -> i128 {
        get_shares(&e, &user)
    }

    pub fn deposit(
        e: Env,
        to: Address,
        desired_a: i128,
        min_a: i128,
        desired_b: i128,
        min_b: i128,
    ) {
        // Depositor needs to authorize the deposit
        to.require_auth();

        let (reserve_a, reserve_b) = (get_reserve_a(&e), get_reserve_b(&e));

        // Calculate deposit amounts
        let (amount_a, amount_b) =
            get_deposit_amounts(desired_a, min_a, desired_b, min_b, reserve_a, reserve_b);

        if amount_a <= 0 || amount_b <= 0 {
            // If one of the amounts can be zero, we can get into a situation
            // where one of the reserves is 0, which leads to a divide by zero.
            panic!("both amounts must be strictly positive");
        }

        let token_a_client = token::Client::new(&e, &get_token_a(&e));
        let token_b_client = token::Client::new(&e, &get_token_b(&e));

        token_a_client.transfer(&to, &e.current_contract_address(), &amount_a);
        token_b_client.transfer(&to, &e.current_contract_address(), &amount_b);

        // Now calculate how many new pool shares to mint
        let (balance_a, balance_b) = (get_balance_a(&e), get_balance_b(&e));
        let total_shares = get_total_shares(&e);

        let zero = 0;
        let new_total_shares = if reserve_a > zero && reserve_b > zero {
            let shares_a = (balance_a * total_shares) / reserve_a;
            let shares_b = (balance_b * total_shares) / reserve_b;
            shares_a.min(shares_b)
        } else {
            (balance_a * balance_b).sqrt()
        };

        mint_shares(&e, &to, new_total_shares - total_shares);
        put_reserve_a(&e, balance_a);
        put_reserve_b(&e, balance_b);
    }

    // If "buy_a" is true, the swap will buy token_a and sell token_b. This is flipped if "buy_a" is false.
    // "out" is the amount being bought, with in_max being a safety to make sure you receive at least that amount.
    // swap will transfer the selling token "to" to this contract, and then the contract will transfer the buying token to "to".
    pub fn swap(e: Env, to: Address, buy_a: bool, out: i128, in_max: i128) {
        to.require_auth();

        let (reserve_a, reserve_b) = (get_reserve_a(&e), get_reserve_b(&e));
        let (reserve_sell, reserve_buy) = if buy_a {
            (reserve_b, reserve_a)
        } else {
            (reserve_a, reserve_b)
        };

        if reserve_buy < out {
            panic!("not enough token to buy");
        }

        // First calculate how much needs to be sold to buy amount out from the pool
        let n = reserve_sell * out * 1000;
        let d = (reserve_buy - out) * 997;
        let sell_amount = (n / d) + 1;
        if sell_amount > in_max {
            panic!("in amount is over max")
        }

        // Transfer the amount being sold to the contract
        let sell_token = if buy_a {
            get_token_b(&e)
        } else {
            get_token_a(&e)
        };
        let sell_token_client = token::Client::new(&e, &sell_token);
        sell_token_client.transfer(&to, &e.current_contract_address(), &sell_amount);

        let (balance_a, balance_b) = (get_balance_a(&e), get_balance_b(&e));

        // residue_numerator and residue_denominator are the amount that the invariant considers after
        // deducting the fee, scaled up by 1000 to avoid fractions
        let residue_numerator = 997;
        let residue_denominator = 1000;
        let zero = 0;

        let new_invariant_factor = |balance: i128, reserve: i128, out: i128| {
            let delta = balance - reserve - out;
            let adj_delta = if delta > zero {
                residue_numerator * delta
            } else {
                residue_denominator * delta
            };
            residue_denominator * reserve + adj_delta
        };

        let (out_a, out_b) = if buy_a { (out, 0) } else { (0, out) };

        let new_inv_a = new_invariant_factor(balance_a, reserve_a, out_a);
        let new_inv_b = new_invariant_factor(balance_b, reserve_b, out_b);
        let old_inv_a = residue_denominator * reserve_a;
        let old_inv_b = residue_denominator * reserve_b;

        if new_inv_a * new_inv_b < old_inv_a * old_inv_b {
            panic!("constant product invariant does not hold");
        }

        if buy_a {
            transfer_a(&e, to, out_a);
        } else {
            transfer_b(&e, to, out_b);
        }

        let new_reserve_a = balance_a - out_a;
        let new_reserve_b = balance_b - out_b;

        if new_reserve_a <= 0 || new_reserve_b <= 0 {
            panic!("new reserves must be strictly positive");
        }

        put_reserve_a(&e, new_reserve_a);
        put_reserve_b(&e, new_reserve_b);
    }

    // transfers share_amount of pool share tokens to this contract, burns all pools share tokens in this contracts, and sends the
    // corresponding amount of token_a and token_b to "to".
    // Returns amount of both tokens withdrawn
    pub fn withdraw(
        e: Env,
        to: Address,
        share_amount: i128,
        min_a: i128,
        min_b: i128,
    ) -> (i128, i128) {
        to.require_auth();

        let current_shares = get_shares(&e, &to);
        if current_shares < share_amount {
            panic!("insufficient shares");
        }

        let (balance_a, balance_b) = (get_balance_a(&e), get_balance_b(&e));
        let total_shares = get_total_shares(&e);

        // Calculate withdrawal amounts
        let out_a = (balance_a * share_amount) / total_shares;
        let out_b = (balance_b * share_amount) / total_shares;

        if out_a < min_a || out_b < min_b {
            panic!("min not satisfied");
        }

        burn_shares(&e, &to, share_amount);
        transfer_a(&e, to.clone(), out_a);
        transfer_b(&e, to, out_b);
        put_reserve_a(&e, balance_a - out_a);
        put_reserve_b(&e, balance_b - out_b);

        (out_a, out_b)
    }

    pub fn get_rsrvs(e: Env) -> (i128, i128) {
        (get_reserve_a(&e), get_reserve_b(&e))
    }
}
```

Ref: [https://github.com/stellar/soroban-examples/tree/v23.0.0/liquidity\_pool](https://github.com/stellar/soroban-examples/tree/v23.0.0/liquidity_pool)

## How it Works

Every asset created on Stellar starts with zero liquidity. The same is true of tokens created on Soroban (unless a Stellar asset with existing liquidity token has its [Stellar Asset Contract (SAC)](https://developers.stellar.org/docs/tokens/stellar-asset-contract.md) deployed for use in Soroban). In simple terms, "liquidity" means how much of an asset in a market is available to be bough or sold. In the "old days," you could generate liquidity in a market by creating buy/sell orders on an order book.

Liquidity pools automate this process by substituting the orders with math. Depositors into the liquidity pool earn fees from `swap` transactions. No orders required!

Open the [`liquidity_pool/src/lib.rs`](https://github.com/stellar/soroban-examples/blob/v23.0.0/liquidity_pool/src/lib.rs) file or see the code above to follow along.

### Initialize the Contract

When this contract is deployed, the `__constructor` function will automatically and atomically be invoked, so the following arguments must be passed in:

* **`token_a`:** The contract `Address` for an **already deployed** (or wrapped) token that will be held in reserve by the liquidity pool.
* **`token_b`:** The contract `Address` for an **already deployed** (or wrapped) token that will be held in reserve by the liquidity pool.

Bear in mind that which token is `token_a` and which is `token_b` is **not** an arbitrary distinction. In line with the Built-in Stellar liquidity pools, this contract can only make a single liquidity pool for a given set of tokens. So, the token addresses must be provided in [lexicographical order] at the time of initialization.

```rust title="liquidity_pool/src/lib.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
pub fn __constructor(e: Env, token_a: Address, token_b: Address) {
    if token_a >= token_b {
        panic!("token_a must be less than token_b");
    }

    put_token_a(&e, token_a);
    put_token_b(&e, token_b);
    put_total_shares(&e, 0);
    put_reserve_a(&e, 0);
    put_reserve_b(&e, 0);
}
```

[creating its own `pool` token]: #minting-and-burning-lp-shares

[interacting with contracts for `token_a` and `token_b`]: #token-transfers-tofrom-the-lp-contract

[lexicographical order]: https://developers.stellar.org/docs/learn/fundamentals/liquidity-on-stellar-sdex-liquidity-pools.md#liquidity-pool-participation

### A "Constant Product" Liquidity Pool

The *type* of liquidity pool this example contract implements is called a "constant product" liquidity pool. While this isn't the only type of liquidity pool out there, it is the most common variety. These liquidity pools are designed to keep the *total* value of each asset in *relative* equilibrium. The "product" in the constant product (also called an "invariant") will change every time the liquidity pool is interacted with (deposit, withdraw, or token swaps). However, the invariant **must** only increase with every interaction.

During a swap, what must be kept in mind is that for every withdrawal from the `token_a` side, you must "refill" the `token_b` side with a sufficient amount to keep the liquidity pool's price balanced. The math is predictable, but it is not linear. The more you take from one side, the more you must give on the opposite site *exponentially*.

Inside the `swap` function, the math is done like this (this is a simplified version, however):

```rust title="liquidity_pool/src/lib.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
pub fn swap(e: Env, to: Address, buy_a: bool, out: i128, in_max: i128) {
    // Get the current balances of both tokens in the liquidity pool
    let (reserve_sell, reserve_buy) = (get_reserve_a(&e), get_reserve_b(&e));

    // Calculate how much needs to be
    let n = reserve_sell * out * 1000;
    let d = (reserve_buy - out) * 997;
    let sell_amount = (n / d) + 1;
}
```

We have much more in-depth information about how this kind of liquidity pool works is available in [Stellar Quest: Series 3, Quest 5]. This is a really useful, interactive way to learn more about how the built-in Stellar liquidity pools work. Much of the knowledge you might gain from there will easily translate to this example contract.

[stellar quest: series 3, quest 5]: https://quest.stellar.org/learn/series/3/quest/5

### Interacting with Token Contracts in Another Contract

This liquidity pool contract will operate with a total of three different Soroban tokens:

* **Pool Shares:** This example uses a very simple share token given to asset depositors in exchange for their deposit. These tokens are "traded in" by the user when they withdraw some amount of their original deposit (plus any earned swap fees). In this simplified system, shares are just added/subtracted whenever a user deposits or withdraws the underlying assets. No distinct *token contract* will be used for these shares.
* **`token_a`** and **`token_b`**: Will be the two "reserve tokens" that users will deposit into the pool. These could be "wrapped" tokens from pre-existing Stellar assets, or they could be Soroban-native tokens. This contract doesn't really care, as long as the functions it needs from the common [Token Interface] are available in the token contract.

[token interface]: https://developers.stellar.org/docs/tokens/token-interface.md

#### Minting and Burning LP Shares

We are minting and burning LP shares within the logic of the main contract, instead of utilizing a distinct token contract. There are some "helper" functions created to facilitate this functionality. These functions are used when a user takes any kind of deposit or withdraw action.

```rust title="liquidity_pool/src/lib.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
fn burn_shares(e: &Env, from: &Address, amount: i128) {
    let current_shares = get_shares(e, from);
    if current_shares < amount {
        panic!("insufficient shares");
    }
    let total = get_total_shares(e);
    put_shares(e, from, current_shares - amount);
    put_total_shares(e, total - amount);
}

fn mint_shares(e: &Env, to: &Address, amount: i128) {
    let current_shares = get_shares(e, to);
    let total = get_total_shares(e);
    put_shares(e, to, current_shares + amount);
    put_total_shares(e, total + amount);
}
```

How is that number of shares calculated, you ask? Excellent question! If it's the very first deposit (see above), it's just the square root of the product of the quantities of `token_a` and `token_b` deposited. Very simple.

However, if there have already been deposits into the liquidity pool, and the user is just adding more tokens into the pool, there's a bit more math. However, the main point is that each depositor receives the same ratio of `POOL` tokens for their deposit as every other depositor.

```rust title=liquidity_pool/src/lib.rs theme={"theme":{"light":"github-light","dark":"github-dark"}}
fn deposit(e: Env, to: Address, desired_a: i128, min_a: i128, desired_b: i128, min_b: i128) {
    let zero = 0;
    let new_total_shares = if reserve_a > zero && reserve_b > zero {
        // Note balance_a and balance_b at this point in the function include
        // the tokens the user is currently depositing, whereas reserve_a and
        // reserve_b do not yet.
        let shares_a = (balance_a * total_shares) / reserve_a;
        let shares_b = (balance_b * total_shares) / reserve_b;
        shares_a.min(shares_b)
    } else {
        (balance_a * balance_b).sqrt()
    };
}
```

#### Token Transfers to/from the LP Contract

As we've already discussed, the liquidity pool contract will make use of the [Token Interface] available in the token contracts that were supplied as `token_a` and `token_b` arguments at the time of initialization. Throughout the rest of the contract, the liquidity pool will make use of that interface to make transfers of those tokens to/from itself.

What's happening is that as a user deposits tokens into the pool, and the contract invokes the `transfer` function to move the tokens from the `to` address (the depositor) to be held by the contract address. `POOL` tokens are then minted to depositor (see previous section). Pretty simple, right!?

```rust title="liquidity_pool/src/lib.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
fn deposit(e: Env, to: Address, desired_a: i128, min_a: i128, desired_b: i128, min_b: i128) {
    // Depositor needs to authorize the deposit
    to.require_auth();

    let token_a_client = token::Client::new(&e, &get_token_a(&e));
    let token_b_client = token::Client::new(&e, &get_token_b(&e));

    token_a_client.transfer(&to, &e.current_contract_address(), &amount_a);
    token_b_client.transfer(&to, &e.current_contract_address(), &amount_b);

    mint_shares(&e, to, new_total_shares - total_shares);
}
```

In contrast, when a user withdraws their deposited tokens, it's a bit more involved, and the following procedure happens.

1. The number of shares being "redeemed" by the user are checked against the *actual* amount of shares the user holds.
2. The withdraw amounts for the reserve tokens are calculated based on the amount of share tokens being redeemed.
3. The share tokens are burned now the withdraw amounts have been calculated, and they are no longer needed.
4. The respective amounts of `token_a` and `token_b` are transferred *from* the contract address into the `to` address (the depositor).

```rust title="liquidity_pool/src/lib.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
fn withdraw(e: Env, to: Address, share_amount: i128, min_a: i128, min_b: i128) -> (i128, i128) {
    to.require_auth();

    // First calculate the specified pool shares are available to the user
    let current_shares = get_shares(&e, &to);
    if current_shares < share_amount {
        panic!("insufficient shares");
    }

    // ... balances of pool shares and underlying assets are retrieved

    // Now calculate the withdraw amounts
    let out_a = (balance_a * balance_shares) / total_shares;
    let out_b = (balance_b * balance_shares) / total_shares;

    burn_shares(&e, balance_shares);
    transfer_a(&e, to.clone(), out_a);
    transfer_b(&e, to, out_b);
}
```

You'll notice that by holding the balance of `token_a` and `token_b` on the liquidity pool contract itself it makes, it very easy for us to perform any of the [Token Interface] actions inside the contract. As a bonus, any outside observer could query the balances of `token_a` or `token_b` held by the contract to verify the reserves are actually in line with the values the contract reports when its own `get_rsvs` function is invoked.

## Tests

Open the [`liquidity_pool/src/test.rs`] file to follow along.

```rust title="liquidity_pool/src/test.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
#![cfg(test)]
extern crate std;

use crate::LiquidityPoolClient;

use soroban_sdk::{
    symbol_short,
    testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation},
    token, Address, Env, IntoVal,
};

fn create_token_contract<'a>(
    e: &Env,
    admin: &Address,
) -> (token::Client<'a>, token::StellarAssetClient<'a>) {
    let sac = e.register_stellar_asset_contract_v2(admin.clone());
    (
        token::Client::new(e, &sac.address()),
        token::StellarAssetClient::new(e, &sac.address()),
    )
}

fn create_liqpool_contract<'a>(
    e: &Env,
    token_a: &Address,
    token_b: &Address,
) -> LiquidityPoolClient<'a> {
    LiquidityPoolClient::new(e, &e.register(crate::LiquidityPool {}, (token_a, token_b)))
}

#[test]
fn test() {
    let e = Env::default();
    e.mock_all_auths();

    let admin1 = Address::generate(&e);
    let admin2 = Address::generate(&e);

    let (token1, token1_admin) = create_token_contract(&e, &admin1);
    let (token2, token2_admin) = create_token_contract(&e, &admin2);
    let user1 = Address::generate(&e);

    let liqpool = create_liqpool_contract(&e, &token1.address, &token2.address);

    token1_admin.mint(&user1, &1000);
    assert_eq!(token1.balance(&user1), 1000);

    token2_admin.mint(&user1, &1000);
    assert_eq!(token2.balance(&user1), 1000);

    liqpool.deposit(&user1, &100, &100, &100, &100);
    assert_eq!(
        e.auths(),
        std::vec![(
            user1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    liqpool.address.clone(),
                    symbol_short!("deposit"),
                    (&user1, 100_i128, 100_i128, 100_i128, 100_i128).into_val(&e)
                )),
                sub_invocations: std::vec![
                    AuthorizedInvocation {
                        function: AuthorizedFunction::Contract((
                            token1.address.clone(),
                            symbol_short!("transfer"),
                            (&user1, &liqpool.address, 100_i128).into_val(&e)
                        )),
                        sub_invocations: std::vec![]
                    },
                    AuthorizedInvocation {
                        function: AuthorizedFunction::Contract((
                            token2.address.clone(),
                            symbol_short!("transfer"),
                            (&user1, &liqpool.address, 100_i128).into_val(&e)
                        )),
                        sub_invocations: std::vec![]
                    }
                ]
            }
        )]
    );

    assert_eq!(liqpool.balance_shares(&user1), 100);
    assert_eq!(token1.balance(&user1), 900);
    assert_eq!(token1.balance(&liqpool.address), 100);
    assert_eq!(token2.balance(&user1), 900);
    assert_eq!(token2.balance(&liqpool.address), 100);

    liqpool.swap(&user1, &false, &49, &100);
    assert_eq!(
        e.auths(),
        std::vec![(
            user1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    liqpool.address.clone(),
                    symbol_short!("swap"),
                    (&user1, false, 49_i128, 100_i128).into_val(&e)
                )),
                sub_invocations: std::vec![AuthorizedInvocation {
                    function: AuthorizedFunction::Contract((
                        token1.address.clone(),
                        symbol_short!("transfer"),
                        (&user1, &liqpool.address, 97_i128).into_val(&e)
                    )),
                    sub_invocations: std::vec![]
                }]
            }
        )]
    );

    assert_eq!(token1.balance(&user1), 803);
    assert_eq!(token1.balance(&liqpool.address), 197);
    assert_eq!(token2.balance(&user1), 949);
    assert_eq!(token2.balance(&liqpool.address), 51);

    e.cost_estimate().budget().reset_unlimited();
    liqpool.withdraw(&user1, &100, &197, &51);

    assert_eq!(
        e.auths(),
        std::vec![(
            user1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    liqpool.address.clone(),
                    symbol_short!("withdraw"),
                    (&user1, 100_i128, 197_i128, 51_i128).into_val(&e)
                )),
                sub_invocations: std::vec![]
            }
        )]
    );

    assert_eq!(token1.balance(&user1), 1000);
    assert_eq!(token2.balance(&user1), 1000);
    assert_eq!(liqpool.balance_shares(&user1), 0);
    assert_eq!(token1.balance(&liqpool.address), 0);
    assert_eq!(token2.balance(&liqpool.address), 0);
}

#[test]
#[should_panic]
fn deposit_amount_zero_should_panic() {
    let e = Env::default();
    e.mock_all_auths();

    // Create contracts
    let admin1 = Address::generate(&e);
    let admin2 = Address::generate(&e);

    let (token1, token1_admin) = create_token_contract(&e, &admin1);
    let (token2, token2_admin) = create_token_contract(&e, &admin2);
    let liqpool = create_liqpool_contract(&e, &token1.address, &token2.address);

    // Create a user
    let user1 = Address::generate(&e);

    token1_admin.mint(&user1, &1000);
    assert_eq!(token1.balance(&user1), 1000);

    token2_admin.mint(&user1, &1000);
    assert_eq!(token2.balance(&user1), 1000);

    liqpool.deposit(&user1, &1, &0, &0, &0);
}

#[test]
#[should_panic]
fn swap_reserve_one_nonzero_other_zero() {
    let e = Env::default();
    e.mock_all_auths();

    // Create contracts
    let admin1 = Address::generate(&e);
    let admin2 = Address::generate(&e);

    let (token1, token1_admin) = create_token_contract(&e, &admin1);
    let (token2, token2_admin) = create_token_contract(&e, &admin2);

    let liqpool = create_liqpool_contract(&e, &token1.address, &token2.address);

    // Create a user
    let user1 = Address::generate(&e);

    token1_admin.mint(&user1, &1000);
    assert_eq!(token1.balance(&user1), 1000);

    token2_admin.mint(&user1, &1000);
    assert_eq!(token2.balance(&user1), 1000);

    // Try to get to a situation where the reserves are 1 and 0.
    // It shouldn't be possible.
    token2.transfer(&user1, &liqpool.address, &1);
    liqpool.swap(&user1, &false, &1, &1);
}
```

[`liquidity_pool/src/test.rs`]: https://github.com/stellar/soroban-examples/tree/v23.0.0/liquidity_pool/src/test.rs

In any test, the first thing that is always required is an `Env`, which is the Soroban environment that the contract will run in.

```rust title="liquidity_pool/src/test.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
let e = Env::default();
```

We mock authentication checks in the tests, which allows the tests to proceed as if all users/addresses/contracts/etc. had successfully authenticated.

```rust title="liquidity_pool/src/test.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
e.mock_all_auths();
```

We have abstracted into a couple functions the tasks of creating token contracts and deploying a liquidity pool contract. Each are then used within the test.

```rust title="liquidity_pool/src/test.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
fn create_token_contract<'a>(
    e: &Env,
    admin: &Address,
) -> (token::Client<'a>, token::StellarAssetClient<'a>) {
    let sac = e.register_stellar_asset_contract_v2(admin.clone());
    (
        token::Client::new(e, &sac.address()),
        token::StellarAssetClient::new(e, &sac.address()),
    )
}

fn create_liqpool_contract<'a>(
    e: &Env,
    token_a: &Address,
    token_b: &Address,
) -> LiquidityPoolClient<'a> {
    LiquidityPoolClient::new(e, &e.register(crate::LiquidityPool {}, (token_a, token_b)))
}
```

All public functions within an `impl` block that is annotated with the `#[contractimpl]` attribute have a corresponding function generated in a generated client type. The client type will be named the same as the contract type with `Client` appended. For example, in our contract the contract type is `LiquidityPool`, and the client is named `LiquidityPoolClient`.

These tests examine the "typical" use-case of a liquidity pool, ensuring that the balances, returns, etc. are appropriate at various points during the test.

1. First, the test sets everything up with an `Env`, two admin addresses, two reserve tokens, a randomly generated address to act as the user of the liquidity pool, the liquidity pool itself, a pool token shares contract, and mints the reserve assets to the user address.
2. The user then deposits some of each asset into the liquidity pool. At this time, the following checks are done:
   * appropriate authorizations for deposits and transfers exist,
   * balances are checked for each token (`token_a`, `token_b`, and `POOL`) from both the user's perspective and the `liqpool` contract's perspective
3. The user performs a swap, buying `token_b` in exchange for `token_a`. The same checks as the previous step are made now, excepting the balances of `POOL`, since a swap has no effect on `POOL` tokens.
4. The user then withdraws all of the deposits it made, trading all of its `POOL` tokens in the process. The same checks are made here as were made in the `deposit` step.

## Build the Contract

To build the contract, use the `stellar contract build` command.

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract build
```

A `.wasm` file should be outputted in the `target` directory:

```
target/wasm32v1-none/release/soroban_liquidity_pool_contract.wasm
```

## Run the Contract

If you have [`stellar-cli`] installed, you can deploy the contract and invoke its functions.

### Deploy

> [tab] macOS/Linux
>
> ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
> stellar contract deploy \
>     --wasm target/wasm32v1-none/release/soroban_liquidity_pool_contract.wasm \
>     --alias liquidity_pool_example \
>     --source-account alice \
>     --network testnet
> ```

> [tab] Windows (PowerShell)
>
> ```powershell theme={"theme":{"light":"github-light","dark":"github-dark"}}
> stellar contract deploy `
>     --wasm target/wasm32v1-none/release/soroban_liquidity_pool_contract.wasm `
>     --alias liquidity_pool_example `
>     --source-account alice `
>     --network testnet
> ```

### Invoke

> [tab] macOS/Linux
>
> ```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
> stellar contract invoke \
>     --id liquidity_pool_example \
>     --source-account alice \
>     --network testnet \
>     -- \
>     deposit \
>     --to GBZV3NONYSUDVTEHATQO4BCJVFXJO3XQU5K32X3XREVZKSMMOZFO4ZXR \
>     --desired_a 100 \
>     --min_a 98 \
>     --desired_be 200 \
>     --min_b 196
> ```

> [tab] Windows (PowerShell)
>
> ```powershell theme={"theme":{"light":"github-light","dark":"github-dark"}}
> stellar contract invoke `
>     --id liquidity_pool_example `
>     --source-account alice `
>     --network testnet `
>     -- `
>     deposit `
>     --to GBZV3NONYSUDVTEHATQO4BCJVFXJO3XQU5K32X3XREVZKSMMOZFO4ZXR `
>     --desired_a 100 `
>     --min_a 98 `
>     --desired_be 200 `
>     --min_b 196
> ```

[`stellar-cli`]: ../../../tools/cli/stellar-cli
