Skip to content

Commit fef4a5c

Browse files
committed
implement rust sdk
1 parent e4ff097 commit fef4a5c

File tree

9 files changed

+1217
-0
lines changed

9 files changed

+1217
-0
lines changed

Cargo.lock

Lines changed: 623 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[workspace]
2+
members = ["sdk/rust"]
3+
resolver = "3"

sdk/rust/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "cairo-oracle-server"
3+
version = "0.1.0"
4+
rust-version = "1.88.0"
5+
edition = "2024"
6+
description = "SDK for writing Cairo oracles in Rust"
7+
authors = ["Software Mansion <[email protected]>"]
8+
homepage = "https://docs.swmansion.com/scarb"
9+
license = "MIT"
10+
repository = "https://github.com/software-mansion/cairo-oracle"
11+
12+
[dependencies]
13+
anyhow = "1"
14+
serde = { version = "1", features = ["derive"] }
15+
serde_json = "1"
16+
starknet-core = "0.15.0"

sdk/rust/src/builder.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use crate::handler::{Handler, HandlerMap, box_handler};
2+
use crate::server::Server;
3+
use std::mem;
4+
use std::process::ExitCode;
5+
6+
pub struct Oracle<'a> {
7+
handler_map: HandlerMap<'a>,
8+
}
9+
10+
impl<'a> Oracle<'a> {
11+
pub fn new() -> Self {
12+
Self {
13+
handler_map: Default::default(),
14+
}
15+
}
16+
17+
pub fn provide<T>(&mut self, selector: &str, handler: impl Handler<T> + 'a) -> &mut Self {
18+
self.handler_map
19+
.insert(selector.into(), box_handler(handler));
20+
self
21+
}
22+
23+
#[must_use = "the returned exit code must be used to exit the process"]
24+
pub fn run(&mut self) -> ExitCode {
25+
let handler_map = mem::take(&mut self.handler_map);
26+
Server::main(handler_map)
27+
}
28+
}
29+
30+
impl Default for Oracle<'_> {
31+
fn default() -> Self {
32+
Self::new()
33+
}
34+
}

sdk/rust/src/handler.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
use anyhow::{Result, ensure};
2+
use starknet_core::codec::{Decode, Encode};
3+
use starknet_core::types::Felt;
4+
use std::collections::HashMap;
5+
6+
pub(crate) type HandlerMap<'a> = HashMap<String, BoxedUntypedHandler<'a>>;
7+
pub(crate) type BoxedUntypedHandler<'a> = Box<dyn (FnMut(Vec<Felt>) -> Result<Vec<Felt>>) + 'a>;
8+
9+
pub(crate) fn box_handler<'a, T>(mut handler: impl Handler<T> + 'a) -> BoxedUntypedHandler<'a> {
10+
Box::new(move |calldata| handler.invoke_untyped(calldata))
11+
}
12+
13+
// NOTE: The T parameter allows us to overcome a lack of specialisation in Rust.
14+
// This trick has been stolen from Axum, the clue is using tuples here:
15+
// https://docs.rs/axum/latest/src/axum/handler/mod.rs.html#193
16+
17+
pub trait Handler<T> {
18+
fn invoke_untyped(&mut self, calldata: Vec<Felt>) -> Result<Vec<Felt>>;
19+
}
20+
21+
macro_rules! impl_handler {
22+
($($var:ident: $param:ident),*) => {
23+
impl<F, $($param,)* R> Handler<(($($param,)*),)> for F
24+
where
25+
F: FnMut($($param),*) -> Result<R>,
26+
$($param: for<'a> Decode<'a>,)*
27+
R: Encode,
28+
{
29+
fn invoke_untyped(&mut self, calldata: Vec<Felt>) -> Result<Vec<Felt>> {
30+
let mut calldata = calldata.iter();
31+
32+
$(
33+
let $var = $param::decode_iter(&mut calldata)?;
34+
)*
35+
36+
ensure!(calldata.next().is_none(), "unexpected parameters");
37+
38+
let value = self($($var,)*)?;
39+
40+
let mut encoded = vec![];
41+
value.encode(&mut encoded)?;
42+
Ok(encoded)
43+
}
44+
}
45+
};
46+
}
47+
48+
// Generate implementations for up to 12 parameters.
49+
impl_handler!();
50+
impl_handler!(a0: A0);
51+
impl_handler!(a0: A0, a1: A1);
52+
impl_handler!(a0: A0, a1: A1, a2: A2);
53+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3);
54+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4);
55+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5);
56+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6);
57+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7);
58+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8);
59+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9);
60+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10);
61+
impl_handler!(a0: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9, a10: A10, a11: A11);

sdk/rust/src/io.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
use crate::jsonrpc;
2+
use anyhow::Context;
3+
use std::io;
4+
use std::io::{BufRead, Write};
5+
6+
pub struct Io {
7+
stdin: io::BufReader<io::StdinLock<'static>>,
8+
stdout: io::StdoutLock<'static>,
9+
}
10+
11+
impl Io {
12+
pub fn lock() -> Self {
13+
Self {
14+
stdin: io::BufReader::new(io::stdin().lock()),
15+
stdout: io::stdout().lock(),
16+
}
17+
}
18+
19+
pub fn send(&mut self, message: impl Into<jsonrpc::Message>) {
20+
let message = message.into();
21+
return inner(self, &message);
22+
23+
// Minimize monomorphisation effects.
24+
fn inner(this: &mut Io, message: &jsonrpc::Message) {
25+
// Serialise the message to string as a whole to avoid emitting unterminated messages.
26+
let line =
27+
serde_json::to_string(&message).expect("failed to serialize message from oracle");
28+
29+
eprintln!("send: {line}");
30+
writeln!(this.stdout, "{line}").expect("failed to write message to oracle stdout");
31+
this.stdout.flush().expect("failed to flush oracle stdout");
32+
}
33+
}
34+
35+
pub fn recv(&mut self) -> Option<anyhow::Result<jsonrpc::Message>> {
36+
let mut buf = String::new();
37+
match self.stdin.read_line(&mut buf) {
38+
Ok(0) => None,
39+
Ok(_n) => {
40+
eprintln!("recv: {buf}");
41+
Some(
42+
serde_json::from_str(buf.trim()).context("failed to parse message from oracle"),
43+
)
44+
}
45+
Err(err) => Some(Err(err).context("failed to read bytes from oracle")),
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)