> ## 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.

# Tokens

> Write a CAP-46-6 compliant token contract.

The [token example] demonstrates how to write a token contract that implements the [Token Interface].

> [!TIP]
>
> This example shows how to build a token from scratch using only the `soroban-sdk`. For many use cases, issuing a Stellar asset and using its built-in [Stellar Asset Contract (SAC)](https://developers.stellar.org/docs/tokens/stellar-asset-contract.md) is all you need. If you want a custom token contract, the fastest path is the audited [OpenZeppelin Stellar Contracts](https://github.com/OpenZeppelin/stellar-contracts) library, which has easy-to-follow examples in its docs for [fungible tokens](https://docs.openzeppelin.com/stellar-contracts/tokens/fungible/fungible) and [non-fungible tokens (NFTs)](https://docs.openzeppelin.com/stellar-contracts/tokens/non-fungible/non-fungible).

[![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

[token example]: https://github.com/stellar/soroban-examples/tree/main/token

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

> [!NOTE]
>
> **Whisk Changes**
>
> With the release of Whisk, Protocol 23, the \[token interface] has seen some changes to incorporate the `MuxedAddress` type into the `transfer` function. Please see the [Rust SDK documentation](https://docs.rs/soroban-token-sdk/latest/soroban_token_sdk/_migrating/v23_token_transfer/index.html) for more details.

## Run the Example

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

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
git clone 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 `hello_world` directory, and use `cargo test`.

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

You should see the output:

```
running 6 tests
test test::decimal_is_over_eighteen - should panic ... ok
test test::test_zero_allowance ... ok
test test::transfer_insufficient_balance - should panic ... ok
test test::transfer_from_insufficient_allowance - should panic ... ok
test test::test_burn ... ok
test test::test ... ok
```

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

## Code

> [!NOTE]
>
> The source code for this \[token example] is broken into several smaller modules. This is a common design pattern for more complex smart contracts.

**lib**

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

mod admin;
mod allowance;
mod balance;
mod contract;
mod metadata;
mod storage_types;
mod test;

pub use crate::contract::TokenClient;
```

**admin**

```rust title="token/src/admin.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
use soroban_sdk::{Address, Env};

use crate::storage_types::DataKey;

pub fn read_administrator(e: &Env) -> Address {
    let key = DataKey::Admin;
    e.storage().instance().get(&key).unwrap()
}

pub fn write_administrator(e: &Env, id: &Address) {
    let key = DataKey::Admin;
    e.storage().instance().set(&key, id);
}
```

**allowance**

```rust title="token/src/allowance.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
use crate::storage_types::{AllowanceDataKey, AllowanceValue, DataKey};
use soroban_sdk::{Address, Env};

pub fn read_allowance(e: &Env, from: Address, spender: Address) -> AllowanceValue {
    let key = DataKey::Allowance(AllowanceDataKey { from, spender });
    if let Some(allowance) = e.storage().temporary().get::<_, AllowanceValue>(&key) {
        if allowance.live_until_ledger < e.ledger().sequence() {
            AllowanceValue {
                amount: 0,
                live_until_ledger: allowance.live_until_ledger,
            }
        } else {
            allowance
        }
    } else {
        AllowanceValue {
            amount: 0,
            live_until_ledger: 0,
        }
    }
}

pub fn write_allowance(
    e: &Env,
    from: Address,
    spender: Address,
    amount: i128,
    live_until_ledger: u32,
) {
    let allowance = AllowanceValue {
        amount,
        live_until_ledger,
    };

    if amount > 0 && live_until_ledger < e.ledger().sequence() {
        panic!("live_until_ledger is less than ledger seq when amount > 0")
    }

    let key = DataKey::Allowance(AllowanceDataKey { from, spender });
    e.storage().temporary().set(&key.clone(), &allowance);

    if amount > 0 {
        let live_for = live_until_ledger
            .checked_sub(e.ledger().sequence())
            .unwrap();

        e.storage().temporary().extend_ttl(&key, live_for, live_for)
    }
}

pub fn spend_allowance(e: &Env, from: Address, spender: Address, amount: i128) {
    let allowance = read_allowance(e, from.clone(), spender.clone());
    if allowance.amount < amount {
        panic!("insufficient allowance");
    }
    if amount > 0 {
        write_allowance(
            e,
            from,
            spender,
            allowance.amount - amount,
            allowance.live_until_ledger,
        );
    }
}
```

**balance**

```rust title="token/src/balance.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
use crate::storage_types::{DataKey, BALANCE_BUMP_AMOUNT, BALANCE_LIFETIME_THRESHOLD};
use soroban_sdk::{Address, Env};

pub fn read_balance(e: &Env, addr: Address) -> i128 {
    let key = DataKey::Balance(addr);
    if let Some(balance) = e.storage().persistent().get::<DataKey, i128>(&key) {
        e.storage()
            .persistent()
            .extend_ttl(&key, BALANCE_LIFETIME_THRESHOLD, BALANCE_BUMP_AMOUNT);
        balance
    } else {
        0
    }
}

fn write_balance(e: &Env, addr: Address, amount: i128) {
    let key = DataKey::Balance(addr);
    e.storage().persistent().set(&key, &amount);
    e.storage()
        .persistent()
        .extend_ttl(&key, BALANCE_LIFETIME_THRESHOLD, BALANCE_BUMP_AMOUNT);
}

pub fn receive_balance(e: &Env, addr: Address, amount: i128) {
    let balance = read_balance(e, addr.clone());
    write_balance(e, addr, balance + amount);
}

pub fn spend_balance(e: &Env, addr: Address, amount: i128) {
    let balance = read_balance(e, addr.clone());
    if balance < amount {
        panic!("insufficient balance");
    }
    write_balance(e, addr, balance - amount);
}
```

**contract**

```rust title="token/src/contract.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
//! This contract demonstrates a sample implementation of the Soroban token
//! interface.
use crate::admin::{read_administrator, write_administrator};
use crate::allowance::{read_allowance, spend_allowance, write_allowance};
use crate::balance::{read_balance, receive_balance, spend_balance};
use crate::metadata::{read_decimal, read_name, read_symbol, write_metadata};
#[cfg(test)]
use crate::storage_types::{AllowanceDataKey, AllowanceValue, DataKey};
use crate::storage_types::{INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD};
use soroban_sdk::{
    contract, contractevent, contractimpl, token::TokenInterface, Address, Env, MuxedAddress,
    String,
};
use soroban_token_sdk::events;
use soroban_token_sdk::metadata::TokenMetadata;

fn check_nonnegative_amount(amount: i128) {
    if amount < 0 {
        panic!("negative amount is not allowed: {}", amount)
    }
}

#[contract]
pub struct Token;

// SetAdmin is not a standardized token event, so we just define a custom event
// for our token.
#[contractevent(data_format = "single-value")]
pub struct SetAdmin {
    #[topic]
    admin: Address,
    new_admin: Address,
}

#[contractimpl]
impl Token {
    pub fn __constructor(e: Env, admin: Address, decimal: u32, name: String, symbol: String) {
        if decimal > 18 {
            panic!("Decimal must not be greater than 18");
        }
        write_administrator(&e, &admin);
        write_metadata(
            &e,
            TokenMetadata {
                decimal,
                name,
                symbol,
            },
        )
    }

    pub fn mint(e: Env, to: Address, amount: i128) {
        check_nonnegative_amount(amount);
        let admin = read_administrator(&e);
        admin.require_auth();

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        receive_balance(&e, to.clone(), amount);
        events::MintWithAmountOnly { to, amount }.publish(&e);
    }

    pub fn set_admin(e: Env, new_admin: Address) {
        let admin = read_administrator(&e);
        admin.require_auth();

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        write_administrator(&e, &new_admin);
        SetAdmin { admin, new_admin }.publish(&e);
    }

    #[cfg(test)]
    pub fn get_allowance(e: Env, from: Address, spender: Address) -> Option<AllowanceValue> {
        let key = DataKey::Allowance(AllowanceDataKey { from, spender });
        let allowance = e.storage().temporary().get::<_, AllowanceValue>(&key);
        allowance
    }
}

#[contractimpl(contracttrait)]
impl TokenInterface for Token {
    fn allowance(e: Env, from: Address, spender: Address) -> i128 {
        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);
        read_allowance(&e, from, spender).amount
    }

    fn approve(e: Env, from: Address, spender: Address, amount: i128, live_until_ledger: u32) {
        from.require_auth();

        check_nonnegative_amount(amount);

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        write_allowance(&e, from.clone(), spender.clone(), amount, live_until_ledger);
        events::Approve {
            from,
            spender,
            amount,
            live_until_ledger,
        }
        .publish(&e);
    }

    fn balance(e: Env, id: Address) -> i128 {
        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);
        read_balance(&e, id)
    }

    fn transfer(e: Env, from: Address, to_muxed: MuxedAddress, amount: i128) {
        from.require_auth();

        check_nonnegative_amount(amount);

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        spend_balance(&e, from.clone(), amount);
        let to: Address = to_muxed.address();
        receive_balance(&e, to.clone(), amount);
        events::Transfer {
            from,
            to,
            to_muxed_id: to_muxed.id(),
            amount,
        }
        .publish(&e);
    }

    fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128) {
        spender.require_auth();

        check_nonnegative_amount(amount);

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        spend_allowance(&e, from.clone(), spender, amount);
        spend_balance(&e, from.clone(), amount);
        receive_balance(&e, to.clone(), amount);
        events::Transfer {
            from,
            to,
            // `transfer_from` does not support muxed destination.
            to_muxed_id: None,
            amount,
        }
        .publish(&e);
    }

    fn burn(e: Env, from: Address, amount: i128) {
        from.require_auth();

        check_nonnegative_amount(amount);

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        spend_balance(&e, from.clone(), amount);
        events::Burn { from, amount }.publish(&e);
    }

    fn burn_from(e: Env, spender: Address, from: Address, amount: i128) {
        spender.require_auth();

        check_nonnegative_amount(amount);

        e.storage()
            .instance()
            .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

        spend_allowance(&e, from.clone(), spender, amount);
        spend_balance(&e, from.clone(), amount);
        events::Burn { from, amount }.publish(&e);
    }

    fn decimals(e: Env) -> u32 {
        read_decimal(&e)
    }

    fn name(e: Env) -> String {
        read_name(&e)
    }

    fn symbol(e: Env) -> String {
        read_symbol(&e)
    }
}
```

**metadata**

```rust title="token/src/metadata.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
use soroban_sdk::{Env, String};
use soroban_token_sdk::{metadata::TokenMetadata, TokenUtils};

pub fn read_decimal(e: &Env) -> u32 {
    let util = TokenUtils::new(e);
    util.metadata().get_metadata().decimal
}

pub fn read_name(e: &Env) -> String {
    let util = TokenUtils::new(e);
    util.metadata().get_metadata().name
}

pub fn read_symbol(e: &Env) -> String {
    let util = TokenUtils::new(e);
    util.metadata().get_metadata().symbol
}

pub fn write_metadata(e: &Env, metadata: TokenMetadata) {
    let util = TokenUtils::new(e);
    util.metadata().set_metadata(&metadata);
}
```

**storage\_types**

```rust title="token/src/storage_types.rs" theme={"theme":{"light":"github-light","dark":"github-dark"}}
use soroban_sdk::{contracttype, Address};

pub(crate) const DAY_IN_LEDGERS: u32 = 17280;
pub(crate) const INSTANCE_BUMP_AMOUNT: u32 = 7 * DAY_IN_LEDGERS;
pub(crate) const INSTANCE_LIFETIME_THRESHOLD: u32 = INSTANCE_BUMP_AMOUNT - DAY_IN_LEDGERS;

pub(crate) const BALANCE_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS;
pub(crate) const BALANCE_LIFETIME_THRESHOLD: u32 = BALANCE_BUMP_AMOUNT - DAY_IN_LEDGERS;

#[derive(Clone)]
#[contracttype]
pub struct AllowanceDataKey {
    pub from: Address,
    pub spender: Address,
}

#[contracttype]
pub struct AllowanceValue {
    pub amount: i128,
    pub live_until_ledger: u32,
}

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
    Allowance(AllowanceDataKey),
    Balance(Address),
    State(Address),
    Admin,
}
```

Ref: [https://github.com/stellar/soroban-examples/tree/main/token](https://github.com/stellar/soroban-examples/tree/main/token)

## How it Works

Tokens created on a smart contract platform can take many different forms, include a variety of different functionalities, and meet very different needs or use-cases. While each token can fulfill a unique niche, there are some "normal" features that almost all tokens will need to make use of (e.g., payments, transfers, balance queries, etc.). In an effort to minimize repetition and streamline token deployments, Soroban implements the [Token Interface], which provides a uniform, predictable interface for developers and users.

Creating a Soroban token compatible contract from an existing Stellar asset is very easy, it requires deploying the built-in [Stellar Asset Contract].

This example contract, however, demonstrates how a smart contract token might be constructed that doesn't take advantage of the Stellar Asset Contract, but does still satisfy the commonly used Token Interface to maximize interoperability.

[stellar asset contract]: https://developers.stellar.org/docs/tokens/stellar-asset-contract.md

### Separation of Functionality

You have likely noticed that this example contract is broken into discrete modules, with each one responsible for a siloed set of functionality. This common practice helps to organize the code and make it more maintainable.

For example, most of the token logic exists in the `contract.rs` module. Functions like `mint`, `burn`, `transfer`, etc. are written and programmed in that file. The Token Interface describes how some of these functions should emit events when they occur. However, keeping all that event-emitting logic bundled in with the rest of the contract code could make it harder to track what is happening in the code, and that confusion could ultimately lead to errors.

Instead, we have a separate `soroban_token_sdk::events` module that takes away all the headache of emitting events when other functions run. Here is the event emitted when a token is minted:

```rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
events::MintWithAmountOnly { to, amount }.publish(&e);
```

Admittedly, this is a simple example, but constructing the contract this way makes it very clear to the developer what is happening and where. This function is then used by the `contract.rs` module whenever the `mint` function is invoked:

```rust highlight={14} theme={"theme":{"light":"github-light","dark":"github-dark"}}
// earlier in `contract.rs`
use soroban_token_sdk::events;

pub fn mint(e: Env, to: Address, amount: i128) {
    check_nonnegative_amount(amount);
    let admin = read_administrator(&e);
    admin.require_auth();

    e.storage()
        .instance()
        .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);

    receive_balance(&e, to.clone(), amount);
    events::MintWithAmountOnly { to, amount }.publish(&e);
}
```

This same convention is used to separate from the "main" contract code the metadata for the token, the storage type definitions, etc.

### Standardized Interface, Customized Behavior

This example contract follows the standardized [Token Interface], implementing all of the same functions as the [Stellar Asset Contract] (SAC). This gives wallets, users, developers, etc. a predictable interface to interact with the token. Even though we are implementing the same *interface* of functions, that doesn't mean we have to implement the same *behavior* inside those functions. While this example contract doesn't actually modify any of the functions that would be present in a deployed instance of the SAC, that possibility remains open to the contract developer.

By way of example, perhaps you have an NFT project, and the artist wants to have a small royalty paid every time their token transfers hands:

```rust highlight={9-17} theme={"theme":{"light":"github-light","dark":"github-dark"}}
// This is mainly the `transfer` function from `token/src/contract.rs`
fn transfer(e: Env, from: Address, to_muxed: MuxedAddress, amount: i128) {
    from.require_auth();

    check_nonnegative_amount(amount);
    spend_balance(&e, from.clone(), amount);
    let to: Address = to_muxed.address();

    // We calculate some new amounts for payment and royalty
    let payment = (amount * 997) / 1000;
    let royalty = amount - payment
    let artist = read_artist(&e);
    receive_balance(&e, artist.clone(), royalty);
    events::TransferWithAmountOnly {
        to: artist.clone(),
        amount: royalty,
    }.publish(&e);
    receive_balance(&e, to.clone(), payment);
    events::Transfer {
        from,
        to,
        to_muxed_id: to_muxed.id(),
        amount,
    }
    .publish(&e);
}
```

The `transfer` interface is still in use, and is still the same as other tokens, but we've customized the behavior to address a specific need. Another use-case might be a tightly controlled token that requires authentication from an admin before any `transfer`, `allowance`, etc. function could be invoked.

> [!TIP]
>
> Of course, you will want your token to behave in an *intuitive* and *transparent* manner. If a user is invoking a `transfer`, they will expect tokens to move. If an asset issuer needs to invoke a `clawback` they will likely *require* the right kind of behavior to take place.

## Tests

Open the [`token/src/test.rs`](https://github.com/stellar/soroban-examples/tree/main/token/src/test.rs) file to follow along.

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

use crate::{contract::Token, TokenClient};
use soroban_sdk::{
    symbol_short,
    testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation},
    Address, Env, FromVal, IntoVal, String, Symbol,
};

fn create_token<'a>(e: &Env, admin: &Address) -> TokenClient<'a> {
    let token_contract = e.register(
        Token,
        (
            admin,
            7_u32,
            String::from_val(e, &"name"),
            String::from_val(e, &"symbol"),
        ),
    );
    TokenClient::new(e, &token_contract)
}

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

    let admin1 = Address::generate(&e);
    let admin2 = Address::generate(&e);
    let user1 = Address::generate(&e);
    let user2 = Address::generate(&e);
    let user3 = Address::generate(&e);
    let token = create_token(&e, &admin1);

    token.mint(&user1, &1000);
    assert_eq!(
        e.auths(),
        std::vec![(
            admin1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("mint"),
                    (&user1, 1000_i128).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );
    assert_eq!(token.balance(&user1), 1000);

    token.approve(&user2, &user3, &500, &200);
    assert_eq!(
        e.auths(),
        std::vec![(
            user2.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("approve"),
                    (&user2, &user3, 500_i128, 200_u32).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );
    assert_eq!(token.allowance(&user2, &user3), 500);

    token.transfer(&user1, &user2, &600);
    assert_eq!(
        e.auths(),
        std::vec![(
            user1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("transfer"),
                    (&user1, &user2, 600_i128).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );
    assert_eq!(token.balance(&user1), 400);
    assert_eq!(token.balance(&user2), 600);

    token.transfer_from(&user3, &user2, &user1, &400);
    assert_eq!(
        e.auths(),
        std::vec![(
            user3.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    Symbol::new(&e, "transfer_from"),
                    (&user3, &user2, &user1, 400_i128).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );
    assert_eq!(token.balance(&user1), 800);
    assert_eq!(token.balance(&user2), 200);

    token.transfer(&user1, &user3, &300);
    assert_eq!(token.balance(&user1), 500);
    assert_eq!(token.balance(&user3), 300);

    token.set_admin(&admin2);
    assert_eq!(
        e.auths(),
        std::vec![(
            admin1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("set_admin"),
                    (&admin2,).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );

    // Increase to 500
    token.approve(&user2, &user3, &500, &200);
    assert_eq!(token.allowance(&user2, &user3), 500);
    token.approve(&user2, &user3, &0, &200);
    assert_eq!(
        e.auths(),
        std::vec![(
            user2.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("approve"),
                    (&user2, &user3, 0_i128, 200_u32).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );
    assert_eq!(token.allowance(&user2, &user3), 0);
}

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

    let admin = Address::generate(&e);
    let user1 = Address::generate(&e);
    let user2 = Address::generate(&e);
    let token = create_token(&e, &admin);

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

    token.approve(&user1, &user2, &500, &200);
    assert_eq!(token.allowance(&user1, &user2), 500);

    token.burn_from(&user2, &user1, &500);
    assert_eq!(
        e.auths(),
        std::vec![(
            user2.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("burn_from"),
                    (&user2, &user1, 500_i128).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );

    assert_eq!(token.allowance(&user1, &user2), 0);
    assert_eq!(token.balance(&user1), 500);
    assert_eq!(token.balance(&user2), 0);

    token.burn(&user1, &500);
    assert_eq!(
        e.auths(),
        std::vec![(
            user1.clone(),
            AuthorizedInvocation {
                function: AuthorizedFunction::Contract((
                    token.address.clone(),
                    symbol_short!("burn"),
                    (&user1, 500_i128).into_val(&e),
                )),
                sub_invocations: std::vec![]
            }
        )]
    );

    assert_eq!(token.balance(&user1), 0);
    assert_eq!(token.balance(&user2), 0);
}

#[test]
#[should_panic(expected = "insufficient balance")]
fn transfer_insufficient_balance() {
    let e = Env::default();
    e.mock_all_auths();

    let admin = Address::generate(&e);
    let user1 = Address::generate(&e);
    let user2 = Address::generate(&e);
    let token = create_token(&e, &admin);

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

    token.transfer(&user1, &user2, &1001);
}

#[test]
#[should_panic(expected = "insufficient allowance")]
fn transfer_from_insufficient_allowance() {
    let e = Env::default();
    e.mock_all_auths();

    let admin = Address::generate(&e);
    let user1 = Address::generate(&e);
    let user2 = Address::generate(&e);
    let user3 = Address::generate(&e);
    let token = create_token(&e, &admin);

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

    token.approve(&user1, &user3, &100, &200);
    assert_eq!(token.allowance(&user1, &user3), 100);

    token.transfer_from(&user3, &user1, &user2, &101);
}

#[test]
#[should_panic(expected = "Decimal must not be greater than 18")]
fn decimal_is_over_eighteen() {
    let e = Env::default();
    let admin = Address::generate(&e);
    let _ = TokenClient::new(
        &e,
        &e.register(
            Token,
            (
                admin,
                19_u32,
                String::from_val(&e, &"name"),
                String::from_val(&e, &"symbol"),
            ),
        ),
    );
}

#[test]
fn test_zero_allowance() {
    // Here we test that transfer_from with a 0 amount does not create an empty allowance
    let e = Env::default();
    e.mock_all_auths();

    let admin = Address::generate(&e);
    let spender = Address::generate(&e);
    let from = Address::generate(&e);
    let token = create_token(&e, &admin);

    token.transfer_from(&spender, &from, &spender, &0);
    assert!(token.get_allowance(&from, &spender).is_none());
}
```

The token example implements six different tests to cover a wide array of potential behaviors and problems. However, all of the tests start with a few common pieces. 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 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 theme={"theme":{"light":"github-light","dark":"github-dark"}}
e.mock_all_auths();
```

We're also using a `create_token` function to ease the repetition of having to register our token contract. The resulting `token` client is then used to invoke the contract during each test.

```rust theme={"theme":{"light":"github-light","dark":"github-dark"}}
// It is defined at the top of the file...
fn create_token<'a>(e: &Env, admin: &Address) -> TokenClient<'a> {
    let token_contract = e.register(
        Token,
        (
            admin,
            7_u32,
            String::from_val(e, &"name"),
            String::from_val(e, &"symbol"),
        ),
    );
    TokenClient::new(e, &token_contract)
}

// ... and it is used inside each test
let token = create_token(&e, &admin);
```

All public functions within an `impl` block that has been annotated with the `#[contractimpl]` attribute will have a corresponding function in the test's 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 named `Token`, and the client type is named `TokenClient`.

The six tests created for this example contract test a range of possible conditions and ensure the contract responds appropriately to each one:

* **`test()`** - This function makes use of a variety of the built-in token functions to test the "predictable" way an asset might be interacted with by a user, as well as an administrator.
* **`test_burn()`** - This function ensures a `burn()` invocation decreases a user's balance, and that a `burn_from()` invocation decreases a user's balance as well as consuming another user's allowance of that balance.
* **`transfer_insufficient_balance()`** - This function ensures a `transfer()` invocation panics when the `from` user doesn't have the balance to cover it.
* **`transfer_from_insufficient_allowance()`** - This function ensures a user with an existing allowance for someone else's balance cannot make a `transfer()` greater than that allowance.
* **`decimal_is_over_eighteen()`** - This function tests that constructing a token with too high of a decimal precision will not succeed.
* **`test_zero_allowance()`** - This function makes sure that a `transfer_from()` with an zero balance doesn't create an empty allowance.

## 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:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
target/wasm32v1-none/release/soroban_token_contract.wasm
```

## Run the Contract

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

### Deploy

**macOS/Linux**

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract deploy \
    --wasm target/wasm32v1-none/release/soroban_token_contract.wasm \
    --alias token_example \
    --source-account alice \
    --network testnet
```

**Windows (PowerShell)**

```powershell theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract deploy `
    --wasm target/wasm32v1-none/release/soroban_token_contract.wasm `
    --alias token_example `
    --source-account alice `
    --network testnet
```

### Invoke

**macOS/Linux**

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract invoke \
    --id token_example \
    --source-account alice \
    --network testnet \
    -- \
    balance \
    --id GBZV3NONYSUDVTEHATQO4BCJVFXJO3XQU5K32X3XREVZKSMMOZFO4ZXR
```

**Windows (PowerShell)**

```powershell theme={"theme":{"light":"github-light","dark":"github-dark"}}
stellar contract invoke `
    --id token_example `
    --source-account alice `
    --network testnet `
    -- `
    balance `
    --id GBZV3NONYSUDVTEHATQO4BCJVFXJO3XQU5K32X3XREVZKSMMOZFO4ZXR
```

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