Skip to main content

Migration of gstd programs

Sails is a library for writing applications utilizing the Gear Protocol with simplicity and clarity. This powerful tool represents the next step in the evolution of the Gear Protocol.

Initially, Gear applications typically employ the less convenient gstd library for managing low-level operations and essential functionality. The Sails library builds programs on top of gstd, handling most low-level interactions under the hood. As projects grow, adopting the Sails framework allows developers to shift towards a more modular and service-oriented architecture. Existing dApps written with gstd can be effortlessly migrated to Sails.

This guide focuses on the key steps involved in transitioning existing gstd-based code to the Sails framework, outlining the necessary code transformations and architectural adjustments while leveraging the underlying gstd infrastructure.

Setting Up Your Sails Project

To get started with the migration process, you can use a template to create a new project with Sails.

  • First, install the sails-cli tool using the following command:

    cargo install sails-cli
  • After installation, you can generate a new project named "vara-app" by running:

    cargo-sails new-program vara-app

Migrating Action Handling

  • In gstd-based projects, actions are typically defined using enums, which are processed within a handle function. This function decodes incoming messages and matches them to the corresponding action:

    pub enum Action {
    DoA,
    DoB,
    }

    impl Metadata for ProgramMetadata {
    type Handle = In<Action>;
    }

    #[no_mangle]
    extern fn handle() {
    let action: Action = msg::load().expect("Failed to decode `Action` message.");
    match action {
    Action::DoA => { /* Implementation for action A */ },
    Action::DoB => { /* Implementation for action B */ },
    }
    }
  • In Sails, this approach is restructured to leverage the framework’s service-oriented architecture. Rather than processing all actions through a single entry point, each action is encapsulated in its own service method. Here’s how the same functionality would look in Sails:

    #[service]
    impl Service {
    pub fn do_a(&mut self) { /* Implementation for action A */ }
    pub fn do_b(&mut self) { /* Implementation for action B */ }
    }

This shift abstracts the message decoding and routing logic, with Sails automatically handling the dispatch of incoming messages to the appropriate service methods. The resulting code is more modular, with each action represented as a discrete method.

Migrating Reply Messages

  • In the case of low-level use of gstd, sending a reply message is done manually after the action has been processed. The reply should be sent using the msg::reply function:

    #[no_mangle]
    extern fn handle() {
    let action: Action = msg::load().expect("Failed to decode `Action` message.");
    match action {
    Action::DoA => {
    // Implementation for action A
    msg::reply(reply, 0).expect("Failed to encode `reply`");
    },
    }

    }
  • When using Sails, replies are returned directly from the service function, and the framework manages the encoding and sending of the message:

    #[service]
    impl Service {
    pub fn do_a(&mut self) -> TypeOfReply {
    // Implementation for action A
    reply
    }
    }

This reduces manual work, as the framework abstracts the reply handling, resulting in more concise code.

Migrating State Queries

  • State queries are often implemented through enums, which handle different state requests and responses manually within a state function:

    pub enum StateQuery {
    State1,
    State2,
    }

    pub enum StateReply {
    State1(...),
    State2(...),
    }

    impl Metadata for ProgramMetadata {
    type State = InOut<StateQuery, StateReply>;
    }

    #[no_mangle]
    extern fn state() {
    let query: StateQuery = msg::load().expect("Unable to load the state query");
    match query {
    StateQuery::State1 => {
    msg::reply(StateReply::State1(state), 0).expect("Unable to share the state");
    },
    StateQuery::State2 => {
    msg::reply(StateReply::State2(state), 0).expect("Unable to share the state");
    },
    }
    }
  • When migrating to Sails, state-related functions are methods that take &self as a parameter, since they don’t modify the state but only query it:

    #[service]
    impl Service {
    pub fn state_1(&self) -> TypeOfReply {
    // Implementation for State1
    state
    }

    pub fn state_2(&self) -> TypeOfReply {
    // Implementation for State2
    state
    }
    }

Conclusion

Migrating a project from the gstd library to the Sails framework is a straightforward process that primarily involves adapting the code to a more modular and service-oriented structure. Since Sails operates on top of gstd, the core functionality remains the same, but the framework provides a more organized and maintainable approach to handling actions, replies, and state queries.

By following the steps outlined in this guide, you can easily refactor your project to take advantage of the abstractions and efficiency offered by Sails. This migration not only simplifies code management but also enhances the long-term scalability.

With just a few adjustments to your existing codebase, such as reorganizing action handling into service methods and streamlining state queries, your project can seamlessly transition to Sails without sacrificing any of the functionality provided by gstd.