Soroban Contracts 101 : Tokens (Part 2)
Tags: soroban, sorobanathon, stellar, smartcontract
Published: 2023-03-20 18:57:30

- **`events.rs`**

Contains event emitting logic

```rust

use soroban_sdk::{Address, Env, Symbol};

pub(crate) fn increase_allowance(e: &Env, from: Address, to: Address, amount: i128) {
    let topics = (Symbol::new(e, "increase_allowance"), from, to);
    e.events().publish(topics, amount);
}

pub(crate) fn decrease_allowance(e: &Env, from: Address, to: Address, amount: i128) {
    let topics = (Symbol::new(e, "decrease_allowance"), from, to);
    e.events().publish(topics, amount);
}

pub(crate) fn transfer(e: &Env, from: Address, to: Address, amount: i128) {
    let topics = (Symbol::short("transfer"), from, to);
    e.events().publish(topics, amount);
}

pub(crate) fn mint(e: &Env, admin: Address, to: Address, amount: i128) {
    let topics = (Symbol::short("mint"), admin, to);
    e.events().publish(topics, amount);
}

pub(crate) fn clawback(e: &Env, admin: Address, from: Address, amount: i128) {
    let topics = (Symbol::short("clawback"), admin, from);
    e.events().publish(topics, amount);
}

pub(crate) fn set_authorized(e: &Env, admin: Address, id: Address, authorize: bool) {
    let topics = (Symbol::new(e, "set_authorized"), admin, id);
    e.events().publish(topics, authorize);
}

pub(crate) fn set_admin(e: &Env, admin: Address, new_admin: Address) {
    let topics = (Symbol::short("set_admin"), admin);
    e.events().publish(topics, new_admin);
}

pub(crate) fn burn(e: &Env, from: Address, amount: i128) {
    let topics = (Symbol::short("burn"), from);
    e.events().publish(topics, amount);
}

```

This module contains implementations of functions that emit events from the token contract. Events allow light clients to track state changes on the blockchain without needing to fully verify all transactions.

The functions are:

`increase_allowance` - Emits an event when an allowance is increased
`decrease_allowance` - Emits an event when an allowance is decreased
`transfer` - Emits an event when tokens are transferred
`mint` - Emits an event when tokens are minted
`clawback` - Emits an event when tokens are clawed back
`set_authorized` - Emits an event when an account's authorization is changed
`set_admin` - Emits an event when the admin is changed
`burn` - Emits an event when tokens are burned

Each function requires various arguments (Env, addresses, amounts, authorize flag) and publishes an event using the Env e argument, specifying topics ( identifiers for the event type and relevant addresses/admin) and data (the amount or authorize flag).

- **`lib.rs`**

This is our main crate file
```rust
#![no_std]

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

pub use crate::contract::TokenClient;
```
This file imports and exports the key modules (functionality groupings) of the token crate, making the main `TokenClient` type available for use by other crates.

- **`metadata.rs`**

Contains metadata logic (reading/writing name, symbol, decimals)

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

pub fn read_decimal(e: &Env) -> u32 {
    let key = DataKey::Decimals;
    e.storage().get_unchecked(&key).unwrap()
}

pub fn write_decimal(e: &Env, d: u8) {
    let key = DataKey::Decimals;
    e.storage().set(&key, &u32::from(d))
}

pub fn read_name(e: &Env) -> Bytes {
    let key = DataKey::Name;
    e.storage().get_unchecked(&key).unwrap()
}

pub fn write_name(e: &Env, d: Bytes) {
    let key = DataKey::Name;
    e.storage().set(&key, &d)
}

pub fn read_symbol(e: &Env) -> Bytes {
    let key = DataKey::Symbol;
    e.storage().get_unchecked(&key).unwrap()
}

pub fn write_symbol(e: &Env, d: Bytes) {
    let key = DataKey::Symbol;
    e.storage().set(&key, &d)
}
```
The `metadata.rs` file contains functions for reading and writing metadata from storage.

It imports:

`DataKey` from `storage_types` (to access keys for storage)
`Env`, `Bytes` from `soroban_sdk` (to access the environment and work with byte vectors)

It then defines functions for:

Reading/writing the decimal places
Reading/writing the token name
Reading/writing the token symbol

Each function takes an `Env` reference and either reads a value from or writes a value to storage, using the appropriate `DataKey` and calling getter/setter functions on the Env's storage.
So in summary, this file provides an interface for managing the metadata (decimals, name, symbol) stored for the token contract.

- **`storage_types.rs`** 

Types for contract storage

```rust
use soroban_sdk::{contracttype, Address};

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

#[derive(Clone)]
#[contracttype]
pub enum DataKey {
    Allowance(AllowanceDataKey),
    Balance(Address),
    Nonce(Address),
    State(Address),
    Admin,
    Decimals,
    Name,
    Symbol,
}
```
The `storage_types.rs` file defines types for keys used to access storage.

It imports `Address` from soroban_sdk

It defines:

`AllowanceDataKey` - A key for allowance data, containing from and spender addresses
`DataKey` - An enum containing keys for all the main storage items:

- `Allowance` (using AllowanceDataKey)
- `Balance` (using an address)
- `Nonce` (using an address)
- `State` (using an address)
- `Admin`
- `Decimals`
- `Name`
- `Symbol`

The `#[contracttype]` attribute indicates these are types used for a Soroban contract.
So in summary, this file defines typed keys to use to access the key storage items for the token contract, allowing strong typing and safety when accessing storage.

##Running Contract Test
To ensure that the contract functions as intended, you can run the contract tests using the following command:
```
cargo test 
```
If the tests are successful, you should see an output similar to:
```
running 8 tests
test test::decimal_is_over_max - should panic ... ok
test test::initialize_already_initialized - should panic ... ok
test test::xfer_insufficient_balance - should panic ... ok
test test::test_burn ... ok
test test::xfer_from_insufficient_allowance - should panic ... ok
test test::xfer_spend_deauthorized - should panic ... ok
test test::xfer_receive_deauthorized - should panic ... ok
test test::test ... ok

test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s
```

##Building The Contract
To build the contract, use the following command:
```
cargo build --target wasm32-unknown-unknown --release
```
This should output a .wasm file in the ../target directory:
```
../target/wasm32-unknown-unknown/release/soroban_token_contract.wasm
```

##Deploying The Contract
To deploy the contract, use the following command:

```
soroban contract deploy --wasm /target/wasm32-unknown-unknown/release/soroban_token_contract.wasm 
```

##Invoking The Contract

This token contract contains many functions, but in this post I will only demonstrate initializing the token and minting tokens to an address. You can explore the other functions yourself. To initialize the token i'm using this command :

```
$ soroban contract invoke --id 31e2cf6483b1d6e4149780c7b97a455f3d17d6b43ef9050549fbd987d988039e --secret-key SCTDJOSFJOD3XGDAPDW73TSEVKRHSYVBQ4CK3QEHIAI4NH6SWDG53EBG -- initialize --admin GAREW2RCA4GELVLKPAIZNMNETTN57PAIVGOMPNMXHV3KMHLMMTPT2BAD --decimal 100 --name "4d79546f6b656e" --symbol "4d544b"
success
```

- `31e2cf6483b1d6e4149780c7b97a455f3d17d6b43ef9050549fbd987d988039e`
 - The ContractID of the deployed contract before
- `SCTDJOSFJOD3XGDAPDW73TSEVKRHSYVBQ4CK3QEHIAI4NH6SWDG53EBG` - Secret Key from the account used to invoke the function
- `initialize` - invoked function
- `GAREW2RCA4GELVLKPAIZNMNETTN57PAIVGOMPNMXHV3KMHLMMTPT2BAD` - - The admin address
- `100` - value for `decimal`
- `4d79546f6b656e` - The token name (in Hex bytes - "MyToken" in string)
- `4d544b` - The Token Symbol (in Hex bytes - "MTK" in string)

Token Contract already initialized, then i will mint that token into an address, using this command :

```
$ soroban contract invoke --id 31e2cf6483b1d6e4149780c7b97a455f3d17d6b43ef9050549fbd987d988039e --secret-key SCTDJOSFJOD3XGDAPDW73TSEVKRHSYVBQ4CK3QEHIAI4NH6SWDG53EBG -- mint --admin GAREW2RCA4GELVLKPAIZNMNETTN57PAIVGOMPNMXHV3KMHLMMTPT2BAD --to GDB3PKTGIMFAE4D36RAU5CKV3BHS75G5PMTJECIWAGELUSVBEMUBOUA3 --amount 100
success
```
- `31e2cf6483b1d6e4149780c7b97a455f3d17d6b43ef9050549fbd987d988039e` - The Contract ID
- `SCTDJOSFJOD3XGDAPDW73TSEVKRHSYVBQ4CK3QEHIAI4NH6SWDG53EBG` - The Secret Key from the account used to invoke the function
- `mint` - The Invoked Function
- `GAREW2RCA4GELVLKPAIZNMNETTN57PAIVGOMPNMXHV3KMHLMMTPT2BAD` - The Admin Address of The Token Contract
- `GDB3PKTGIMFAE4D36RAU5CKV3BHS75G5PMTJECIWAGELUSVBEMUBOUA3` - The recipient address
- `100` - Token minted amount

To make sure the token already minted we will check it using this command :
```
$ soroban contract invoke --id 31e2cf6483b1d6e4149780c7b97a455f3d17d6b43ef9050549fbd987d988039e --secret-key SCTDJOSFJOD3XGDAPDW73TSEVKRHSYVBQ4CK3QEHIAI4NH6SWDG53EBG -- balance --id GDB3PKTGIMFAE4D36RAU5CKV3BHS75G5PMTJECIWAGELUSVBEMUBOUA3
success
"100"
```
##Conclusion
That's it! We explored all modules code and the key parts of a token contract on Soroban. We saw how the contract is initialized and token minted. By piecing together the admin, allowance, balance, event, metadata, and storage_types modules, and using the TokenClient interface, a custom token contract can be easily developed and managed on Soroban. Stay tuned for more post in this "Soroban Contracts 101" Series where we will dive deeper into Soroban Contracts and their functionalities.