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]): exposed business methods.#[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.
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:
#![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()
}
}
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]): exposed business methods.#[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.
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:
#![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()
}
}