diff --git a/Cargo.lock b/Cargo.lock
index f41033d65a..9d146f2f4b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8458,6 +8458,7 @@ dependencies = [
"tonic-build 0.10.2",
"tonic-build 0.9.2",
"tonic-web-wasm-client",
+ "torii-core",
"tower",
"tracing",
"url",
diff --git a/Cargo.toml b/Cargo.toml
index 03682b4438..e935e5fc2b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -85,6 +85,7 @@ serde = { version = "1.0.156", features = [ "derive" ] }
serde_json = "1.0"
serde_with = "2.3.1"
smol_str = { version = "0.2.0", features = [ "serde" ] }
+sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] }
starknet = "0.6.0"
starknet-crypto = "0.6.0"
starknet_api = { git = "https://github.com/starkware-libs/starknet-api", rev = "ecc9b6946ef13003da202838e4124a9ad2efabb0" }
diff --git a/crates/dojo-world/src/contracts/model.rs b/crates/dojo-world/src/contracts/model.rs
index f6c1ec6c84..b2fee105da 100644
--- a/crates/dojo-world/src/contracts/model.rs
+++ b/crates/dojo-world/src/contracts/model.rs
@@ -1,5 +1,6 @@
use std::vec;
+use async_trait::async_trait;
use dojo_types::packing::{parse_ty, unpack, PackingError, ParseError};
use dojo_types::primitive::PrimitiveError;
use dojo_types::schema::Ty;
@@ -46,7 +47,17 @@ pub enum ModelError
{
Packing(#[from] PackingError),
}
-pub struct ModelReader<'a, P> {
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+pub trait ModelReader {
+ fn class_hash(&self) -> FieldElement;
+ async fn schema(&self) -> Result;
+ async fn packed_size(&self) -> Result;
+ async fn unpacked_size(&self) -> Result;
+ async fn layout(&self) -> Result, E>;
+}
+
+pub struct ModelRPCReader<'a, P: Sync + Send> {
/// The name of the model
name: FieldElement,
/// The class hash of the model
@@ -55,14 +66,14 @@ pub struct ModelReader<'a, P> {
world_reader: &'a WorldContractReader,
}
-impl<'a, P> ModelReader<'a, P>
+impl<'a, P> ModelRPCReader<'a, P>
where
- P: Provider,
+ P: Provider + Sync + Send,
{
pub async fn new(
name: &str,
world: &'a WorldContractReader
,
- ) -> Result, ModelError> {
+ ) -> Result, ModelError> {
let name = cairo_short_string_to_felt(name)?;
let class_hash = world
@@ -88,11 +99,60 @@ where
Ok(Self { world_reader: world, class_hash, name })
}
- pub fn class_hash(&self) -> FieldElement {
+ pub async fn entity_storage(
+ &self,
+ keys: &[FieldElement],
+ ) -> Result, ModelError> {
+ let packed_size: u8 =
+ self.packed_size().await?.try_into().map_err(ParseError::ValueOutOfRange)?;
+
+ let key = poseidon_hash_many(keys);
+ let key = poseidon_hash_many(&[short_string!("dojo_storage"), self.name, key]);
+
+ let mut packed = Vec::with_capacity(packed_size as usize);
+ for slot in 0..packed_size {
+ let value = self
+ .world_reader
+ .provider()
+ .get_storage_at(
+ self.world_reader.address(),
+ key + slot.into(),
+ self.world_reader.block_id(),
+ )
+ .await?;
+
+ packed.push(value);
+ }
+
+ Ok(packed)
+ }
+
+ pub async fn entity(&self, keys: &[FieldElement]) -> Result> {
+ let mut schema = self.schema().await?;
+
+ let layout = self.layout().await?;
+ let raw_values = self.entity_storage(keys).await?;
+
+ let unpacked = unpack(raw_values, layout)?;
+ let mut keys_and_unpacked = [keys, &unpacked].concat();
+
+ schema.deserialize(&mut keys_and_unpacked)?;
+
+ Ok(schema)
+ }
+}
+
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+impl<'a, P> ModelReader> for ModelRPCReader<'a, P>
+where
+ P: Provider + Sync + Send,
+{
+ fn class_hash(&self) -> FieldElement {
self.class_hash
}
- pub async fn schema(&self) -> Result> {
+ async fn schema(&self) -> Result> {
let entrypoint = get_selector_from_name(SCHEMA_SELECTOR_STR).unwrap();
let res = self
@@ -103,7 +163,7 @@ where
Ok(parse_ty(&res[1..])?)
}
- pub async fn packed_size(&self) -> Result> {
+ async fn packed_size(&self) -> Result> {
let entrypoint = get_selector_from_name(PACKED_SIZE_SELECTOR_STR).unwrap();
let res = self
@@ -114,7 +174,7 @@ where
Ok(res[1])
}
- pub async fn unpacked_size(&self) -> Result> {
+ async fn unpacked_size(&self) -> Result> {
let entrypoint = get_selector_from_name(UNPACKED_SIZE_SELECTOR_STR).unwrap();
let res = self
@@ -125,7 +185,7 @@ where
Ok(res[1])
}
- pub async fn layout(&self) -> Result, ModelError> {
+ async fn layout(&self) -> Result, ModelError> {
let entrypoint = get_selector_from_name(LAYOUT_SELECTOR_STR).unwrap();
let res = self
@@ -135,46 +195,4 @@ where
Ok(res[2..].into())
}
-
- pub async fn entity_storage(
- &self,
- keys: &[FieldElement],
- ) -> Result, ModelError> {
- let packed_size: u8 =
- self.packed_size().await?.try_into().map_err(ParseError::ValueOutOfRange)?;
-
- let key = poseidon_hash_many(keys);
- let key = poseidon_hash_many(&[short_string!("dojo_storage"), self.name, key]);
-
- let mut packed = Vec::with_capacity(packed_size as usize);
- for slot in 0..packed_size {
- let value = self
- .world_reader
- .provider()
- .get_storage_at(
- self.world_reader.address(),
- key + slot.into(),
- self.world_reader.block_id(),
- )
- .await?;
-
- packed.push(value);
- }
-
- Ok(packed)
- }
-
- pub async fn entity(&self, keys: &[FieldElement]) -> Result> {
- let mut schema = self.schema().await?;
-
- let layout = self.layout().await?;
- let raw_values = self.entity_storage(keys).await?;
-
- let unpacked = unpack(raw_values, layout)?;
- let mut keys_and_unpacked = [keys, &unpacked].concat();
-
- schema.deserialize(&mut keys_and_unpacked)?;
-
- Ok(schema)
- }
}
diff --git a/crates/dojo-world/src/contracts/model_test.rs b/crates/dojo-world/src/contracts/model_test.rs
index 300ac6ede2..6b4b5e1981 100644
--- a/crates/dojo-world/src/contracts/model_test.rs
+++ b/crates/dojo-world/src/contracts/model_test.rs
@@ -7,6 +7,7 @@ use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty};
use starknet::accounts::ConnectedAccount;
use starknet::core::types::FieldElement;
+use crate::contracts::model::ModelReader;
use crate::contracts::world::test::deploy_world;
use crate::contracts::world::WorldContractReader;
diff --git a/crates/dojo-world/src/contracts/world.rs b/crates/dojo-world/src/contracts/world.rs
index 1f9440ff7a..9421d97bb9 100644
--- a/crates/dojo-world/src/contracts/world.rs
+++ b/crates/dojo-world/src/contracts/world.rs
@@ -11,7 +11,7 @@ use starknet::core::utils::{
use starknet::macros::selector;
use starknet::providers::{Provider, ProviderError};
-use super::model::{ModelError, ModelReader};
+use super::model::{ModelError, ModelRPCReader};
#[cfg(test)]
#[path = "world_test.rs"]
@@ -177,7 +177,7 @@ where
pub async fn model(
&'a self,
name: &str,
- ) -> Result, ModelError<::Error>>
+ ) -> Result, ModelError<::Error>>
{
self.reader.model(name).await
}
@@ -340,9 +340,12 @@ where
impl<'a, P> WorldContractReader
where
- P: Provider,
+ P: Provider + Sync + Send,
{
- pub async fn model(&'a self, name: &str) -> Result, ModelError> {
- ModelReader::new(name, self).await
+ pub async fn model(
+ &'a self,
+ name: &str,
+ ) -> Result, ModelError> {
+ ModelRPCReader::new(name, self).await
}
}
diff --git a/crates/sozo/src/ops/model.rs b/crates/sozo/src/ops/model.rs
index aba6fa548d..c22404dcec 100644
--- a/crates/sozo/src/ops/model.rs
+++ b/crates/sozo/src/ops/model.rs
@@ -1,4 +1,5 @@
use anyhow::Result;
+use dojo_world::contracts::model::ModelReader;
use dojo_world::contracts::world::WorldContractReader;
use dojo_world::metadata::Environment;
use starknet::core::types::{BlockId, BlockTag};
diff --git a/crates/torii/core/Cargo.toml b/crates/torii/core/Cargo.toml
index 9daf884848..829cddce9d 100644
--- a/crates/torii/core/Cargo.toml
+++ b/crates/torii/core/Cargo.toml
@@ -11,6 +11,7 @@ version.workspace = true
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
+base64.workspace = true
chrono.workspace = true
dojo-types = { path = "../../dojo-types" }
dojo-world = { path = "../../dojo-world", features = [ "contracts", "manifest" ] }
@@ -20,12 +21,12 @@ hex.workspace = true
lazy_static.workspace = true
log = "0.4.17"
once_cell.workspace = true
-reqwest = { version = "0.11.22", features = [ "blocking" ]}
+reqwest = { version = "0.11.22", features = [ "blocking" ] }
scarb-ui.workspace = true
serde.workspace = true
serde_json.workspace = true
slab = "0.4.2"
-sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] }
+sqlx.workspace = true
starknet-crypto.workspace = true
starknet.workspace = true
thiserror.workspace = true
@@ -33,7 +34,6 @@ tokio = { version = "1.32.0", features = [ "sync" ], default-features = true }
tokio-stream = "0.1.11"
tokio-util = "0.7.7"
tracing.workspace = true
-base64.workspace = true
[dev-dependencies]
camino.workspace = true
diff --git a/crates/torii/core/src/error.rs b/crates/torii/core/src/error.rs
new file mode 100644
index 0000000000..5384512428
--- /dev/null
+++ b/crates/torii/core/src/error.rs
@@ -0,0 +1,18 @@
+use starknet::core::types::FromStrError;
+use starknet::core::utils::CairoShortStringToFeltError;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("parsing error: {0}")]
+ Parse(#[from] ParseError),
+ #[error(transparent)]
+ Sql(#[from] sqlx::Error),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ParseError {
+ #[error(transparent)]
+ FromStr(#[from] FromStrError),
+ #[error(transparent)]
+ CairoShortStringToFelt(#[from] CairoShortStringToFeltError),
+}
diff --git a/crates/torii/core/src/lib.rs b/crates/torii/core/src/lib.rs
index 6049e63d98..e4690cc5ec 100644
--- a/crates/torii/core/src/lib.rs
+++ b/crates/torii/core/src/lib.rs
@@ -4,6 +4,8 @@ use sqlx::FromRow;
use crate::types::SQLFieldElement;
pub mod engine;
+pub mod error;
+pub mod model;
pub mod processors;
pub mod simple_broker;
pub mod sql;
diff --git a/crates/torii/grpc/src/server/utils.rs b/crates/torii/core/src/model.rs
similarity index 77%
rename from crates/torii/grpc/src/server/utils.rs
rename to crates/torii/core/src/model.rs
index 5e8772806d..fad073317f 100644
--- a/crates/torii/grpc/src/server/utils.rs
+++ b/crates/torii/core/src/model.rs
@@ -1,4 +1,80 @@
+use async_trait::async_trait;
use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty};
+use dojo_world::contracts::model::ModelReader;
+use sqlx::{Pool, Sqlite};
+use starknet::core::types::FieldElement;
+
+use super::error::{self, Error};
+
+pub struct ModelSQLReader {
+ /// The name of the model
+ name: String,
+ /// The class hash of the model
+ class_hash: FieldElement,
+ pool: Pool,
+ packed_size: FieldElement,
+ unpacked_size: FieldElement,
+ layout: Vec,
+}
+
+impl ModelSQLReader {
+ pub async fn new(name: &str, pool: Pool) -> Result {
+ let (name, class_hash, packed_size, unpacked_size, layout): (
+ String,
+ String,
+ u32,
+ u32,
+ String,
+ ) = sqlx::query_as(
+ "SELECT name, class_hash, packed_size, unpacked_size, layout FROM models WHERE id = ?",
+ )
+ .bind(name)
+ .fetch_one(&pool)
+ .await?;
+
+ let class_hash =
+ FieldElement::from_hex_be(&class_hash).map_err(error::ParseError::FromStr)?;
+ let packed_size = FieldElement::from(packed_size);
+ let unpacked_size = FieldElement::from(unpacked_size);
+
+ let layout = hex::decode(layout).unwrap();
+ let layout = layout.iter().map(|e| FieldElement::from(*e)).collect();
+
+ Ok(Self { name: name.clone(), class_hash, pool, packed_size, unpacked_size, layout })
+ }
+}
+
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+impl ModelReader for ModelSQLReader {
+ fn class_hash(&self) -> FieldElement {
+ self.class_hash
+ }
+
+ async fn schema(&self) -> Result {
+ let model_members: Vec = sqlx::query_as(
+ "SELECT id, model_idx, member_idx, name, type, type_enum, enum_options, key FROM \
+ model_members WHERE model_id = ? ORDER BY model_idx ASC, member_idx ASC",
+ )
+ .bind(self.name.clone())
+ .fetch_all(&self.pool)
+ .await?;
+
+ Ok(parse_sql_model_members(&self.name, &model_members))
+ }
+
+ async fn packed_size(&self) -> Result {
+ Ok(self.packed_size)
+ }
+
+ async fn unpacked_size(&self) -> Result {
+ Ok(self.unpacked_size)
+ }
+
+ async fn layout(&self) -> Result, Error> {
+ Ok(self.layout.clone())
+ }
+}
#[allow(unused)]
#[derive(Debug, sqlx::FromRow)]
@@ -73,7 +149,7 @@ mod tests {
use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty};
use super::SqlModelMember;
- use crate::server::utils::parse_sql_model_members;
+ use crate::model::parse_sql_model_members;
#[test]
fn parse_simple_model_members_to_ty() {
diff --git a/crates/torii/core/src/processors/register_model.rs b/crates/torii/core/src/processors/register_model.rs
index 2d63014c93..f632f6e437 100644
--- a/crates/torii/core/src/processors/register_model.rs
+++ b/crates/torii/core/src/processors/register_model.rs
@@ -1,5 +1,6 @@
use anyhow::{Error, Ok, Result};
use async_trait::async_trait;
+use dojo_world::contracts::model::ModelReader;
use dojo_world::contracts::world::WorldContractReader;
use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt};
use starknet::core::utils::parse_cairo_short_string;
diff --git a/crates/torii/graphql/Cargo.toml b/crates/torii/graphql/Cargo.toml
index 8371cdfe1f..e428b833b1 100644
--- a/crates/torii/graphql/Cargo.toml
+++ b/crates/torii/graphql/Cargo.toml
@@ -21,7 +21,7 @@ lazy_static.workspace = true
scarb-ui.workspace = true
serde.workspace = true
serde_json.workspace = true
-sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] }
+sqlx.workspace = true
strum.workspace = true
strum_macros.workspace = true
thiserror.workspace = true
diff --git a/crates/torii/grpc/Cargo.toml b/crates/torii/grpc/Cargo.toml
index aa3376f1f3..fc19299146 100644
--- a/crates/torii/grpc/Cargo.toml
+++ b/crates/torii/grpc/Cargo.toml
@@ -15,6 +15,7 @@ rayon.workspace = true
starknet-crypto.workspace = true
starknet.workspace = true
thiserror.workspace = true
+torii-core = { path = "../core", optional = true }
serde.workspace = true
strum_macros.workspace = true
@@ -22,10 +23,10 @@ strum_macros.workspace = true
# server
hex.workspace = true
hyper = "0.14.27"
+rand = "0.8.5"
serde_json.workspace = true
tower = "0.4.13"
tracing.workspace = true
-rand = "0.8.5"
[target.'cfg(target_arch = "wasm32")'.dependencies]
tonic-web-wasm-client.workspace = true
@@ -34,7 +35,7 @@ wasm-tonic.workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
prost.workspace = true
-sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "sqlite", "uuid" ] }
+sqlx.workspace = true
tokio-stream = "0.1.14"
tokio.workspace = true
tonic.workspace = true
@@ -46,4 +47,4 @@ wasm-tonic-build.workspace = true
[features]
client = [ ]
-server = [ ] # this feature can't be build on wasm32
+server = [ "dep:torii-core" ] # this feature can't be build on wasm32
diff --git a/crates/torii/grpc/src/server/error.rs b/crates/torii/grpc/src/server/error.rs
index 5c3a99c729..539de8f527 100644
--- a/crates/torii/grpc/src/server/error.rs
+++ b/crates/torii/grpc/src/server/error.rs
@@ -1,27 +1,10 @@
-use starknet::core::types::FromStrError;
-use starknet::core::utils::CairoShortStringToFeltError;
use starknet::providers::{Provider, ProviderError};
-
-#[derive(Debug, thiserror::Error)]
-pub enum Error {
- #[error("parsing error: {0}")]
- Parse(#[from] ParseError),
- #[error(transparent)]
- Sql(#[from] sqlx::Error),
-}
-
-#[derive(Debug, thiserror::Error)]
-pub enum ParseError {
- #[error(transparent)]
- FromStr(#[from] FromStrError),
- #[error(transparent)]
- CairoShortStringToFelt(#[from] CairoShortStringToFeltError),
-}
+use torii_core::error::ParseError;
#[derive(Debug, thiserror::Error)]
pub enum SubscriptionError {
#[error(transparent)]
- Parse(#[from] super::error::ParseError),
+ Parse(#[from] ParseError),
#[error(transparent)]
Provider(ProviderError<::Error>),
}
diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs
index 52a2ca734c..0fb13a61a7 100644
--- a/crates/torii/grpc/src/server/mod.rs
+++ b/crates/torii/grpc/src/server/mod.rs
@@ -1,7 +1,6 @@
pub mod error;
pub mod logger;
pub mod subscription;
-pub mod utils;
use std::pin::Pin;
use std::str::FromStr;
@@ -20,10 +19,10 @@ use starknet_crypto::FieldElement;
use tokio::sync::mpsc::Receiver;
use tokio_stream::wrappers::ReceiverStream;
use tonic::{Request, Response, Status};
+use torii_core::error::{Error, ParseError};
+use torii_core::model::{parse_sql_model_members, SqlModelMember};
-use self::error::{Error, ParseError};
use self::subscription::SubscribeRequest;
-use self::utils::{parse_sql_model_members, SqlModelMember};
use crate::protos::{self};
#[derive(Clone)]
diff --git a/crates/torii/server/Cargo.toml b/crates/torii/server/Cargo.toml
index d03afad9a9..2e266501ef 100644
--- a/crates/torii/server/Cargo.toml
+++ b/crates/torii/server/Cargo.toml
@@ -23,7 +23,7 @@ indexmap = "1.9.3"
scarb.workspace = true
serde.workspace = true
serde_json.workspace = true
-sqlx = { version = "0.6.2", features = [ "chrono", "macros", "offline", "runtime-actix-rustls", "uuid" ] }
+sqlx.workspace = true
starknet-crypto.workspace = true
starknet.workspace = true
tokio-stream = "0.1.11"