diff --git a/Cargo.lock b/Cargo.lock index 8ad49068fa..8c94f84792 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4135,7 +4135,7 @@ dependencies = [ [[package]] name = "create-output-dir" version = "1.0.0" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "anyhow", "core-foundation 0.10.0", @@ -4873,7 +4873,7 @@ dependencies = [ [[package]] name = "dojo-lang" version = "1.0.12" -source = "git+https://github.com/dojoengine/dojo?rev=99bdd37c1aeb6d75028c32d4039916b97cb3a438#99bdd37c1aeb6d75028c32d4039916b97cb3a438" +source = "git+https://github.com/dojoengine/dojo?rev=d32e89a1035a8cd6abafe2e35b2c640b4d43de38#d32e89a1035a8cd6abafe2e35b2c640b4d43de38" dependencies = [ "anyhow", "cairo-lang-defs", @@ -4883,7 +4883,7 @@ dependencies = [ "cairo-lang-semantic", "cairo-lang-syntax", "cairo-lang-utils", - "dojo-types 1.0.12 (git+https://github.com/dojoengine/dojo?rev=99bdd37c1aeb6d75028c32d4039916b97cb3a438)", + "dojo-types 1.0.12 (git+https://github.com/dojoengine/dojo?rev=d32e89a1035a8cd6abafe2e35b2c640b4d43de38)", "itertools 0.12.1", "serde", "smol_str", @@ -4965,7 +4965,7 @@ dependencies = [ [[package]] name = "dojo-types" version = "1.0.12" -source = "git+https://github.com/dojoengine/dojo?rev=99bdd37c1aeb6d75028c32d4039916b97cb3a438#99bdd37c1aeb6d75028c32d4039916b97cb3a438" +source = "git+https://github.com/dojoengine/dojo?rev=d32e89a1035a8cd6abafe2e35b2c640b4d43de38#d32e89a1035a8cd6abafe2e35b2c640b4d43de38" dependencies = [ "anyhow", "cainome 0.4.11", @@ -13182,7 +13182,7 @@ dependencies = [ [[package]] name = "scarb" version = "2.9.2" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "anyhow", "async-trait", @@ -13214,7 +13214,7 @@ dependencies = [ "derive_builder", "dialoguer", "directories", - "dojo-lang 1.0.12 (git+https://github.com/dojoengine/dojo?rev=99bdd37c1aeb6d75028c32d4039916b97cb3a438)", + "dojo-lang 1.0.12 (git+https://github.com/dojoengine/dojo?rev=d32e89a1035a8cd6abafe2e35b2c640b4d43de38)", "dunce", "flate2", "fs4", @@ -13235,9 +13235,9 @@ dependencies = [ "redb", "reqwest 0.11.27", "scarb-build-metadata", - "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc)", - "scarb-proc-macro-server-types 0.1.0 (git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc)", - "scarb-stable-hash 1.0.0 (git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc)", + "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df)", + "scarb-proc-macro-server-types 0.1.0 (git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df)", + "scarb-stable-hash 1.0.0 (git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df)", "scarb-ui", "semver 1.0.23", "serde", @@ -13267,7 +13267,7 @@ dependencies = [ [[package]] name = "scarb-build-metadata" version = "2.9.2" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "cargo_metadata", ] @@ -13288,7 +13288,7 @@ dependencies = [ [[package]] name = "scarb-metadata" version = "1.13.0" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "camino", "derive_builder", @@ -13312,7 +13312,7 @@ dependencies = [ [[package]] name = "scarb-proc-macro-server-types" version = "0.1.0" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "cairo-lang-macro", "serde", @@ -13332,7 +13332,7 @@ dependencies = [ [[package]] name = "scarb-stable-hash" version = "1.0.0" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "data-encoding", "xxhash-rust", @@ -13341,14 +13341,14 @@ dependencies = [ [[package]] name = "scarb-ui" version = "0.1.5" -source = "git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc#b5ab351aa9b52dcf27a397021d84a423afd016dc" +source = "git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df#a3b3c7fae12aecd3ce1502b1a651fced6be546df" dependencies = [ "anyhow", "camino", "clap", "console", "indicatif", - "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc)", + "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df)", "serde", "serde_json", "tracing-core", @@ -14136,7 +14136,7 @@ dependencies = [ "notify", "reqwest 0.11.27", "scarb", - "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=b5ab351aa9b52dcf27a397021d84a423afd016dc)", + "scarb-metadata 1.13.0 (git+https://github.com/dojoengine/scarb?rev=a3b3c7fae12aecd3ce1502b1a651fced6be546df)", "scarb-ui", "semver 1.0.23", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7ad37a4bdb..19bb4397e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -214,9 +214,9 @@ rpassword = "7.2.0" rstest = "0.18.2" rstest_reuse = "0.6.0" salsa = "0.16.1" -scarb = { git = "https://github.com/dojoengine/scarb", rev = "b5ab351aa9b52dcf27a397021d84a423afd016dc" } -scarb-metadata = { git = "https://github.com/dojoengine/scarb", rev = "b5ab351aa9b52dcf27a397021d84a423afd016dc" } -scarb-ui = { git = "https://github.com/dojoengine/scarb", rev = "b5ab351aa9b52dcf27a397021d84a423afd016dc" } +scarb = { git = "https://github.com/dojoengine/scarb", rev = "a3b3c7fae12aecd3ce1502b1a651fced6be546df" } +scarb-metadata = { git = "https://github.com/dojoengine/scarb", rev = "a3b3c7fae12aecd3ce1502b1a651fced6be546df" } +scarb-ui = { git = "https://github.com/dojoengine/scarb", rev = "a3b3c7fae12aecd3ce1502b1a651fced6be546df" } semver = "1.0.5" serde = { version = "1.0", features = [ "derive" ] } serde_json = { version = "1.0", features = [ "arbitrary_precision" ] } diff --git a/crates/dojo/core-cairo-test/src/lib.cairo b/crates/dojo/core-cairo-test/src/lib.cairo index 27259486a3..2963fb10a6 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; + pub(crate) mod bytearray_hash; } mod helpers { diff --git a/crates/dojo/core-cairo-test/src/tests/expanded/bytearray_hash.cairo b/crates/dojo/core-cairo-test/src/tests/expanded/bytearray_hash.cairo new file mode 100644 index 0000000000..964a208ef7 --- /dev/null +++ b/crates/dojo/core-cairo-test/src/tests/expanded/bytearray_hash.cairo @@ -0,0 +1,51 @@ +use core::poseidon::poseidon_hash_span; + +#[test] +fn test_bytearray_hash() { + let bytes: ByteArray = "foo"; + let hash = bytearray_hash!("foo"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_bytearray_hash_empty() { + let bytes: ByteArray = ""; + let hash = bytearray_hash!(""); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_bytearray_hash_31() { + let bytes: ByteArray = "0123456789012345678901234567890"; + let hash = bytearray_hash!("0123456789012345678901234567890"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_bytearray_hash_long() { + let bytes: ByteArray = "0123456789012345678901234567890foo"; + let hash = bytearray_hash!("0123456789012345678901234567890foo"); + let mut array = array![]; + bytes.serialize(ref array); + let computed = poseidon_hash_span(array.span()); + assert_eq!(computed, hash); +} + +#[test] +fn test_bytearray_hash_ne() { + let bytes: ByteArray = "foo"; + let hash = bytearray_hash!("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/lib.cairo b/crates/dojo/core/src/lib.cairo index 67b2dc9290..8a8fc4a6b7 100644 --- a/crates/dojo/core/src/lib.cairo +++ b/crates/dojo/core/src/lib.cairo @@ -67,7 +67,9 @@ pub mod storage { pub mod utils { pub mod hash; - pub use hash::{bytearray_hash, selector_from_names, selector_from_namespace_and_name}; + pub use hash::{ + bytearray_hash, selector_from_hashes, selector_from_names, selector_from_namespace_and_name, + }; pub mod key; pub use key::{entity_id_from_serialized_keys, combine_key, entity_id_from_keys}; diff --git a/crates/dojo/core/src/utils/hash.cairo b/crates/dojo/core/src/utils/hash.cairo index d356c6f88c..e09a453a6e 100644 --- a/crates/dojo/core/src/utils/hash.cairo +++ b/crates/dojo/core/src/utils/hash.cairo @@ -17,3 +17,8 @@ pub fn selector_from_names(namespace: @ByteArray, name: @ByteArray) -> felt252 { pub fn selector_from_namespace_and_name(namespace_hash: felt252, name: @ByteArray) -> felt252 { poseidon_hash_span([namespace_hash, bytearray_hash(name)].span()) } + +/// Computes the selector from two hashes. +pub fn selector_from_hashes(namespace_hash: felt252, name_hash: felt252) -> felt252 { + poseidon_hash_span([namespace_hash, name_hash].span()) +} diff --git a/crates/dojo/core/src/world/storage.cairo b/crates/dojo/core/src/world/storage.cairo index b813a6325a..25cb2ebec8 100644 --- a/crates/dojo/core/src/world/storage.cairo +++ b/crates/dojo/core/src/world/storage.cairo @@ -32,15 +32,30 @@ 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, dojo::utils::selector_from_hashes(*self.namespace_hash, contract_name_hash), + ) + } + + fn dns_from_selector( + self: @WorldStorage, selector: felt252, + ) -> Option<(ContractAddress, ClassHash)> { + match (*self.dispatcher).resource(selector) { Resource::Contract(( contract_address, _, )) => { 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..18d2308b28 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_ns_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..69ce6b4360 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::{BytearrayHashMacro, SelectorFromTagMacro}; // #[cfg(test)] // #[path = "plugin_test.rs"] @@ -25,7 +25,10 @@ pub struct BuiltinDojoPlugin; 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::() + .add_inline_macro_plugin::(); suite } diff --git a/crates/dojo/lang/src/inline_macros/bytearray_hash.rs b/crates/dojo/lang/src/inline_macros/bytearray_hash.rs new file mode 100644 index 0000000000..46435f6011 --- /dev/null +++ b/crates/dojo/lang/src/inline_macros/bytearray_hash.rs @@ -0,0 +1,63 @@ +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 BytearrayHashMacro; + +impl NamedPlugin for BytearrayHashMacro { + const NAME: &'static str = "bytearray_hash"; +} + +impl InlineMacroExprPlugin for BytearrayHashMacro { + 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 \"bytearray_hash!(\"long string\")\"" + .to_string(), + severity: Severity::Error, + }], + }; + } + + let bytearray = &args[0].as_syntax_node().get_text(db).replace('\"', ""); + + let bytearray_hash = naming::compute_bytearray_hash(bytearray); + + let mut builder = PatchBuilder::new(db, syntax); + builder.add_str(&format!("{:#64x}", bytearray_hash)); + + let (code, code_mappings) = builder.build(); + + InlinePluginResult { + code: Some(PluginGeneratedFile { + name: "bytearray_hash_macro".into(), + content: code, + code_mappings, + diagnostics_note: None, + aux_data: None, + }), + diagnostics: vec![], + } + } +} diff --git a/crates/dojo/lang/src/inline_macros/mod.rs b/crates/dojo/lang/src/inline_macros/mod.rs index 1f28612be1..e65f3659d6 100644 --- a/crates/dojo/lang/src/inline_macros/mod.rs +++ b/crates/dojo/lang/src/inline_macros/mod.rs @@ -8,6 +8,7 @@ use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::{ast, Terminal, TypedStablePtr, TypedSyntaxNode}; use smol_str::SmolStr; +pub mod bytearray_hash; pub mod delete; pub mod emit; pub mod get; @@ -17,6 +18,7 @@ pub mod set; pub mod spawn_test_world; pub mod utils; +pub use bytearray_hash::BytearrayHashMacro; pub use delete::DeleteMacro; pub use emit::EmitMacro; pub use get::GetMacro; diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index 352a4ae18d..4683eddb37 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -215,13 +215,19 @@ pub mod actions { fn world_default(self: @ContractState) -> dojo::world::WorldStorage { self.world(@"ns") } + + /// A gas optimized version of `world_default`, where hash is computed at compile time. + /// Can make a difference if switching between namespaces is frequent. + fn world_default_ns_hash(self: @ContractState) -> dojo::world::WorldStorage { + self.world_ns_hash(bytearray_hash!("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, @@ -323,6 +329,18 @@ mod tests { 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 = bytearray_hash!("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); + } + #[test] #[available_gas(30000000)] #[cfg(feature: 'dungeon')] diff --git a/spawn-and-move-db.tar.gz b/spawn-and-move-db.tar.gz index 20dcfbeb51..b8309c9feb 100644 Binary files a/spawn-and-move-db.tar.gz and b/spawn-and-move-db.tar.gz differ diff --git a/types-test-db.tar.gz b/types-test-db.tar.gz index c5c1b3f889..afd4fc8309 100644 Binary files a/types-test-db.tar.gz and b/types-test-db.tar.gz differ