Using Soroban Inside Zephyr

Learn about how you can use the very Soroban SDK inside Zephyr!

This section is dedicated to showcasing the amazing features in Zephyr that are enabled by having hardwired Soroban within the Zephyr Virtual Machine.

As a matter of fact, you can use the same Soroban SDK you use to write your smart contracts to write Zephyr indexing programs!

This not only brings innovation, powerful helpers, and potential use cases to the world of indexing but also makes the experience of working with the chain's contract data types in a way no other chain has seen before!

This part of the documentation is just getting started, more features and explainers coming soon!

Working with ScVals

One of the most painful things about working with on-chain smart contract data in general is their complexity. When we deal with this data in the contract's environment everything is neatly defined according to the language's structures. However, once this data is translated into the chain's data format for consensus these become complex nested objects which can be difficult and time-consuming to navigate, making maintainability even more difficult.

In Zephyr, this is not a concern, because you're "kind of" running on a contract environment! Your programs are still executed by the ZephyrVM, tailored for working with Mercury, not by the Soroban VM. However, we have manipulated the Soroban host environment to link soroban host functions into Zephyr and abstract the wasmi Soroban host implementation to enable Soroban to actually execute within Zephyr.

As a result, something that should look like this:

#[no_mangle]
pub extern "C" fn on_close() {
    let env = EnvClient::new();

    let events = env.reader().soroban_events();
    for event in events {
        let ContractEventBody::V0(event) = event.body;

        let action = match &event.topics[0] {
            ScVal::Symbol(symbol) => {
                symbol.0.to_string()
            },
            _ => return
        };

        if &action == "deposit" {
            // ...
        }
    }
}

Can be replaced with:

#[no_mangle]
pub extern "C" fn on_close() {
    let env = EnvClient::new();

    let events = env.reader().soroban_events();
    for event in events {
        let ContractEventBody::V0(event) = event.body;

        if env.from_scval::<Symbol>(&event.topics[0]) == Symbol::new(env.soroban(), "deposit") {
            // ...
        }
    }
}

This is already great when working with very simple types such as symbols, but wait until you see what you can do with more complex data types.

Shared Contract <> Indexer contracttypes?

Let's assume that in your contract, you emit an event with the following structure in its data:

#[contracttype]
pub enum SomeAction {
    Perform(Symbol),
    NotPerform,
    PartiallyPerform
}

#[contracttype]
pub struct MyCustomData {
    action: SomeAction,
    data: i128
}

This is more complex in structure compared to a Symbol ScVal.

Generally, what you should do if you want to for example aggregate the data integer depending on the action would be to parse the scval object tree you retrieve from the event. This means that you'd have to parse something that looks like this:

{
  "map": [
    { "key": { "symbol": "action" }, "val": { "vec": [{ "vec": [{ "symbol": "perform" }, { "symbol": "somesym" }] }] } },
    { "key": { "symbol": "data" }, "val": { "i128": { "i128parts": {hi, lo} } } }
  ]
}

This is not nice, and it's why when writing soroban contracts you don't have to parse this.

Guess what, you don't need to do so even in Zephyr!

You could either import the types from your contract crate in the indexer or define them in the indexer the same way you'd do it on your contract and then simply:

let constructed: MyCustomData = env.from_scval(&event.data);

match constructed.action {
    SomeAction::Perform(_) => (), // do something
    _ => () // do something else
}

Using SDK helpers

In zephyr, you can also use SDK functions like address calculation with the deployer and crypto operations. Not as exciting as working with ScVals or invoking contracts, but can still come in handy.

[Soon] Invoking contracts

This is the final milestone for the soroban integration in Zephyr. Invoking contracts within Zephyr allows for endless innovation in how we conceive off-chain blockchain data services. You could be running contract functions for each user for every checkpoint to monitor that everything is running as expected (for example, make sure that all users can withdraw all their funds to detect rounding errors), make sure that cargo tests are executing correctly with on-chain data and be alerted if otherwise, or your bot could simulate or "fuzz" the outcomes of a transaction.

To achieve this, we need to make a few changes to the Soroban host environment as the VM cannot unfortunately be as easily abstracted as the host, but be advised that this is definitely in our short-term roadmap!

Last updated