Oracles with Soroban Smart Contracts: A Practical and Flexible On-Chain Framework
Tags: blockchain, rust, soroban, smartcontract
Published: 2023-12-22 16:25:55

The latest release of [soroban-kit 0.1.8](https://github.com/FredericRezeau/soroban-kit) is now available, and it brings a set of tools designed to streamline both synchronous and asynchronous cross-contract communication, particularly for building more robust oracle systems with [soroban](https://soroban.stellar.org)—exciting topic for my first post on dev.to!

In this article, I'm going to walk you through the new `soroban-kit::oracle` module step-by-step. You will learn how to build a flexible and extensible oracle system leveraging the _pub/sub_ pattern for asynchronous communication between multiple **on-chain** oracle subscribers and brokers.

We are implementing this system for our Soroban-powered [smart contracts on Litemint](https://github.com/litemint/litemint-soroban-contracts), specifically for feeding royalty contracts with market prices.

## How It Works

[soroban-kit 0.1.8](https://github.com/FredericRezeau/soroban-kit) introduces two procedural attribute macros, `oracle_broker` and `oracle_subscriber`.

Applying these macros to your contract `struct` automatically generates a lightweight messaging framework that enables both synchronous and asynchronous cross-contract communication, as demonstrated in the following diagram:

![Sequence diagram - soroban-kit::oracle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dok561ulnksvs1y8nmcl.png)

By leveraging the publisher-subscriber pattern, we can enable multiplicity between oracle subscribers, brokers and publishers. Not only this enhances deployment flexibility but also allows for a gradual increase in system resilience and robustness—mitigating the risks associated with reliance on a single point.

![Deployment diagram - soroban-kit::oracle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tftsztedlrohyfbx5ioi.png)

The best part is, setting up your contracts for this requires **only a single line of code**, all thanks to Rust's sophisticated macro system. For those curious about the inner workings and mechanics, take a look at [oracle.rs](https://github.com/FredericRezeau/soroban-kit/blob/master/crates/soroban-macros/src/oracle.rs).

💡 **Tip:** Alternatively, you can run `cargo expand` command to view the expanded code generated by the macros.

```rust
// Implement the oracle broker interface for your contract.
#[contract]
#[oracle_broker(Bytes, Bytes)]
pub struct OracleBrokerContract;
```
```rust
// Implement the oracle subscriber interface for your contract.
#[contract]
#[oracle_subscriber(Bytes, Bytes)]
pub struct OracleSubscriberContract;
```

These macros also allow you to customize the types for both the `topic` and the `data`. We use `Bytes` in this example but any built-in type and user-defined type is supported, Soroban transparently provides for all our serialization needs!

After setting it up, all that's required is to implement the `soroban-kit::oracle::Events` trait to manage and process the workflow.

Let's go!

## Getting the Code on Github

The code for the example oracle project is located in this [GitHub repo](https://github.com/FredericRezeau/soroban-oracle-example).

It consists of 2 smart contracts:

- **Oracle Broker Contract** (see [lib.rs](https://github.com/FredericRezeau/soroban-oracle-example/blob/master/crates/broker/src/lib.rs))
  - Handling sync/async topic-based subscriber requests.
  - Handling publishing requests.
  - Collecting subscriber fees.
  - Managing envelopes for async routing.
  - Managing publishers whitelist.

- **Oracle Subscriber Contract** (see [lib.rs](https://github.com/FredericRezeau/soroban-oracle-example/blob/master/crates/subscriber/src/lib.rs))
  - Routing topic-based data requests to broker contract.
  - Handling sync/async responses.
  - Managing brokers whitelist.
  - Providing data reconciliation.

_Note that our example **data reconciliation** function simply replaces the old data with the new but more elaborate mechanisms can be implemented, including price aggregation, average computation, validation and coalescing of results, based on specific use cases._

First, clone the repository. Then, execute the following command to compile the contracts:

```shell
$ soroban contract build
```
Once compiled, you'll find two oracle contracts _ready to deploy_ in the target directory.

💡 **Tip:** While they are already compact, using the optimization command (`soroban contract optimize`) can shrink their sizes even more (to less than 3.5K for the broker contract and 2.5K for the subscriber one):

```shell
$ ls -lh broker.optimized.wasm subscriber.optimized.wasm
-rwxrwxrwx 1 3.4K Dec 22 22:29 broker.optimized.wasm
-rwxrwxrwx 1 2.5K Dec 22 22:29 subscriber.optimized.wasm
```

## Step-by-Step Deployment and Setup

For our scenario below, we will need **2 subscribers** and **2 brokers**.

Remember, you always have the option to deploy additional nodes at any time. `soroban-kit::oracle` ensures consistency for communication with decoupled, events-driven, interactions between your contracts (as long as the types are consistent, they can communicate).

We assume that the following network and identities are pre-configured (`soroban config`) and funded. Follow [this link](https://soroban.stellar.org/docs/getting-started/setup) if you need instructions.

| Type    | Name    | Description            |
| ------- | ------- | ---------------------- |
| Network | `TESTNET` | Stellar Testnet      |
| Account | `ADMIN`   | Contracts admin      |
| Account | `ALICE`   | Subscriber operator  |
| Account | `BOB`     | Publisher            |

To deploy the oracle broker contracts and simultaneously save their _IDs_, use the following commands:

```shell
$ soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/broker.wasm \
--source ADMIN --network TESTNET > BROKER1

$ soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/broker.wasm \
--source ADMIN --network TESTNET > BROKER2
```

Do the same for the oracle subscriber contracts:

```shell
$ soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/subscriber.wasm \
--source ADMIN --network TESTNET > SUBSCRIBER1

$ soroban contract deploy \
--wasm target/wasm32-unknown-unknown/release/subscriber.wasm \
--source ADMIN --network TESTNET > SUBSCRIBER2
```

Setup the `ADMIN` for all contracts:

```shell
$ soroban contract invoke --id $(cat BROKER1) \
--source ADMIN --network TESTNET -- set_admin --admin ADMIN

$ soroban contract invoke --id $(cat BROKER2) \
--source ADMIN --network TESTNET -- set_admin --admin ADMIN

$ soroban contract invoke --id $(cat SUBSCRIBER1) \
--source ADMIN --network TESTNET -- set_admin --admin ADMIN

$ soroban contract invoke --id $(cat SUBSCRIBER2) \
--source ADMIN --network TESTNET -- set_admin --admin ADMIN
```

Whitelist the publisher (`BOB`) for both oracle brokers:

```shell
$ soroban contract invoke --id $(cat BROKER1) \
--source ADMIN --network TESTNET -- allow_publisher --publisher BOB

$ soroban contract invoke --id $(cat BROKER2) \
--source ADMIN --network TESTNET -- allow_publisher --publisher BOB
```

Whitelist the brokers (`BROKER1` and `BROKER2`) for both oracle subscribers:

```shell
$ soroban contract invoke --id $(cat SUBSCRIBER1) \
--source ADMIN --network TESTNET -- allow_broker --broker $(cat BROKER1)

$ soroban contract invoke --id $(cat SUBSCRIBER2) \
--source ADMIN --network TESTNET -- allow_broker --broker $(cat BROKER1)

$ soroban contract invoke --id $(cat SUBSCRIBER1) \
--source ADMIN --network TESTNET -- allow_broker --broker $(cat BROKER2)

$ soroban contract invoke --id $(cat SUBSCRIBER2) \
--source ADMIN --network TESTNET -- allow_broker --broker $(cat BROKER2)
```

## Executing a Test Scenario

Everything is now ready! `ALICE` is initiating data requests for **`Topic 21`** through `SUBSCRIBER1` and `SUBSCRIBER2` to both `BROKER1` and `BROKER2`.

Here's a deployment diagram to provide a clearer visual representation of the setup:

![Alice deployment setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x45uroq9a7pi1wsvrr6v.png)

Here are the commands to do so:

```shell
$ soroban contract invoke --id $(cat SUBSCRIBER1) \
--source ALICE --network TESTNET -- request \
--subscriber ALICE --topic 21 --broker $(cat BROKER1)

> null

$ soroban contract invoke --id $(cat SUBSCRIBER2) \
--source ALICE --network TESTNET -- request \
--subscriber ALICE --topic 21 --broker $(cat BROKER1)

> null

$ soroban contract invoke --id $(cat SUBSCRIBER1) \
--source ALICE --network TESTNET -- request \
--subscriber ALICE --topic 21 --broker $(cat BROKER2)

> null

$ soroban contract invoke --id $(cat SUBSCRIBER2) \
--source ALICE --network TESTNET -- request \
--subscriber ALICE --topic 21 --broker $(cat BROKER2)

> null
```

`BOB` is setup to publish data exclusively to `BROKER2`, and note that `BROKER1` will not receive this data. Here's the command:

```shell
soroban contract invoke --id $(cat BROKER2) \
--source BOB --network TESTNET -- publish --topic 21 \
--publisher BOB --data 21000000
```

Because both subscribers were connected to `BROKER2`, they have received and can now serve the data synchronously. Here are some example commands:

```shell
$ soroban contract invoke --id $(cat SUBSCRIBER1) \
--source ADMIN --network TESTNET -- get_data --topic 21

> 21000000

$ soroban contract invoke --id $(cat SUBSCRIBER2) \
--source ADMIN --network TESTNET -- get_data --topic 21

> 21000000
```

## Conclusion

Oracles serve as bridges between blockchains and external data sources. There are many key challenges in implementing Oracle services, including decentralization, synchronicity, decoupling and multiplicity.

[soroban-kit](https://github.com/FredericRezeau/soroban-kit) proposes a lightweight solution for implementing the pub/sub messaging pattern to help address these challenges for cross-contract communication. Allowing you to deploy nodes gradually to achieve resilience and robustness for your oracle system.

I hope you found this enjoyable! Stay tuned for more detailed tutorials showcasing the full range of [soroban-kit](https://github.com/FredericRezeau/soroban-kit) features!

If you have any questions, don't hesitate to reach out. I'm usually active on [X @FredericRezeau](https://x.com/FredericRezeau) and Discord where you can find me in the [Litemint](https://litemint.gg), [Stellar Global](https://discord.gg/stellar-global-761985725453303838) and [Stellar Developers](https://discord.gg/stellardev) servers!

Feel free to reach out on [GitHub](https://github.com/FredericRezeau) too.

Happy coding!