Skip to content

Commit

Permalink
Implement ModelReader trait (#1131)
Browse files Browse the repository at this point in the history
* Implement ModelReader trait

* Implement SQL model reader

* Disable torii-core for grpc client
  • Loading branch information
tarrencev authored Nov 1, 2023
1 parent 102b03b commit 39fb806
Show file tree
Hide file tree
Showing 16 changed files with 192 additions and 87 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
120 changes: 69 additions & 51 deletions crates/dojo-world/src/contracts/model.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -46,7 +47,17 @@ pub enum ModelError<P> {
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<E> {
fn class_hash(&self) -> FieldElement;
async fn schema(&self) -> Result<Ty, E>;
async fn packed_size(&self) -> Result<FieldElement, E>;
async fn unpacked_size(&self) -> Result<FieldElement, E>;
async fn layout(&self) -> Result<Vec<FieldElement>, E>;
}

pub struct ModelRPCReader<'a, P: Sync + Send> {
/// The name of the model
name: FieldElement,
/// The class hash of the model
Expand All @@ -55,14 +66,14 @@ pub struct ModelReader<'a, P> {
world_reader: &'a WorldContractReader<P>,
}

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<P>,
) -> Result<ModelReader<'a, P>, ModelError<P::Error>> {
) -> Result<ModelRPCReader<'a, P>, ModelError<P::Error>> {
let name = cairo_short_string_to_felt(name)?;

let class_hash = world
Expand All @@ -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<Vec<FieldElement>, ModelError<P::Error>> {
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<Ty, ModelError<P::Error>> {
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<ModelError<P::Error>> for ModelRPCReader<'a, P>
where
P: Provider + Sync + Send,
{
fn class_hash(&self) -> FieldElement {
self.class_hash
}

pub async fn schema(&self) -> Result<Ty, ModelError<P::Error>> {
async fn schema(&self) -> Result<Ty, ModelError<P::Error>> {
let entrypoint = get_selector_from_name(SCHEMA_SELECTOR_STR).unwrap();

let res = self
Expand All @@ -103,7 +163,7 @@ where
Ok(parse_ty(&res[1..])?)
}

pub async fn packed_size(&self) -> Result<FieldElement, ModelError<P::Error>> {
async fn packed_size(&self) -> Result<FieldElement, ModelError<P::Error>> {
let entrypoint = get_selector_from_name(PACKED_SIZE_SELECTOR_STR).unwrap();

let res = self
Expand All @@ -114,7 +174,7 @@ where
Ok(res[1])
}

pub async fn unpacked_size(&self) -> Result<FieldElement, ModelError<P::Error>> {
async fn unpacked_size(&self) -> Result<FieldElement, ModelError<P::Error>> {
let entrypoint = get_selector_from_name(UNPACKED_SIZE_SELECTOR_STR).unwrap();

let res = self
Expand All @@ -125,7 +185,7 @@ where
Ok(res[1])
}

pub async fn layout(&self) -> Result<Vec<FieldElement>, ModelError<P::Error>> {
async fn layout(&self) -> Result<Vec<FieldElement>, ModelError<P::Error>> {
let entrypoint = get_selector_from_name(LAYOUT_SELECTOR_STR).unwrap();

let res = self
Expand All @@ -135,46 +195,4 @@ where

Ok(res[2..].into())
}

pub async fn entity_storage(
&self,
keys: &[FieldElement],
) -> Result<Vec<FieldElement>, ModelError<P::Error>> {
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<Ty, ModelError<P::Error>> {
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)
}
}
1 change: 1 addition & 0 deletions crates/dojo-world/src/contracts/model_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
13 changes: 8 additions & 5 deletions crates/dojo-world/src/contracts/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -177,7 +177,7 @@ where
pub async fn model(
&'a self,
name: &str,
) -> Result<ModelReader<'_, &'a A::Provider>, ModelError<<A::Provider as Provider>::Error>>
) -> Result<ModelRPCReader<'_, &'a A::Provider>, ModelError<<A::Provider as Provider>::Error>>
{
self.reader.model(name).await
}
Expand Down Expand Up @@ -340,9 +340,12 @@ where

impl<'a, P> WorldContractReader<P>
where
P: Provider,
P: Provider + Sync + Send,
{
pub async fn model(&'a self, name: &str) -> Result<ModelReader<'a, P>, ModelError<P::Error>> {
ModelReader::new(name, self).await
pub async fn model(
&'a self,
name: &str,
) -> Result<ModelRPCReader<'a, P>, ModelError<P::Error>> {
ModelRPCReader::new(name, self).await
}
}
1 change: 1 addition & 0 deletions crates/sozo/src/ops/model.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
6 changes: 3 additions & 3 deletions crates/torii/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ] }
Expand All @@ -20,20 +21,19 @@ 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
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
Expand Down
18 changes: 18 additions & 0 deletions crates/torii/core/src/error.rs
Original file line number Diff line number Diff line change
@@ -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),
}
2 changes: 2 additions & 0 deletions crates/torii/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Sqlite>,
packed_size: FieldElement,
unpacked_size: FieldElement,
layout: Vec<FieldElement>,
}

impl ModelSQLReader {
pub async fn new(name: &str, pool: Pool<Sqlite>) -> Result<Self, Error> {
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<Error> for ModelSQLReader {
fn class_hash(&self) -> FieldElement {
self.class_hash
}

async fn schema(&self) -> Result<Ty, Error> {
let model_members: Vec<SqlModelMember> = 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<FieldElement, Error> {
Ok(self.packed_size)
}

async fn unpacked_size(&self) -> Result<FieldElement, Error> {
Ok(self.unpacked_size)
}

async fn layout(&self) -> Result<Vec<FieldElement>, Error> {
Ok(self.layout.clone())
}
}

#[allow(unused)]
#[derive(Debug, sqlx::FromRow)]
Expand Down Expand Up @@ -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() {
Expand Down
Loading

0 comments on commit 39fb806

Please sign in to comment.