Skip to content

Commit

Permalink
Implementing a code generator for starknet-types-rpc (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
nils-mathieu authored Nov 7, 2023
1 parent 14b58ff commit 01b222e
Show file tree
Hide file tree
Showing 19 changed files with 8,588 additions and 3 deletions.
10 changes: 10 additions & 0 deletions crates/starknet-types-rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,14 @@ keywords = ["stark", "zkp", "cairo"]
description = "Starknet RPC types."
readme = "README.md"

[features]
default = ["std"]

std = ["serde/std", "starknet-types-core/std"]

[dependencies]
starknet-types-core = { path = "../starknet-types-core", default-features = false, features = ["serde"] }
serde = { version = "1", default-features = false, features = ["derive"] }

[dev-dependencies]
serde_json = "1"
21 changes: 20 additions & 1 deletion crates/starknet-types-rpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,26 @@ starknet-types-rpc = { version = "0.0.2", git = "https://github.com/starknet-io/

## Build from source

Clone the repository and navigate to the starknet-types-rpc directory. Then run:
The crate is built in two steps:

### Generating bindings against the Starknet OpenRPC specification

The specification is hosted on Starknet's repository ([link](https://github.com/starkware-libs/starknet-specs/blob/master/api/starknet_api_openrpc.json)).

Bindings are generated using [`openrpc-gen`](https://github.com/nils-mathieu/openrpc-gen).

After having built `openrpc-gen`, you can use the following command to generate the final generated
Rust files:

```bash
openrpc-gen --config configs/v0.5.0.toml --document configs/spec_v0.5.0.json --output src/generated/v0.5.0.rs
```

*Note that this first step is normally already done for you upon cloning the repository.*

### Building the generated files

Once this is done, you can build the crate with:

```bash
cargo build --release
Expand Down
129 changes: 129 additions & 0 deletions crates/starknet-types-rpc/src/custom/block_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use serde::{Deserialize, Deserializer, Serialize};

use crate::{BlockHash, BlockNumber, BlockTag};

/// A hexadecimal number.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockId {
/// The tag of the block.
Tag(BlockTag),
/// The hash of the block.
Hash(BlockHash),
/// The height of the block.
Number(BlockNumber),
}

#[derive(Serialize, Deserialize)]
struct BlockHashHelper {
block_hash: BlockHash,
}

#[derive(Serialize, Deserialize)]
struct BlockNumberHelper {
block_number: BlockNumber,
}

#[derive(Deserialize)]
#[serde(untagged)]
enum BlockIdHelper {
Tag(BlockTag),
Hash(BlockHashHelper),
Number(BlockNumberHelper),
}

impl serde::Serialize for BlockId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *self {
BlockId::Tag(tag) => tag.serialize(serializer),
BlockId::Hash(block_hash) => {
let helper = BlockHashHelper { block_hash };
helper.serialize(serializer)
}
BlockId::Number(block_number) => {
let helper = BlockNumberHelper { block_number };
helper.serialize(serializer)
}
}
}
}

impl<'de> serde::Deserialize<'de> for BlockId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let helper = BlockIdHelper::deserialize(deserializer)?;
match helper {
BlockIdHelper::Tag(tag) => Ok(BlockId::Tag(tag)),
BlockIdHelper::Hash(helper) => Ok(BlockId::Hash(helper.block_hash)),
BlockIdHelper::Number(helper) => Ok(BlockId::Number(helper.block_number)),
}
}
}

#[test]
fn block_id_from_hash() {
use crate::Felt;

let s = "{\"block_hash\":\"0x123\"}";
let block_id: BlockId = serde_json::from_str(s).unwrap();
assert_eq!(block_id, BlockId::Hash(Felt::from_hex("0x123").unwrap()));
}

#[test]
fn block_id_from_number() {
let s = "{\"block_number\":123}";
let block_id: BlockId = serde_json::from_str(s).unwrap();
assert_eq!(block_id, BlockId::Number(123));
}

#[test]
fn block_id_from_latest() {
let s = "\"latest\"";
let block_id: BlockId = serde_json::from_str(s).unwrap();
assert_eq!(block_id, BlockId::Tag(BlockTag::Latest));
}

#[test]
fn block_id_from_pending() {
let s = "\"pending\"";
let block_id: BlockId = serde_json::from_str(s).unwrap();
assert_eq!(block_id, BlockId::Tag(BlockTag::Pending));
}

#[cfg(test)]
#[test]
fn block_id_to_hash() {
use crate::Felt;

let block_id = BlockId::Hash(Felt::from_hex("0x123").unwrap());
let s = serde_json::to_string(&block_id).unwrap();
assert_eq!(s, "{\"block_hash\":\"0x123\"}");
}

#[cfg(test)]
#[test]
fn block_id_to_number() {
let block_id = BlockId::Number(123);
let s = serde_json::to_string(&block_id).unwrap();
assert_eq!(s, "{\"block_number\":123}");
}

#[cfg(test)]
#[test]
fn block_id_to_latest() {
let block_id = BlockId::Tag(BlockTag::Latest);
let s = serde_json::to_string(&block_id).unwrap();
assert_eq!(s, "\"latest\"");
}

#[cfg(test)]
#[test]
fn block_id_to_pending() {
let block_id = BlockId::Tag(BlockTag::Pending);
let s = serde_json::to_string(&block_id).unwrap();
assert_eq!(s, "\"pending\"");
}
7 changes: 7 additions & 0 deletions crates/starknet-types-rpc/src/custom/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod block_id;
mod query;
mod syncing_status;

pub use self::block_id::*;
pub use self::query::*;
pub use self::syncing_status::*;
48 changes: 48 additions & 0 deletions crates/starknet-types-rpc/src/custom/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// query offset:
// 0x0000000000000000000000000000000100000000000000000000000000000000

use serde::{Deserialize, Serialize};

use crate::{
BroadcastedDeclareTxnV1, BroadcastedDeclareTxnV2, DeployAccountTxnV1, InvokeTxnV0, InvokeTxnV1,
};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "version")]
pub enum BroadcastedDeclareTxn {
#[serde(rename = "0x1")]
V1(BroadcastedDeclareTxnV1),
#[serde(rename = "0x2")]
V2(BroadcastedDeclareTxnV2),
/// Query-only broadcasted declare transaction.
#[serde(rename = "0x0000000000000000000000000000000100000000000000000000000000000001")]
QueryV1(BroadcastedDeclareTxnV1),
/// Query-only broadcasted declare transaction.
#[serde(rename = "0x0000000000000000000000000000000100000000000000000000000000000002")]
QueryV2(BroadcastedDeclareTxnV2),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "version")]
pub enum BroadcastedDeployAccountTxn {
#[serde(rename = "0x1")]
V1(DeployAccountTxnV1),
/// Query-only broadcasted deploy account transaction.
#[serde(rename = "0x0000000000000000000000000000000100000000000000000000000000000001")]
QueryV1(DeployAccountTxnV1),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "version")]
pub enum BroadcastedInvokeTxn {
#[serde(rename = "0x0")]
V0(InvokeTxnV0),
#[serde(rename = "0x1")]
V1(InvokeTxnV1),
/// Query-only broadcasted invoke transaction.
#[serde(rename = "0x0000000000000000000000000000000100000000000000000000000000000000")]
QueryV0(InvokeTxnV0),
/// Query-only broadcasted invoke transaction.
#[serde(rename = "0x0000000000000000000000000000000100000000000000000000000000000001")]
QueryV1(InvokeTxnV1),
}
88 changes: 88 additions & 0 deletions crates/starknet-types-rpc/src/custom/syncing_status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::SyncStatus;

/// The syncing status of a node.
#[derive(Clone, Debug)]
pub enum SyncingStatus {
/// The node is not syncing.
NotSyncing,
/// The node is syncing.
Syncing(SyncStatus),
}

impl Serialize for SyncingStatus {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
SyncingStatus::NotSyncing => serializer.serialize_bool(false),
SyncingStatus::Syncing(status) => status.serialize(serializer),
}
}
}

impl<'de> Deserialize<'de> for SyncingStatus {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SyncingStatusVisitor;

impl<'de> Visitor<'de> for SyncingStatusVisitor {
type Value = SyncingStatus;

fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
writeln!(formatter, "a syncing status")
}

fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v {
Err(serde::de::Error::custom("expected a syncing status"))
} else {
Ok(SyncingStatus::NotSyncing)
}
}

fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let status =
SyncStatus::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;

Ok(SyncingStatus::Syncing(status))
}
}

deserializer.deserialize_any(SyncingStatusVisitor)
}
}

#[cfg(test)]
#[test]
fn syncing_status_from_false() {
let s = "false";
let syncing_status: SyncingStatus = serde_json::from_str(s).unwrap();
assert!(matches!(syncing_status, SyncingStatus::NotSyncing));
}

#[cfg(test)]
#[test]
fn syncing_status_to_false() {
let syncing_status = SyncingStatus::NotSyncing;
let s = serde_json::to_string(&syncing_status).unwrap();
assert_eq!(s, "false");
}

#[cfg(test)]
#[test]
fn syncing_status_from_true() {
let s = "true";
assert!(serde_json::from_str::<SyncingStatus>(s).is_err());
}
5 changes: 5 additions & 0 deletions crates/starknet-types-rpc/src/custom_serde/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! Custom serialization and deserialization routines.
mod num_as_hex;

pub use self::num_as_hex::NumAsHex;
Loading

0 comments on commit 01b222e

Please sign in to comment.