diff --git a/Cargo.toml b/Cargo.toml index a54a7d57e2..295518ea8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,13 +119,13 @@ katana-trie = { path = "crates/katana/trie" } # torii torii-cli = { path = "crates/torii/cli" } torii-client = { path = "crates/torii/client" } -torii-indexer = { path = "crates/torii/indexer" } -torii-sqlite = { path = "crates/torii/sqlite" } torii-graphql = { path = "crates/torii/graphql" } torii-grpc = { path = "crates/torii/grpc" } +torii-indexer = { path = "crates/torii/indexer" } torii-relay = { path = "crates/torii/libp2p" } -torii-server = { path = "crates/torii/server" } torii-runner = { path = "crates/torii/runner" } +torii-server = { path = "crates/torii/server" } +torii-sqlite = { path = "crates/torii/sqlite" } torii-typed-data = { path = "crates/torii/typed-data" } # saya diff --git a/crates/dojo/core-cairo-test/Scarb.lock b/crates/dojo/core-cairo-test/Scarb.lock index 6d9a3fcab9..b696256903 100644 --- a/crates/dojo/core-cairo-test/Scarb.lock +++ b/crates/dojo/core-cairo-test/Scarb.lock @@ -3,7 +3,7 @@ version = 1 [[package]] name = "dojo" -version = "1.0.11" +version = "1.0.12" dependencies = [ "dojo_plugin", ] diff --git a/crates/dojo/core-cairo-test/src/lib.cairo b/crates/dojo/core-cairo-test/src/lib.cairo index 7517f85da7..94d8532bf4 100644 --- a/crates/dojo/core-cairo-test/src/lib.cairo +++ b/crates/dojo/core-cairo-test/src/lib.cairo @@ -38,6 +38,7 @@ mod tests { mod expanded { pub(crate) mod selector_attack; + mod poseidon_hash_string; } mod helpers { diff --git a/crates/dojo/core-cairo-test/src/tests/expanded/poseidon_hash_string.cairo b/crates/dojo/core-cairo-test/src/tests/expanded/poseidon_hash_string.cairo new file mode 100644 index 0000000000..3f1f861c7d --- /dev/null +++ b/crates/dojo/core-cairo-test/src/tests/expanded/poseidon_hash_string.cairo @@ -0,0 +1,52 @@ +use core::poseidon::poseidon_hash_span; + + +#[test] +fn test_poseidon_hash_string() { + let bytes: ByteArray = "foo"; + let hash = poseidon_hash_string!("foo"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} +#[test] +fn test_poseidon_hash_string_empty() { + let bytes: ByteArray = ""; + let hash = poseidon_hash_string!(""); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_poseidon_hash_string_31() { + let bytes: ByteArray = "0123456789012345678901234567890"; + let hash = poseidon_hash_string!("0123456789012345678901234567890"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_poseidon_hash_string_long() { + let bytes: ByteArray = "0123456789012345678901234567890foo"; + let hash = poseidon_hash_string!("0123456789012345678901234567890foo"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_poseidon_hash_string_ne() { + let bytes: ByteArray = "foo"; + let hash = poseidon_hash_string!("bar"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_ne!(computed, hash); +} + diff --git a/crates/dojo/core/src/world/storage.cairo b/crates/dojo/core/src/world/storage.cairo index 6fdc9e9160..b2b2f70111 100644 --- a/crates/dojo/core/src/world/storage.cairo +++ b/crates/dojo/core/src/world/storage.cairo @@ -1,6 +1,7 @@ //! A simple storage abstraction for the world's storage. use core::panic_with_felt252; +use core::poseidon::poseidon_hash_span; use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, Resource}; use dojo::model::{Model, ModelIndex, ModelValueKey, ModelValue, ModelStorage, ModelPtr}; use dojo::event::{Event, EventStorage}; @@ -32,18 +33,34 @@ pub impl WorldStorageInternalImpl of WorldStorageTrait { WorldStorage { dispatcher: world, namespace_hash } } + fn new_from_hash(world: IWorldDispatcher, namespace_hash: felt252) -> WorldStorage { + WorldStorage { dispatcher: world, namespace_hash } + } + fn set_namespace(ref self: WorldStorage, namespace: @ByteArray) { self.namespace_hash = dojo::utils::bytearray_hash(namespace); } + fn dns(self: @WorldStorage, contract_name: @ByteArray) -> Option<(ContractAddress, ClassHash)> { - match (*self.dispatcher) - .resource( - dojo::utils::selector_from_namespace_and_name(*self.namespace_hash, contract_name) - ) { + Self::dns_from_hash(self, dojo::utils::bytearray_hash(contract_name)) + } + + fn dns_from_hash( + self: @WorldStorage, contract_name_hash: felt252 + ) -> Option<(ContractAddress, ClassHash)> { + Self::dns_from_selector( + self, poseidon_hash_span([*self.namespace_hash, contract_name_hash].span()) + ) + } + + fn dns_from_selector( + self: @WorldStorage, selector: felt252 + ) -> Option<(ContractAddress, ClassHash)> { + match (*self.dispatcher).resource(selector) { Resource::Contract(( contract_address, class_hash - )) => Option::Some((contract_address, class_hash.try_into().unwrap())), + )) => { Option::Some((contract_address, class_hash.try_into().unwrap())) }, _ => Option::None } } diff --git a/crates/dojo/lang/src/attribute_macros/patches/contract.patch.cairo b/crates/dojo/lang/src/attribute_macros/patches/contract.patch.cairo index 46ee7353b1..0d55a9c9cd 100644 --- a/crates/dojo/lang/src/attribute_macros/patches/contract.patch.cairo +++ b/crates/dojo/lang/src/attribute_macros/patches/contract.patch.cairo @@ -29,6 +29,10 @@ pub mod $name$ { fn world(self: @ContractState, namespace: @ByteArray) -> dojo::world::storage::WorldStorage { dojo::world::WorldStorageTrait::new(self.world_provider.world_dispatcher(), namespace) } + + fn world_from_hash(self: @ContractState, namespace_hash: felt252) -> dojo::world::storage::WorldStorage { + dojo::world::WorldStorageTrait::new_from_hash(self.world_provider.world_dispatcher(), namespace_hash) + } } $body$ diff --git a/crates/dojo/lang/src/cairo_plugin.rs b/crates/dojo/lang/src/cairo_plugin.rs index a13cc76533..fcc4b459c9 100644 --- a/crates/dojo/lang/src/cairo_plugin.rs +++ b/crates/dojo/lang/src/cairo_plugin.rs @@ -12,7 +12,7 @@ use super::attribute_macros::{ DojoContract, DojoEvent, DojoModel, DOJO_CONTRACT_ATTR, DOJO_EVENT_ATTR, DOJO_MODEL_ATTR, }; use super::derive_macros::{dojo_derive_all, DOJO_INTROSPECT_DERIVE, DOJO_PACKED_DERIVE}; -use super::inline_macros::SelectorFromTagMacro; +use super::inline_macros::{PoseidonHashStringMacro, SelectorFromTagMacro}; // #[cfg(test)] // #[path = "plugin_test.rs"] @@ -26,6 +26,7 @@ pub fn dojo_plugin_suite() -> PluginSuite { let mut suite = PluginSuite::default(); suite.add_plugin::().add_inline_macro_plugin::(); + suite.add_plugin::().add_inline_macro_plugin::(); suite } diff --git a/crates/dojo/lang/src/inline_macros/mod.rs b/crates/dojo/lang/src/inline_macros/mod.rs index 1f28612be1..3d42efd193 100644 --- a/crates/dojo/lang/src/inline_macros/mod.rs +++ b/crates/dojo/lang/src/inline_macros/mod.rs @@ -12,6 +12,7 @@ pub mod delete; pub mod emit; pub mod get; pub mod get_models_test_class_hashes; +pub mod poseidon_hash_string; pub mod selector_from_tag; pub mod set; pub mod spawn_test_world; @@ -21,6 +22,7 @@ pub use delete::DeleteMacro; pub use emit::EmitMacro; pub use get::GetMacro; pub use get_models_test_class_hashes::GetModelsTestClassHashes; +pub use poseidon_hash_string::PoseidonHashStringMacro; pub use selector_from_tag::SelectorFromTagMacro; pub use set::SetMacro; pub use spawn_test_world::SpawnTestWorld; diff --git a/crates/dojo/lang/src/inline_macros/poseidon_hash_string.rs b/crates/dojo/lang/src/inline_macros/poseidon_hash_string.rs new file mode 100644 index 0000000000..92421ee367 --- /dev/null +++ b/crates/dojo/lang/src/inline_macros/poseidon_hash_string.rs @@ -0,0 +1,62 @@ +use cairo_lang_defs::patcher::PatchBuilder; +use cairo_lang_defs::plugin::{ + InlineMacroExprPlugin, InlinePluginResult, MacroPluginMetadata, NamedPlugin, PluginDiagnostic, + PluginGeneratedFile, +}; +use cairo_lang_defs::plugin_utils::unsupported_bracket_diagnostic; +use cairo_lang_diagnostics::Severity; +use cairo_lang_syntax::node::{ast, TypedStablePtr, TypedSyntaxNode}; +use dojo_types::naming; + +#[derive(Debug, Default)] +pub struct PoseidonHashStringMacro; + +impl NamedPlugin for PoseidonHashStringMacro { + const NAME: &'static str = "poseidon_hash_string"; +} + +impl InlineMacroExprPlugin for PoseidonHashStringMacro { + fn generate_code( + &self, + db: &dyn cairo_lang_syntax::node::db::SyntaxGroup, + syntax: &ast::ExprInlineMacro, + _metadata: &MacroPluginMetadata<'_>, + ) -> InlinePluginResult { + let ast::WrappedArgList::ParenthesizedArgList(arg_list) = syntax.arguments(db) else { + return unsupported_bracket_diagnostic(db, syntax); + }; + + let args = arg_list.arguments(db).elements(db); + + if args.len() != 1 { + return InlinePluginResult { + code: None, + diagnostics: vec![PluginDiagnostic { + stable_ptr: syntax.stable_ptr().untyped(), + message: "Invalid arguments. Expected \"poseidon_hash_string!(\"tag\")\"" + .to_string(), + severity: Severity::Error, + }], + }; + } + + let tag = &args[0].as_syntax_node().get_text(db).replace('\"', ""); + + let selector = naming::compute_bytearray_hash(tag); + + let mut builder = PatchBuilder::new(db, syntax); + builder.add_str(&format!("{:#64x}", selector)); + + let (code, code_mappings) = builder.build(); + + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "poseidon_hash_string_macro".into(), + content: code, + code_mappings, + aux_data: None, + }), + diagnostics: vec![], + } + } +} diff --git a/crates/dojo/lang/src/semantics/tests.rs b/crates/dojo/lang/src/semantics/tests.rs index e45bcf2040..32dea6c580 100644 --- a/crates/dojo/lang/src/semantics/tests.rs +++ b/crates/dojo/lang/src/semantics/tests.rs @@ -22,6 +22,8 @@ test_file_test!( get_models_test_class_hashes: "get_models_test_class_hashes", spawn_test_world: "spawn_test_world", + + poseidon_hash_string: "poseidon_hash_string", }, test_semantics ); diff --git a/examples/spawn-and-move/Scarb.lock b/examples/spawn-and-move/Scarb.lock index 019c180bc8..ac0f63fee5 100644 --- a/examples/spawn-and-move/Scarb.lock +++ b/examples/spawn-and-move/Scarb.lock @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "dojo_examples" -version = "1.0.11" +version = "1.0.12" dependencies = [ "armory", "bestiary", diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index cd116a2255..b959e5b06a 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -215,13 +215,18 @@ pub mod actions { fn world_default(self: @ContractState) -> dojo::world::WorldStorage { self.world(@"ns") } + + /// A gas optimized version of `world_default`, where hash are computed at compile time. + fn world_default_from_hash(self: @ContractState) -> dojo::world::WorldStorage { + self.world_from_hash(poseidon_hash_string!("ns")) + } } } #[cfg(test)] mod tests { use dojo::model::{ModelStorage, ModelValueStorage, ModelStorageTest}; - use dojo::world::WorldStorageTrait; + use dojo::world::{WorldStorageTrait}; use dojo_cairo_test::{ spawn_test_world, NamespaceDef, TestResource, ContractDefTrait, ContractDef, WorldStorageTestTrait @@ -318,4 +323,16 @@ mod tests { assert(new_position.vec.x == initial_position.vec.x + 1, 'position x is wrong'); assert(new_position.vec.y == initial_position.vec.y, 'position y is wrong'); } + + #[test] + #[available_gas(30000000)] + fn test_world_from_hash() { + let ndef = namespace_def(); + let mut world = spawn_test_world([ndef].span()); + world.sync_perms_and_inits(contract_defs()); + let hash: felt252 = poseidon_hash_string!("ns"); + let storage = dojo::world::WorldStorageTrait::new_from_hash(world.dispatcher, hash); + assert_eq!(storage.namespace_hash, world.namespace_hash); + assert_eq!(storage.dispatcher.contract_address, world.dispatcher.contract_address); + } }