Meet Your Program
2/9 tutorials
22%

Lesson 2 — Meet Your Program

OneOfUs is a membership registry.

  • join_us() registers caller and returns true/false (new or already joined).
  • count() returns total members.
  • is_one_of_us(addr) checks membership.
  • list(page, page_size) paginates members.
  • version() returns program version.

The Sails layout is standard:

  • Service (#[service]): exposed business methods.
  • Program (#[program]): entry point that hosts services.

State is stored in isolated program memory via static mut. This is safe because each program instance is a single-threaded actor with its own queue and memory — no concurrent access is possible.

Deployment target is selected in Cargo.toml:

sails-rs = { version = "0.10.1", features = ["ethexe"] }

With ethexe, the same code targets Vara.eth.

IDL (quick mental model)

You do not build or edit IDL manually in this tutorial.
IDL is the generated API contract of your program: constructor, service methods, inputs, and outputs.

In our case:

  • constructor { Init } initializes state.
  • JoinUs is a state-changing call.
  • Count, IsOneOfUs, List, Version are query methods (read-only).

Why it matters:

  • Tooling uses IDL to encode/decode calls correctly.
  • Solidity ABI is generated from this IDL in the next lesson.
#![no_std]

use sails_rs::{collections::BTreeSet, gstd::msg, prelude::*};

const PROGRAM_VERSION: u32 = 8;

static mut STATE: Option<OneOfUsState> = None;

#[derive(Clone, Default)]
pub struct OneOfUsState {
    pub builders: Vec<ActorId>,
    pub registered: BTreeSet<ActorId>,
}

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

    pub fn get_mut() -> &'static mut Self {
        unsafe { STATE.as_mut().expect("State not initialized") }
    }

    pub fn init() {
        unsafe {
            if STATE.is_some() {
                panic!("State already initialized");
            }
            STATE = Some(Self::default());
        }
    }
}

#[derive(Default)]
pub struct OneOfUsService;

impl OneOfUsService {
    pub fn init() -> Self {
        OneOfUsState::init();
        Self::default()
    }
}

#[service]
impl OneOfUsService {
    #[export]
    pub fn join_us(&mut self) -> bool {
        let sender = msg::source();
        let state = OneOfUsState::get_mut();

        if state.registered.contains(&sender) {
            return false;
        }

        state.builders.push(sender);
        state.registered.insert(sender);
        true
    }

    #[export]
    pub fn count(&self) -> u32 {
        OneOfUsState::get().builders.len() as u32
    }

    #[export]
    pub fn is_one_of_us(&self, addr: ActorId) -> bool {
        OneOfUsState::get().registered.contains(&addr)
    }

    #[export]
    pub fn list(&self, page: u32, page_size: u32) -> Vec<ActorId> {
        let state = OneOfUsState::get();
        let start = (page * page_size) as usize;

        state
            .builders
            .iter()
            .skip(start)
            .take(page_size as usize)
            .copied()
            .collect()
    }

    #[export]
    pub fn version(&self) -> u32 {
        PROGRAM_VERSION
    }
}

pub struct OneOfUsProgram;

#[program]
impl OneOfUsProgram {
    pub fn init() -> Self {
        OneOfUsService::init();
        Self
    }

    pub fn one_of_us(&self) -> OneOfUsService {
        OneOfUsService::default()
    }
}