Services, Storage, RoutingFungible Token Contract
2/11 tutorials
18%

Understanding Services, Storage, and Message Routing

Let's implement a simple fungible token contract. This contract will manage user balances and mint new tokens.

Start by introducing a service that initializes the token name during contract deployment. The service will also include a method to return the specified name.

Here's the step-by-step explanation:

  1. Structure Definition: The State structure stores the token name.
  2. Static State Storage: A static variable holds the state. Before initialization, STATE is None, allowing for the possibility of the storage being uninitialized.
  3. Storage Implementation: The State struct has a get method that returns a reference to the storage.
  4. Token Service Implementation: The Token struct is marked as a service with the #[service] attribute.
    • Initialization (init method): Takes a String parameter name and initializes the static storage with this name.
    • Name Retrieval (name method): Returns the name stored in the static storage.
  5. Program Implementation: The previous lesson only added a service constructor. Here, an application constructor is added. Associated public functions returning Self are treated as application constructors and are called once at the beginning of the application’s lifetime when the application is loaded onto the network.
    • Application Constructor (new method): Initializes the token with the specified name by calling Token::init and returns an instance of MyProgram.
    • Token Service Access (token method): Returns a default instance of the Token struct.
    • Another crucial concept is message routing. This concept doesn't have a mandatory representation in the code but can be altered using the #[route] attribute applied to associated public functions. Message routing is about rules for dispatching an incoming request message to a specific service's method using service and method names. If the #[route("token")] attribute was not used above the application constructor of the token service, then the service is exposed as TokenSvc. With the #[route("token")] macro, it is exposed as Token.
#![no_std]
use sails_rs::{prelude::*};

pub struct State {
    name: String,
}

static mut STATE: Option<State> = None;

impl State {
    pub fn get() -> &'static Self {
        unsafe { STATE.as_ref().expect("State is not initialized") }
    }
}

#[derive(Default)]
pub struct Token;

#[service]
impl Token {
    pub fn init(name: String) {
        unsafe {
            STATE = Some(State {
                name,
            });
        }
    }

    pub fn name(&self) -> &'static str {
        let state = State::get();
        &state.name
    }
}

pub struct MyProgram;

#[program]
impl MyProgram {
    pub fn new(name: String) -> Self {
        Token::init(name);
        Self
    }

    #[route("token")]
    pub fn token_svc(&self) -> Token {
        Token::default()
    }
}