Adding supply control to a soroban token
Tags: soroban, stellar, token, tokenization
Published: 2024-04-09 12:30:36

## Introduction

In the realm of tokenization projects, supply refers to the total number of tokens that will ever be minted.
A fixed token supply ensures there will never be more tokens minted than what was initially established. This can be crucial for projects where a predetermined value is assigned to each token. For instance, a project that tokenizes a single, high-value property might leverage a fixed supply to represent ownership. Let's say the property is valued at $1 million, and the project creates 1 million tokens. Each token would then represent $1 of ownership in the property. By setting a limit on the number of tokens, scarcity is introduced, potentially driving up the value of each individual token.

In the next sections, we are going to learn how to incorporate supply capabilities into the [soroban token example](https://github.com/stellar/soroban-examples/tree/v20.0.0/token).

> You can see the complete code [here](https://github.com/icolomina/soroban-examples/tree/token_with_supply/token_with_supply)

## Adding the Supply module
The soroban token example is divided in several modules. This makes the code more readable and easier for other programmers to understand. The main code is located in the *contract.rs* module which contains the contract implementation and uses the other modules functions.
Let's first add a new variant to the *DataKey* enum which is located in the [storage_types](https://github.com/icolomina/soroban-examples/blob/token_supply/token/src/storage_types.rs) module.

```rust
#[derive(Clone)]
#[contracttype]
pub enum DataKey {
    Allowance(AllowanceDataKey),
    Balance(Address),
    Nonce(Address),
    State(Address),
    Admin,
    Supply
}
```
Now, let's create a supply module which will use the *Supply* DataKey variant to create and manage the supply storage key. 

```rust
use soroban_sdk::{Env};
use crate::storage_types::DataKey;

pub fn read_supply(e: &Env) -> i128 {
    let key = DataKey::Supply;
    e.storage().instance().get(&key).unwrap()
}

pub fn write_supply(e: &Env, amount: &i128) {
    let key = DataKey::Supply;
    e.storage().instance().set(&key, amount);
}

pub fn decrement_supply(e: &Env, amount: &i128) {
    let key = DataKey::Supply;
    let current_supply: i128 = e.storage().instance().get(&key).unwrap();
    let new_supply = current_supply - amount;
    
    e.storage().instance().set(&key, &new_supply);
}

pub fn increment_supply(e: &Env, amount: &i128) {
    let key = DataKey::Supply;
    let current_supply: i128 = e.storage().instance().get(&key).unwrap();
    let new_supply = current_supply + amount;
    
    e.storage().instance().set(&key, &new_supply);
}
```
The supply module includes the following functions:

- **read_supply**: Gets the token supply from the storage.
- **write_supply**: Sets the token supply on the storage.
- **decrement_supply**: Decrements the token supply. This function will be called after minting an address with tokens.
- **increment_supply**: Increments the token supply. It will be called after burning tokens so that those tokens are removed from the circulation but they can be minted again.

Finally, we have to add the module to the *lib.rs* file:

```rust
#![no_std]

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

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

## Write supply when initialize the token
The *initialize* function can be executed once and configures the following token parameters:
- Decimals.
- Symbol.
- Token administrator address.

We have to modify this function so that:

- It receives the supply as parameter.
- It writes the supply to the storage.

```rust
pub fn initialize(e: Env, admin: Address, decimal: u32, name: String, symbol: String, supply: i128) {
    if has_administrator(&e) {
        panic!("already initialized")
    }
    write_administrator(&e, &admin);
    if decimal > u8::MAX.into() {
        panic!("Decimal must fit in a u8");
    }

    if supply <= 0 {
        panic!("Supply must be greater than 0");
    }

    write_supply(&e, &supply);
    write_metadata(
        &e,
        TokenMetadata {
            decimal,
            name,
            symbol,
        },
    )
}
```
Before writing the supply, the function checks that the supply amount received is greater than 0. If not, the function panics. 

## Decrement supply after minting
Having the supply module ready, we have to modify the mint function with the following new functionality :
- Checks whether there is enough supply to mint the address with the specified amount
- Decrements the available supply after minting an address.

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

    if amount > read_supply(&e) {
        panic!("Amount greater than remaining supply");
    }

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

    receive_balance(&e, to.clone(), amount);
    decrement_supply(&e, &amount);
    TokenUtils::new(&e).events().mint(admin, to, amount);
}
```
The mint function uses the *receive_supply* function from the supply module to get the current supply. If there isn't enough supply the function panics. If there is enough supply, it decrements the supply with the amount minted.

## Create a supply function
We need a function in the contract which returns the current supply:

```rust
pub fn supply(e: Env) -> i128 {
    e.storage()
        .instance()
        .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT);
        
    read_supply(&e)
}
```

It uses the *read_supply* function from the supply module to get the current supply.

## Increments supply after burning
Every time that an amount of tokens are burned they are removed from the circulation. As we are controlling the supply, we have to increment the supply after burning so those tokens can be minted again in the future. To do it, we have to modify both *burn* and *burn_from* functions.

```rust
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);
   increment_supply(&e, &amount);
   TokenUtils::new(&e).events().burn(from, amount);
}
    
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);
   increment_supply(&e, &amount);
   TokenUtils::new(&e).events().burn(from, amount)
}
```
As shown, after spending the balance the supply is incremented with the amount burned.

## Conclusion
In this article, we've learned how to add supply capabilities to a soroban token so that only can be minted a limited number of tokens. This article shows a fixed supply example, that is, once the contract is initialized, the supply cannot be modified.
There could be another use cases where the supply would be dynamic (it could be modified following some rules). In this case, we would add a function to the contract to set the supply. Obviously, only the contract administrator should be able to modify the supply.