diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index fd130ce2f0..af3fba18c6 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -39,6 +39,7 @@ use torii_core::executor::Executor; use torii_core::processors::store_transaction::StoreTransactionProcessor; use torii_core::processors::EventProcessorConfig; use torii_core::simple_broker::SimpleBroker; +use torii_core::sql::cache::ModelCache; use torii_core::sql::Sql; use torii_core::types::{Contract, ContractType, Model}; use torii_server::proxy::Proxy; @@ -243,7 +244,9 @@ async fn main() -> anyhow::Result<()> { executor.run().await.unwrap(); }); - let db = Sql::new(pool.clone(), sender.clone(), &args.indexing.contracts).await?; + let model_cache = Arc::new(ModelCache::new(pool.clone())); + let db = Sql::new(pool.clone(), sender.clone(), &args.indexing.contracts, model_cache.clone()) + .await?; let processors = Processors { transaction: vec![Box::new(StoreTransactionProcessor)], @@ -283,9 +286,15 @@ async fn main() -> anyhow::Result<()> { ); let shutdown_rx = shutdown_tx.subscribe(); - let (grpc_addr, grpc_server) = - torii_grpc::server::new(shutdown_rx, &pool, block_rx, world_address, Arc::clone(&provider)) - .await?; + let (grpc_addr, grpc_server) = torii_grpc::server::new( + shutdown_rx, + &pool, + block_rx, + world_address, + Arc::clone(&provider), + model_cache, + ) + .await?; let mut libp2p_relay_server = torii_relay::server::Relay::new( db, diff --git a/crates/dojo/types/src/primitive.rs b/crates/dojo/types/src/primitive.rs index 0c43e653ee..5c803ed614 100644 --- a/crates/dojo/types/src/primitive.rs +++ b/crates/dojo/types/src/primitive.rs @@ -190,46 +190,29 @@ impl Primitive { } } - pub fn to_sql_value(&self) -> Result { - let value = self.serialize()?; - - if value.is_empty() { - return Err(PrimitiveError::MissingFieldElement); - } - + pub fn to_sql_value(&self) -> String { match self { // Integers - Primitive::I8(_) => Ok(format!("{}", try_from_felt::(value[0])?)), - Primitive::I16(_) => Ok(format!("{}", try_from_felt::(value[0])?)), - Primitive::I32(_) => Ok(format!("{}", try_from_felt::(value[0])?)), - Primitive::I64(_) => Ok(format!("{}", try_from_felt::(value[0])?)), + Primitive::I8(i8) => format!("{}", i8.unwrap_or_default()), + Primitive::I16(i16) => format!("{}", i16.unwrap_or_default()), + Primitive::I32(i32) => format!("{}", i32.unwrap_or_default()), + Primitive::I64(i64) => format!("{}", i64.unwrap_or_default()), - Primitive::U8(_) - | Primitive::U16(_) - | Primitive::U32(_) - | Primitive::USize(_) - | Primitive::Bool(_) => Ok(format!("{}", value[0])), + Primitive::U8(u8) => format!("{}", u8.unwrap_or_default()), + Primitive::U16(u16) => format!("{}", u16.unwrap_or_default()), + Primitive::U32(u32) => format!("{}", u32.unwrap_or_default()), + Primitive::USize(u32) => format!("{}", u32.unwrap_or_default()), + Primitive::Bool(bool) => format!("{}", bool.unwrap_or_default() as i32), // Hex string - Primitive::I128(_) => Ok(format!("{:#064x}", try_from_felt::(value[0])?)), - Primitive::ContractAddress(_) - | Primitive::ClassHash(_) - | Primitive::Felt252(_) - | Primitive::U128(_) - | Primitive::U64(_) => Ok(format!("{:#064x}", value[0])), - - Primitive::U256(_) => { - if value.len() < 2 { - Err(PrimitiveError::NotEnoughFieldElements) - } else { - let mut buffer = [0u8; 32]; - let value0_bytes = value[0].to_bytes_be(); - let value1_bytes = value[1].to_bytes_be(); - buffer[16..].copy_from_slice(&value0_bytes[16..]); - buffer[..16].copy_from_slice(&value1_bytes[16..]); - Ok(format!("0x{}", hex::encode(buffer))) - } - } + Primitive::I128(i128) => format!("0x{:064x}", i128.unwrap_or_default()), + Primitive::ContractAddress(felt) => format!("0x{:064x}", felt.unwrap_or_default()), + Primitive::ClassHash(felt) => format!("0x{:064x}", felt.unwrap_or_default()), + Primitive::Felt252(felt) => format!("0x{:064x}", felt.unwrap_or_default()), + Primitive::U128(u128) => format!("0x{:064x}", u128.unwrap_or_default()), + Primitive::U64(u64) => format!("0x{:064x}", u64.unwrap_or_default()), + + Primitive::U256(u256) => format!("0x{:064x}", u256.unwrap_or_default()), } } @@ -436,7 +419,7 @@ mod tests { let primitive = Primitive::U256(Some(U256::from_be_hex( "aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbccccccccccccccccdddddddddddddddd", ))); - let sql_value = primitive.to_sql_value().unwrap(); + let sql_value = primitive.to_sql_value(); let serialized = primitive.serialize().unwrap(); let mut deserialized = primitive; diff --git a/crates/dojo/types/src/schema.rs b/crates/dojo/types/src/schema.rs index 943996d7ef..5ebaea0d42 100644 --- a/crates/dojo/types/src/schema.rs +++ b/crates/dojo/types/src/schema.rs @@ -173,6 +173,11 @@ impl Ty { } pub fn deserialize(&mut self, felts: &mut Vec) -> Result<(), PrimitiveError> { + if felts.is_empty() { + // return early if there are no felts to deserialize + return Ok(()); + } + match self { Ty::Primitive(c) => { c.deserialize(felts)?; @@ -224,6 +229,85 @@ impl Ty { } Ok(()) } + + /// Returns a new Ty containing only the differences between self and other + pub fn diff(&self, other: &Ty) -> Option { + match (self, other) { + (Ty::Struct(s1), Ty::Struct(s2)) => { + // Find members that exist in s1 but not in s2, or are different + let diff_children: Vec = s1 + .children + .iter() + .filter(|m1| { + s2.children + .iter() + .find(|m2| m2.name == m1.name) + .map_or(true, |m2| *m1 != m2) + }) + .cloned() + .collect(); + + if diff_children.is_empty() { + None + } else { + Some(Ty::Struct(Struct { name: s1.name.clone(), children: diff_children })) + } + } + (Ty::Enum(e1), Ty::Enum(e2)) => { + // Find options that exist in e1 but not in e2, or are different + let diff_options: Vec = e1 + .options + .iter() + .filter(|o1| { + e2.options.iter().find(|o2| o2.name == o1.name).map_or(true, |o2| *o1 != o2) + }) + .cloned() + .collect(); + + if diff_options.is_empty() { + None + } else { + Some(Ty::Enum(Enum { + name: e1.name.clone(), + option: e1.option, + options: diff_options, + })) + } + } + (Ty::Array(a1), Ty::Array(a2)) => { + if a1 == a2 { + None + } else { + Some(Ty::Array(a1.clone())) + } + } + (Ty::Tuple(t1), Ty::Tuple(t2)) => { + if t1 == t2 { + None + } else { + Some(Ty::Tuple(t1.clone())) + } + } + (Ty::ByteArray(b1), Ty::ByteArray(b2)) => { + if b1 == b2 { + None + } else { + Some(Ty::ByteArray(b1.clone())) + } + } + (Ty::Primitive(p1), Ty::Primitive(p2)) => { + if p1 == p2 { + None + } else { + Some(Ty::Primitive(*p1)) + } + } + // Different types entirely - we cannot diff them + _ => { + panic!("Type mismatch between self {:?} and other {:?}", self.name(), other.name()) + } + } + } } #[derive(Debug)] @@ -351,8 +435,8 @@ impl Enum { } } - pub fn to_sql_value(&self) -> Result { - self.option().map(|option| option.name.clone()) + pub fn to_sql_value(&self) -> String { + self.option().unwrap_or(&self.options[0]).name.clone() } } @@ -597,4 +681,77 @@ mod tests { assert_eq!(format_member(&member), expected); } } + + #[test] + fn test_ty_diff() { + // Test struct diff + let struct1 = Ty::Struct(Struct { + name: "TestStruct".to_string(), + children: vec![ + Member { + name: "field1".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + Member { + name: "field2".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + Member { + name: "field3".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }, + ], + }); + + let struct2 = Ty::Struct(Struct { + name: "TestStruct".to_string(), + children: vec![Member { + name: "field1".to_string(), + ty: Ty::Primitive(Primitive::U32(None)), + key: false, + }], + }); + + // Should show only field2 and field3 as differences + let diff = struct1.diff(&struct2).unwrap(); + if let Ty::Struct(s) = diff { + assert_eq!(s.children.len(), 2); + assert_eq!(s.children[0].name, "field2"); + assert_eq!(s.children[1].name, "field3"); + } else { + panic!("Expected Struct diff"); + } + + // Test enum diff + let enum1 = Ty::Enum(Enum { + name: "TestEnum".to_string(), + option: None, + options: vec![ + EnumOption { name: "Option1".to_string(), ty: Ty::Tuple(vec![]) }, + EnumOption { name: "Option2".to_string(), ty: Ty::Tuple(vec![]) }, + ], + }); + + let enum2 = Ty::Enum(Enum { + name: "TestEnum".to_string(), + option: None, + options: vec![EnumOption { name: "Option1".to_string(), ty: Ty::Tuple(vec![]) }], + }); + + // Should show only Option2 as difference + let diff = enum1.diff(&enum2).unwrap(); + if let Ty::Enum(e) = diff { + assert_eq!(e.options.len(), 1); + assert_eq!(e.options[0].name, "Option2"); + } else { + panic!("Expected Enum diff"); + } + + // Test no differences + let same_struct = struct2.diff(&struct2); + assert!(same_struct.is_none()); + } } diff --git a/crates/dojo/world/src/contracts/model.rs b/crates/dojo/world/src/contracts/model.rs index 623b164a64..7753767e8a 100644 --- a/crates/dojo/world/src/contracts/model.rs +++ b/crates/dojo/world/src/contracts/model.rs @@ -5,7 +5,7 @@ use cainome::cairo_serde::{CairoSerde as _, ContractAddress, Error as CainomeErr use dojo_types::packing::{PackingError, ParseError}; use dojo_types::primitive::{Primitive, PrimitiveError}; use dojo_types::schema::{Enum, EnumOption, Member, Struct, Ty}; -use starknet::core::types::Felt; +use starknet::core::types::{BlockId, Felt}; use starknet::core::utils::{ cairo_short_string_to_felt, parse_cairo_short_string, CairoShortStringToFeltError, NonAsciiNameError, ParseCairoShortStringError, @@ -86,13 +86,22 @@ where namespace: &str, name: &str, world: &'a WorldContractReader

, + ) -> Result, ModelError> { + Self::new_with_block(namespace, name, world, world.block_id).await + } + + pub async fn new_with_block( + namespace: &str, + name: &str, + world: &'a WorldContractReader

, + block_id: BlockId, ) -> Result, ModelError> { let model_selector = naming::compute_selector_from_names(namespace, name); // Events are also considered like models from a off-chain perspective. They both have // introspection and convey type information. let (contract_address, class_hash) = - match world.resource(&model_selector).block_id(world.block_id).call().await? { + match world.resource(&model_selector).block_id(block_id).call().await? { abigen::world::Resource::Model((address, hash)) => (address, hash), abigen::world::Resource::Event((address, hash)) => (address, hash), _ => return Err(ModelError::ModelNotFound), @@ -104,7 +113,8 @@ where return Err(ModelError::ModelNotFound); } - let model_reader = ModelContractReader::new(contract_address.into(), world.provider()); + let mut model_reader = ModelContractReader::new(contract_address.into(), world.provider()); + model_reader.set_block(block_id); Ok(Self { namespace: namespace.into(), @@ -176,7 +186,7 @@ where parse_schema(&abigen::model::Ty::Struct(res)).map_err(ModelError::Parse) } - // For non fixed layouts, packed and unpacked sizes are None. + // For non fixed layouts, packed and unpacked sizes are None. // Therefore we return 0 in this case. async fn packed_size(&self) -> Result { Ok(self.model_reader.packed_size().call().await?.unwrap_or(0)) diff --git a/crates/dojo/world/src/contracts/world.rs b/crates/dojo/world/src/contracts/world.rs index 11224da756..4508c5db07 100644 --- a/crates/dojo/world/src/contracts/world.rs +++ b/crates/dojo/world/src/contracts/world.rs @@ -1,5 +1,6 @@ use std::result::Result; +use starknet::core::types::BlockId; use starknet::providers::Provider; pub use super::abigen::world::{ @@ -33,4 +34,13 @@ where ) -> Result, ModelError> { ModelRPCReader::new(namespace, name, self).await } + + pub async fn model_reader_with_block( + &self, + namespace: &str, + name: &str, + block_id: BlockId, + ) -> Result, ModelError> { + ModelRPCReader::new_with_block(namespace, name, self, block_id).await + } } diff --git a/crates/sozo/ops/src/model.rs b/crates/sozo/ops/src/model.rs index d4d471eeb9..38cdfabe5d 100644 --- a/crates/sozo/ops/src/model.rs +++ b/crates/sozo/ops/src/model.rs @@ -412,7 +412,7 @@ fn format_primitive( let mut _p = *p; let _ = _p.deserialize(values); - format!("{}{}", _start_indent(level, start_indent), _p.to_sql_value().unwrap()) + format!("{}{}", _start_indent(level, start_indent), _p.to_sql_value()) } fn format_byte_array(values: &mut Vec, level: usize, start_indent: bool) -> String { diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index b73b66bcf9..ccac372feb 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -37,6 +37,8 @@ use crate::processors::store_del_record::StoreDelRecordProcessor; use crate::processors::store_set_record::StoreSetRecordProcessor; use crate::processors::store_update_member::StoreUpdateMemberProcessor; use crate::processors::store_update_record::StoreUpdateRecordProcessor; +use crate::processors::upgrade_event::UpgradeEventProcessor; +use crate::processors::upgrade_model::UpgradeModelProcessor; use crate::processors::{ BlockProcessor, EventProcessor, EventProcessorConfig, TransactionProcessor, }; @@ -76,6 +78,8 @@ impl Processors

{ vec![ Box::new(RegisterModelProcessor) as Box>, Box::new(RegisterEventProcessor) as Box>, + Box::new(UpgradeModelProcessor) as Box>, + Box::new(UpgradeEventProcessor) as Box>, Box::new(StoreSetRecordProcessor), Box::new(StoreDelRecordProcessor), Box::new(StoreUpdateRecordProcessor), diff --git a/crates/torii/core/src/model.rs b/crates/torii/core/src/model.rs index 6660bba4fb..e2d06c10c8 100644 --- a/crates/torii/core/src/model.rs +++ b/crates/torii/core/src/model.rs @@ -238,7 +238,7 @@ pub fn build_sql_query( struct TableInfo { table_name: String, parent_table: Option, - is_optional: bool, + // is_optional: bool, depth: usize, // Track nesting depth for proper ordering } @@ -250,7 +250,7 @@ pub fn build_sql_query( selections: &mut Vec, tables: &mut Vec, arrays_queries: &mut HashMap, Vec)>, - parent_is_optional: bool, + _parent_is_optional: bool, depth: usize, ) { match &ty { @@ -261,7 +261,7 @@ pub fn build_sql_query( tables.push(TableInfo { table_name: table_name.clone(), parent_table: if path.is_empty() { None } else { Some(path.to_string()) }, - is_optional: parent_is_optional, + // is_optional: parent_is_optional, depth, }); @@ -273,7 +273,7 @@ pub fn build_sql_query( selections, tables, arrays_queries, - parent_is_optional, + _parent_is_optional, depth + 1, ); } @@ -284,7 +284,7 @@ pub fn build_sql_query( tables.push(TableInfo { table_name: table_name.clone(), parent_table: Some(path.to_string()), - is_optional: parent_is_optional, + // is_optional: parent_is_optional, depth, }); @@ -296,7 +296,7 @@ pub fn build_sql_query( selections, tables, arrays_queries, - parent_is_optional, + _parent_is_optional, depth + 1, ); } @@ -309,7 +309,7 @@ pub fn build_sql_query( let mut array_tables = vec![TableInfo { table_name: table_name.clone(), parent_table: Some(path.to_string()), - is_optional: true, + // is_optional: true, depth, }]; @@ -356,7 +356,7 @@ pub fn build_sql_query( tables.push(TableInfo { table_name, parent_table: Some(path.to_string()), - is_optional: parent_is_optional || is_optional, + // is_optional: parent_is_optional || is_optional, depth, }); } @@ -395,10 +395,9 @@ pub fn build_sql_query( let join_clause = global_tables .iter() .map(|table| { - let join_type = if table.is_optional { "LEFT JOIN" } else { "JOIN" }; let join_condition = format!("{entities_table}.id = [{}].{entity_relation_column}", table.table_name); - format!(" {join_type} [{}] ON {join_condition}", table.table_name) + format!(" LEFT JOIN [{}] ON {join_condition}", table.table_name) }) .collect::>() .join(" "); @@ -424,9 +423,8 @@ pub fn build_sql_query( table.table_name, table.table_name ) } else { - let join_type = if table.is_optional { "LEFT JOIN" } else { "JOIN" }; format!( - " {join_type} [{}] ON [{}].full_array_id = [{}].full_array_id", + " LEFT JOIN [{}] ON [{}].full_array_id = [{}].full_array_id", table.table_name, table.table_name, table.parent_table.as_ref().unwrap() @@ -512,9 +510,12 @@ pub fn map_row_to_ty( Primitive::I128(_) => { let value = row.try_get::(&column_name)?; let hex_str = value.trim_start_matches("0x"); - primitive.set_i128(Some( - i128::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, - ))?; + + if !hex_str.is_empty() { + primitive.set_i128(Some( + i128::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, + ))?; + } } Primitive::U8(_) => { let value = row.try_get::(&column_name)?; @@ -531,21 +532,30 @@ pub fn map_row_to_ty( Primitive::U64(_) => { let value = row.try_get::(&column_name)?; let hex_str = value.trim_start_matches("0x"); - primitive.set_u64(Some( - u64::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, - ))?; + + if !hex_str.is_empty() { + primitive.set_u64(Some( + u64::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, + ))?; + } } Primitive::U128(_) => { let value = row.try_get::(&column_name)?; let hex_str = value.trim_start_matches("0x"); - primitive.set_u128(Some( - u128::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, - ))?; + + if !hex_str.is_empty() { + primitive.set_u128(Some( + u128::from_str_radix(hex_str, 16).map_err(ParseError::ParseIntError)?, + ))?; + } } Primitive::U256(_) => { let value = row.try_get::(&column_name)?; let hex_str = value.trim_start_matches("0x"); - primitive.set_u256(Some(U256::from_be_hex(hex_str)))?; + + if !hex_str.is_empty() { + primitive.set_u256(Some(U256::from_be_hex(hex_str)))?; + } } Primitive::USize(_) => { let value = row.try_get::(&column_name)?; @@ -557,26 +567,35 @@ pub fn map_row_to_ty( } Primitive::Felt252(_) => { let value = row.try_get::(&column_name)?; - primitive - .set_felt252(Some(Felt::from_str(&value).map_err(ParseError::FromStr)?))?; + if !value.is_empty() { + primitive.set_felt252(Some( + Felt::from_str(&value).map_err(ParseError::FromStr)?, + ))?; + } } Primitive::ClassHash(_) => { let value = row.try_get::(&column_name)?; - primitive.set_class_hash(Some( - Felt::from_str(&value).map_err(ParseError::FromStr)?, - ))?; + if !value.is_empty() { + primitive.set_class_hash(Some( + Felt::from_str(&value).map_err(ParseError::FromStr)?, + ))?; + } } Primitive::ContractAddress(_) => { let value = row.try_get::(&column_name)?; - primitive.set_contract_address(Some( - Felt::from_str(&value).map_err(ParseError::FromStr)?, - ))?; + if !value.is_empty() { + primitive.set_contract_address(Some( + Felt::from_str(&value).map_err(ParseError::FromStr)?, + ))?; + } } }; } Ty::Enum(enum_ty) => { let option_name = row.try_get::(&column_name)?; - enum_ty.set_option(&option_name)?; + if !option_name.is_empty() { + enum_ty.set_option(&option_name)?; + } let path = [path, name].join("$"); for option in &mut enum_ty.options { @@ -1070,12 +1089,12 @@ mod tests { [Test-Position$vec].external_y AS \"Test-Position$vec.y\", \ [Test-PlayerConfig$favorite_item].external_Some AS \ \"Test-PlayerConfig$favorite_item.Some\", [Test-PlayerConfig].external_favorite_item \ - AS \"Test-PlayerConfig.favorite_item\" FROM entities JOIN [Test-Position] ON \ - entities.id = [Test-Position].entity_id JOIN [Test-PlayerConfig] ON entities.id = \ - [Test-PlayerConfig].entity_id JOIN [Test-Position$vec] ON entities.id = \ - [Test-Position$vec].entity_id LEFT JOIN [Test-PlayerConfig$favorite_item] ON \ - entities.id = [Test-PlayerConfig$favorite_item].entity_id ORDER BY entities.event_id \ - DESC"; + AS \"Test-PlayerConfig.favorite_item\" FROM entities LEFT JOIN [Test-Position] ON \ + entities.id = [Test-Position].entity_id LEFT JOIN [Test-PlayerConfig] ON \ + entities.id = [Test-PlayerConfig].entity_id LEFT JOIN [Test-Position$vec] ON \ + entities.id = [Test-Position$vec].entity_id LEFT JOIN \ + [Test-PlayerConfig$favorite_item] ON entities.id = \ + [Test-PlayerConfig$favorite_item].entity_id ORDER BY entities.event_id DESC"; // todo: completely tests arrays assert_eq!(query.0, expected_query); } diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index fa24de5e9b..5d0117f15e 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -22,6 +22,8 @@ pub mod store_set_record; pub mod store_transaction; pub mod store_update_member; pub mod store_update_record; +pub mod upgrade_event; +pub mod upgrade_model; const MODEL_INDEX: usize = 0; const ENTITY_ID_INDEX: usize = 1; diff --git a/crates/torii/core/src/processors/register_event.rs b/crates/torii/core/src/processors/register_event.rs index 6b5f0af9a0..b026ef3770 100644 --- a/crates/torii/core/src/processors/register_event.rs +++ b/crates/torii/core/src/processors/register_event.rs @@ -84,18 +84,19 @@ where contract_address = ?event.address, packed_size = %packed_size, unpacked_size = %unpacked_size, - "Registered model content." + "Registered event content." ); db.register_model( &namespace, - schema, + &schema, layout, event.class_hash.into(), event.address.into(), packed_size, unpacked_size, block_timestamp, + None, ) .await?; diff --git a/crates/torii/core/src/processors/register_model.rs b/crates/torii/core/src/processors/register_model.rs index 58c7333a2f..b994313d08 100644 --- a/crates/torii/core/src/processors/register_model.rs +++ b/crates/torii/core/src/processors/register_model.rs @@ -86,13 +86,14 @@ where db.register_model( &namespace, - schema, + &schema, layout, event.class_hash.into(), event.address.into(), packed_size, unpacked_size, block_timestamp, + None, ) .await?; diff --git a/crates/torii/core/src/processors/upgrade_event.rs b/crates/torii/core/src/processors/upgrade_event.rs new file mode 100644 index 0000000000..8479d9ab05 --- /dev/null +++ b/crates/torii/core/src/processors/upgrade_event.rs @@ -0,0 +1,114 @@ +use anyhow::{Error, Ok, Result}; +use async_trait::async_trait; +use dojo_world::contracts::abigen::world::Event as WorldEvent; +use dojo_world::contracts::model::ModelReader; +use dojo_world::contracts::world::WorldContractReader; +use starknet::core::types::Event; +use starknet::providers::Provider; +use tracing::{debug, info}; + +use super::{EventProcessor, EventProcessorConfig}; +use crate::sql::Sql; + +pub(crate) const LOG_TARGET: &str = "torii_core::processors::upgrade_event"; + +#[derive(Default, Debug)] +pub struct UpgradeEventProcessor; + +#[async_trait] +impl

EventProcessor

for UpgradeEventProcessor +where + P: Provider + Send + Sync + std::fmt::Debug, +{ + fn event_key(&self) -> String { + "EventUpgraded".to_string() + } + + // We might not need this anymore, since we don't have fallback and all world events must + // be handled. + fn validate(&self, _event: &Event) -> bool { + true + } + + async fn process( + &self, + world: &WorldContractReader

, + db: &mut Sql, + _block_number: u64, + block_timestamp: u64, + _event_id: &str, + event: &Event, + _config: &EventProcessorConfig, + ) -> Result<(), Error> { + // Torii version is coupled to the world version, so we can expect the event to be well + // formed. + let event = match WorldEvent::try_from(event).unwrap_or_else(|_| { + panic!( + "Expected {} event to be well formed.", + >::event_key(self) + ) + }) { + WorldEvent::EventUpgraded(e) => e, + _ => { + unreachable!() + } + }; + + // Called model here by language, but it's an event. Torii rework will make clear + // distinction. + let model = db.model(event.selector).await?; + let name = model.name; + let namespace = model.namespace; + let prev_schema = model.schema; + + let model = world.model_reader(&namespace, &name).await?; + let new_schema = model.schema().await?; + let schema_diff = new_schema.diff(&prev_schema); + // No changes to the schema. This can happen if torii is re-run with a fresh database. + // As the register model fetches the latest schema from the chain. + if schema_diff.is_none() { + return Ok(()); + } + + let schema_diff = schema_diff.unwrap(); + let layout = model.layout().await?; + + // Events are never stored onchain, hence no packing or unpacking. + let unpacked_size: u32 = 0; + let packed_size: u32 = 0; + + info!( + target: LOG_TARGET, + namespace = %namespace, + name = %name, + "Upgraded event." + ); + + debug!( + target: LOG_TARGET, + name, + diff = ?schema_diff, + layout = ?layout, + class_hash = ?event.class_hash, + contract_address = ?event.address, + packed_size = %packed_size, + unpacked_size = %unpacked_size, + "Upgraded event content." + ); + + db.register_model( + &namespace, + &new_schema, + layout, + event.class_hash.into(), + event.address.into(), + packed_size, + unpacked_size, + block_timestamp, + Some(&schema_diff), + ) + .await?; + + Ok(()) + } +} diff --git a/crates/torii/core/src/processors/upgrade_model.rs b/crates/torii/core/src/processors/upgrade_model.rs new file mode 100644 index 0000000000..403e1e17fd --- /dev/null +++ b/crates/torii/core/src/processors/upgrade_model.rs @@ -0,0 +1,111 @@ +use anyhow::{Error, Ok, Result}; +use async_trait::async_trait; +use dojo_world::contracts::abigen::world::Event as WorldEvent; +use dojo_world::contracts::model::ModelReader; +use dojo_world::contracts::world::WorldContractReader; +use starknet::core::types::Event; +use starknet::providers::Provider; +use tracing::{debug, info}; + +use super::{EventProcessor, EventProcessorConfig}; +use crate::sql::Sql; + +pub(crate) const LOG_TARGET: &str = "torii_core::processors::upgrade_model"; + +#[derive(Default, Debug)] +pub struct UpgradeModelProcessor; + +#[async_trait] +impl

EventProcessor

for UpgradeModelProcessor +where + P: Provider + Send + Sync + std::fmt::Debug, +{ + fn event_key(&self) -> String { + "ModelUpgraded".to_string() + } + + // We might not need this anymore, since we don't have fallback and all world events must + // be handled. + fn validate(&self, _event: &Event) -> bool { + true + } + + async fn process( + &self, + world: &WorldContractReader

, + db: &mut Sql, + _block_number: u64, + block_timestamp: u64, + _event_id: &str, + event: &Event, + _config: &EventProcessorConfig, + ) -> Result<(), Error> { + // Torii version is coupled to the world version, so we can expect the event to be well + // formed. + let event = match WorldEvent::try_from(event).unwrap_or_else(|_| { + panic!( + "Expected {} event to be well formed.", + >::event_key(self) + ) + }) { + WorldEvent::ModelUpgraded(e) => e, + _ => { + unreachable!() + } + }; + + let model = db.model(event.selector).await?; + let name = model.name; + let namespace = model.namespace; + let prev_schema = model.schema; + + let model = world.model_reader(&namespace, &name).await?; + let new_schema = model.schema().await?; + let schema_diff = new_schema.diff(&prev_schema); + // No changes to the schema. This can happen if torii is re-run with a fresh database. + // As the register model fetches the latest schema from the chain. + if schema_diff.is_none() { + return Ok(()); + } + + let schema_diff = schema_diff.unwrap(); + let layout = model.layout().await?; + + let unpacked_size: u32 = model.unpacked_size().await?; + let packed_size: u32 = model.packed_size().await?; + + info!( + target: LOG_TARGET, + namespace = %namespace, + name = %name, + "Upgraded model." + ); + + debug!( + target: LOG_TARGET, + name = %name, + diff = ?schema_diff, + layout = ?layout, + class_hash = ?event.class_hash, + contract_address = ?event.address, + packed_size = %packed_size, + unpacked_size = %unpacked_size, + "Upgraded model content." + ); + + db.register_model( + &namespace, + &new_schema, + layout, + event.class_hash.into(), + event.address.into(), + packed_size, + unpacked_size, + block_timestamp, + Some(&schema_diff), + ) + .await?; + + Ok(()) + } +} diff --git a/crates/torii/core/src/sql/mod.rs b/crates/torii/core/src/sql/mod.rs index ae72371039..8c6302447d 100644 --- a/crates/torii/core/src/sql/mod.rs +++ b/crates/torii/core/src/sql/mod.rs @@ -59,7 +59,8 @@ impl Sql { pub async fn new( pool: Pool, executor: UnboundedSender, - contracts: &Vec, + contracts: &[Contract], + model_cache: Arc, ) -> Result { for contract in contracts { executor.send(QueryMessage::other( @@ -75,12 +76,7 @@ impl Sql { } let local_cache = LocalCache::new(pool.clone()).await; - let db = Self { - pool: pool.clone(), - executor, - model_cache: Arc::new(ModelCache::new(pool.clone())), - local_cache, - }; + let db = Self { pool: pool.clone(), executor, model_cache, local_cache }; db.execute().await?; @@ -254,13 +250,14 @@ impl Sql { pub async fn register_model( &mut self, namespace: &str, - model: Ty, + model: &Ty, layout: Layout, class_hash: Felt, contract_address: Felt, packed_size: u32, unpacked_size: u32, block_timestamp: u64, + upgrade_diff: Option<&Ty>, ) -> Result<()> { let selector = compute_selector_from_names(namespace, &model.name()); let namespaced_name = format!("{}-{}", namespace, model.name()); @@ -292,12 +289,13 @@ impl Sql { let mut model_idx = 0_i64; self.build_register_queries_recursive( selector, - &model, + model, vec![namespaced_name.clone()], &mut model_idx, block_timestamp, &mut 0, &mut 0, + upgrade_diff, )?; // we set the model in the cache directly @@ -633,6 +631,7 @@ impl Sql { block_timestamp: u64, array_idx: &mut usize, parent_array_idx: &mut usize, + upgrade_diff: Option<&Ty>, ) -> Result<()> { if let Ty::Enum(e) = model { if e.options.iter().all(|o| if let Ty::Tuple(t) = &o.ty { t.is_empty() } else { false }) @@ -649,6 +648,7 @@ impl Sql { block_timestamp, *array_idx, *parent_array_idx, + upgrade_diff, )?; let mut build_member = |pathname: &str, member: &Ty| -> Result<()> { @@ -669,6 +669,8 @@ impl Sql { block_timestamp, &mut (*array_idx + if let Ty::Array(_) = member { 1 } else { 0 }), &mut (*parent_array_idx + if let Ty::Array(_) = model { 1 } else { 0 }), + // nested members are not upgrades + None, )?; Ok(()) @@ -762,11 +764,11 @@ impl Sql { match &member.ty { Ty::Primitive(ty) => { columns.push(format!("external_{}", &member.name)); - arguments.push(Argument::String(ty.to_sql_value().unwrap())); + arguments.push(Argument::String(ty.to_sql_value())); } Ty::Enum(e) => { columns.push(format!("external_{}", &member.name)); - arguments.push(Argument::String(e.to_sql_value().unwrap())); + arguments.push(Argument::String(e.to_sql_value())); } Ty::ByteArray(b) => { columns.push(format!("external_{}", &member.name)); @@ -1025,6 +1027,7 @@ impl Sql { block_timestamp: u64, array_idx: usize, parent_array_idx: usize, + upgrade_diff: Option<&Ty>, ) -> Result<()> { let table_id = path.join("$"); let mut indices = Vec::new(); @@ -1034,20 +1037,37 @@ impl Sql { entity_id TEXT, event_message_id TEXT, " ); + let mut alter_table_queries = Vec::new(); + if array_idx > 0 { // index columns for i in 0..array_idx { - create_table_query.push_str(&format!("idx_{i} INTEGER NOT NULL, ", i = i)); + let column = format!("idx_{i} INTEGER NOT NULL"); + create_table_query.push_str(&format!("{column}, ")); + + alter_table_queries.push(format!( + "ALTER TABLE [{table_id}] ADD COLUMN idx_{i} INTEGER NOT NULL DEFAULT 0" + )); } // full array id column create_table_query.push_str("full_array_id TEXT NOT NULL UNIQUE, "); + alter_table_queries.push(format!( + "ALTER TABLE [{table_id}] ADD COLUMN full_array_id TEXT NOT NULL UNIQUE DEFAULT ''" + )); } let mut build_member = |name: &str, ty: &Ty, options: &mut Option| { if let Ok(cairo_type) = Primitive::from_str(&ty.name()) { - create_table_query - .push_str(&format!("external_{name} {}, ", cairo_type.to_sql_type())); + let sql_type = cairo_type.to_sql_type(); + let column = format!("external_{name} {sql_type}"); + + create_table_query.push_str(&format!("{column}, ")); + + alter_table_queries.push(format!( + "ALTER TABLE [{table_id}] ADD COLUMN external_{name} {sql_type}" + )); + indices.push(format!( "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] \ (external_{name});" @@ -1060,12 +1080,12 @@ impl Sql { .collect::>() .join(", "); - create_table_query.push_str(&format!( - "external_{name} TEXT CHECK(external_{name} IN ({all_options})) ", - )); + let column = + format!("external_{name} TEXT CHECK(external_{name} IN ({all_options}))",); - // if we're an array, we could have multiple enum options - create_table_query.push_str(if array_idx > 0 { ", " } else { "NOT NULL, " }); + create_table_query.push_str(&format!("{column}, ")); + + alter_table_queries.push(format!("ALTER TABLE [{table_id}] ADD COLUMN {column}")); indices.push(format!( "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] \ @@ -1081,7 +1101,12 @@ impl Sql { .to_string(), )); } else if let Ty::ByteArray(_) = &ty { - create_table_query.push_str(&format!("external_{name} TEXT, ")); + let column = format!("external_{name} TEXT"); + + create_table_query.push_str(&format!("{column}, ")); + + alter_table_queries.push(format!("ALTER TABLE [{table_id}] ADD COLUMN {column}")); + indices.push(format!( "CREATE INDEX IF NOT EXISTS [idx_{table_id}_{name}] ON [{table_id}] \ (external_{name});" @@ -1092,6 +1117,18 @@ impl Sql { match model { Ty::Struct(s) => { for (member_idx, member) in s.children.iter().enumerate() { + if let Some(upgrade_diff) = upgrade_diff { + if !upgrade_diff + .as_struct() + .unwrap() + .children + .iter() + .any(|m| m.name == member.name) + { + continue; + } + } + let name = member.name.clone(); let mut options = None; // TEMP: doesnt support complex enums yet @@ -1248,10 +1285,16 @@ impl Sql { create_table_query .push_str("FOREIGN KEY (event_message_id) REFERENCES event_messages(id));"); - self.executor.send(QueryMessage::other(create_table_query, vec![]))?; + if upgrade_diff.is_some() { + for alter_query in alter_table_queries { + self.executor.send(QueryMessage::other(alter_query, vec![]))?; + } + } else { + self.executor.send(QueryMessage::other(create_table_query, vec![]))?; + } - for s in indices.iter() { - self.executor.send(QueryMessage::other(s.to_string(), vec![]))?; + for index_query in indices { + self.executor.send(QueryMessage::other(index_query, vec![]))?; } Ok(()) diff --git a/crates/torii/core/src/sql/test.rs b/crates/torii/core/src/sql/test.rs index 65076fffa3..dde4934ee1 100644 --- a/crates/torii/core/src/sql/test.rs +++ b/crates/torii/core/src/sql/test.rs @@ -23,6 +23,7 @@ use tokio::sync::broadcast; use crate::engine::{Engine, EngineConfig, Processors}; use crate::executor::Executor; +use crate::sql::cache::ModelCache; use crate::sql::Sql; use crate::types::{Contract, ContractType}; @@ -124,10 +125,12 @@ async fn test_load_from_remote(sequencer: &RunnerCtx) { executor.run().await.unwrap(); }); + let model_cache = Arc::new(ModelCache::new(pool.clone())); let db = Sql::new( pool.clone(), sender.clone(), - &vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }], + &[Contract { address: world_reader.address, r#type: ContractType::WORLD }], + model_cache.clone(), ) .await .unwrap(); @@ -282,10 +285,12 @@ async fn test_load_from_remote_del(sequencer: &RunnerCtx) { executor.run().await.unwrap(); }); + let model_cache = Arc::new(ModelCache::new(pool.clone())); let db = Sql::new( pool.clone(), sender.clone(), - &vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }], + &[Contract { address: world_reader.address, r#type: ContractType::WORLD }], + model_cache.clone(), ) .await .unwrap(); @@ -368,10 +373,12 @@ async fn test_update_with_set_record(sequencer: &RunnerCtx) { executor.run().await.unwrap(); }); + let model_cache = Arc::new(ModelCache::new(pool.clone())); let db = Sql::new( pool.clone(), sender.clone(), - &vec![Contract { address: world_reader.address, r#type: ContractType::WORLD }], + &[Contract { address: world_reader.address, r#type: ContractType::WORLD }], + model_cache.clone(), ) .await .unwrap(); diff --git a/crates/torii/graphql/src/tests/metadata_test.rs b/crates/torii/graphql/src/tests/metadata_test.rs index d6b00ea72b..408731d569 100644 --- a/crates/torii/graphql/src/tests/metadata_test.rs +++ b/crates/torii/graphql/src/tests/metadata_test.rs @@ -1,10 +1,13 @@ #[cfg(test)] mod tests { + use std::sync::Arc; + use dojo_world::config::{ProfileConfig, WorldMetadata}; use sqlx::SqlitePool; use starknet::core::types::Felt; use tokio::sync::broadcast; use torii_core::executor::Executor; + use torii_core::sql::cache::ModelCache; use torii_core::sql::Sql; use torii_core::types::{Contract, ContractType}; @@ -56,10 +59,12 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -120,10 +125,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); diff --git a/crates/torii/graphql/src/tests/mod.rs b/crates/torii/graphql/src/tests/mod.rs index c21419bbba..d351651b6c 100644 --- a/crates/torii/graphql/src/tests/mod.rs +++ b/crates/torii/graphql/src/tests/mod.rs @@ -29,6 +29,7 @@ use tokio::sync::broadcast; use tokio_stream::StreamExt; use torii_core::engine::{Engine, EngineConfig, Processors}; use torii_core::executor::Executor; +use torii_core::sql::cache::ModelCache; use torii_core::sql::Sql; use torii_core::types::{Contract, ContractType}; @@ -211,7 +212,7 @@ pub async fn run_graphql_subscription( pub async fn model_fixtures(db: &mut Sql) { db.register_model( "types_test", - Ty::Struct(Struct { + &Ty::Struct(Struct { name: "Record".to_string(), children: vec![ Member { @@ -266,6 +267,7 @@ pub async fn model_fixtures(db: &mut Sql) { 0, 0, 1710754478_u64, + None, ) .await .unwrap(); @@ -345,10 +347,13 @@ pub async fn spinup_types_test(path: &str) -> Result { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let db = Sql::new( pool.clone(), sender, - &vec![Contract { address: world_address, r#type: ContractType::WORLD }], + &[Contract { address: world_address, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); diff --git a/crates/torii/graphql/src/tests/subscription_test.rs b/crates/torii/graphql/src/tests/subscription_test.rs index 583779c97d..f1b6455b91 100644 --- a/crates/torii/graphql/src/tests/subscription_test.rs +++ b/crates/torii/graphql/src/tests/subscription_test.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod tests { use std::str::FromStr; + use std::sync::Arc; use std::time::Duration; use async_graphql::value; @@ -14,6 +15,7 @@ mod tests { use starknet_crypto::{poseidon_hash_many, Felt}; use tokio::sync::{broadcast, mpsc}; use torii_core::executor::Executor; + use torii_core::sql::cache::ModelCache; use torii_core::sql::utils::felts_to_sql_string; use torii_core::sql::Sql; use torii_core::types::{Contract, ContractType}; @@ -30,10 +32,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -177,10 +182,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -304,10 +312,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -338,13 +349,14 @@ mod tests { }); db.register_model( &namespace, - model, + &model, Layout::Fixed(vec![]), class_hash, contract_address, 0, 0, block_timestamp, + None, ) .await .unwrap(); @@ -381,10 +393,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -414,13 +429,14 @@ mod tests { }); db.register_model( &namespace, - model, + &model, Layout::Fixed(vec![]), class_hash, contract_address, 0, 0, block_timestamp, + None, ) .await .unwrap(); @@ -459,10 +475,13 @@ mod tests { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); diff --git a/crates/torii/grpc/src/server/mod.rs b/crates/torii/grpc/src/server/mod.rs index 4e710713e7..858c4c523c 100644 --- a/crates/torii/grpc/src/server/mod.rs +++ b/crates/torii/grpc/src/server/mod.rs @@ -105,8 +105,8 @@ impl DojoWorld { block_rx: Receiver, world_address: Felt, provider: Arc>, + model_cache: Arc, ) -> Self { - let model_cache = Arc::new(ModelCache::new(pool.clone())); let entity_manager = Arc::new(EntityManager::default()); let event_message_manager = Arc::new(EventMessageManager::default()); let event_manager = Arc::new(EventManager::default()); @@ -624,7 +624,7 @@ impl DojoWorld { Some(ValueType::String(value)) => value, Some(ValueType::Primitive(value)) => { let primitive: Primitive = value.try_into()?; - primitive.to_sql_value()? + primitive.to_sql_value() } None => return Err(QueryError::MissingParam("value_type".into()).into()), }; @@ -1060,7 +1060,7 @@ fn build_composite_clause( Some(ValueType::String(value)) => value, Some(ValueType::Primitive(value)) => { let primitive: Primitive = value.try_into()?; - primitive.to_sql_value()? + primitive.to_sql_value() } None => return Err(QueryError::MissingParam("value_type".into()).into()), }; @@ -1368,6 +1368,7 @@ pub async fn new( block_rx: Receiver, world_address: Felt, provider: Arc>, + model_cache: Arc, ) -> Result< (SocketAddr, impl Future> + 'static), std::io::Error, @@ -1380,7 +1381,7 @@ pub async fn new( .build() .unwrap(); - let world = DojoWorld::new(pool.clone(), block_rx, world_address, provider); + let world = DojoWorld::new(pool.clone(), block_rx, world_address, provider, model_cache); let server = WorldServer::new(world) .accept_compressed(CompressionEncoding::Gzip) .send_compressed(CompressionEncoding::Gzip); diff --git a/crates/torii/grpc/src/server/tests/entities_test.rs b/crates/torii/grpc/src/server/tests/entities_test.rs index 9a0bb39f31..b5f1e83e1c 100644 --- a/crates/torii/grpc/src/server/tests/entities_test.rs +++ b/crates/torii/grpc/src/server/tests/entities_test.rs @@ -24,6 +24,7 @@ use tempfile::NamedTempFile; use tokio::sync::broadcast; use torii_core::engine::{Engine, EngineConfig, Processors}; use torii_core::executor::Executor; +use torii_core::sql::cache::ModelCache; use torii_core::sql::Sql; use torii_core::types::{Contract, ContractType}; @@ -92,10 +93,13 @@ async fn test_entities_queries(sequencer: &RunnerCtx) { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let db = Sql::new( pool.clone(), sender, - &vec![Contract { address: world_address, r#type: ContractType::WORLD }], + &[Contract { address: world_address, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -119,7 +123,8 @@ async fn test_entities_queries(sequencer: &RunnerCtx) { db.execute().await.unwrap(); let (_, receiver) = tokio::sync::mpsc::channel(1); - let grpc = DojoWorld::new(db.pool, receiver, world_address, provider.clone()); + let model_cache = Arc::new(ModelCache::new(pool.clone())); + let grpc = DojoWorld::new(db.pool, receiver, world_address, provider.clone(), model_cache); let entities = grpc .query_by_keys( diff --git a/crates/torii/libp2p/src/tests.rs b/crates/torii/libp2p/src/tests.rs index 0156093935..dc14d131fa 100644 --- a/crates/torii/libp2p/src/tests.rs +++ b/crates/torii/libp2p/src/tests.rs @@ -524,6 +524,7 @@ mod test { #[cfg(not(target_arch = "wasm32"))] #[tokio::test] async fn test_client_messaging() -> Result<(), Box> { + use std::sync::Arc; use std::time::Duration; use dojo_types::schema::{Member, Struct, Ty}; @@ -539,6 +540,7 @@ mod test { use tokio::sync::broadcast; use tokio::time::sleep; use torii_core::executor::Executor; + use torii_core::sql::cache::ModelCache; use torii_core::sql::Sql; use torii_core::types::{Contract, ContractType}; @@ -577,10 +579,13 @@ mod test { tokio::spawn(async move { executor.run().await.unwrap(); }); + + let model_cache = Arc::new(ModelCache::new(pool.clone())); let mut db = Sql::new( pool.clone(), sender, - &vec![Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + &[Contract { address: Felt::ZERO, r#type: ContractType::WORLD }], + model_cache, ) .await .unwrap(); @@ -588,7 +593,7 @@ mod test { // Register the model of our Message db.register_model( "types_test", - Ty::Struct(Struct { + &Ty::Struct(Struct { name: "Message".to_string(), children: vec![ Member { @@ -609,6 +614,7 @@ mod test { 0, 0, 0, + None, ) .await .unwrap();