Oracles with Soroban Smart Contracts: A Practical and Flexible On-Chain Framework
Fred Kyung-jin Rezeau
Posted on December 22, 2023
The latest release of soroban-kit 0.1.8 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—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, specifically for feeding royalty contracts with market prices.
How It Works
soroban-kit 0.1.8 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:
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.
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.
💡 Tip: Alternatively, you can run cargo expand
command to view the expanded code generated by the macros.
// Implement the oracle broker interface for your contract.
#[contract]
#[oracle_broker(Bytes, Bytes)]
pub struct OracleBrokerContract;
// 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.
It consists of 2 smart contracts:
-
Oracle Broker Contract (see 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)
- 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:
$ 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):
$ 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 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:
$ 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:
$ 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:
$ 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:
$ 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:
$ 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:
Here are the commands to do so:
$ 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:
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:
$ 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 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 features!
If you have any questions, don't hesitate to reach out. I'm usually active on X @FredericRezeau and Discord where you can find me in the Litemint, Stellar Global and Stellar Developers servers!
Feel free to reach out on GitHub too.
Happy coding!
Posted on December 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 22, 2023