Skip to content

Commit

Permalink
feat(dojo-lang): add bytearray_hash macro (#2946)
Browse files Browse the repository at this point in the history
* feat(namespace): add poseidon_hash_string macro and related tests

* fix(world-storage): retrieve class hash using syscall for contract resources and fmting

* fix: avoid double declaration of plugin + renamings

* chore: bump scarb

* tests: use new name in tests

* tests: regenerate test db

* tests: fix rename of macro

---------

Co-authored-by: glihm <[email protected]>
  • Loading branch information
bengineer42 and glihm authored Jan 24, 2025
1 parent 94e6e16 commit 8c484fd
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 27 deletions.
32 changes: 16 additions & 16 deletions Cargo.lock

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

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ] }
Expand Down
1 change: 1 addition & 0 deletions crates/dojo/core-cairo-test/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ mod tests {

mod expanded {
pub(crate) mod selector_attack;
pub(crate) mod bytearray_hash;
}

mod helpers {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
4 changes: 3 additions & 1 deletion crates/dojo/core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
5 changes: 5 additions & 0 deletions crates/dojo/core/src/utils/hash.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
23 changes: 19 additions & 4 deletions crates/dojo/core/src/world/storage.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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, _,
)) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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$
Expand Down
7 changes: 5 additions & 2 deletions crates/dojo/lang/src/cairo_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -25,7 +25,10 @@ pub struct BuiltinDojoPlugin;
pub fn dojo_plugin_suite() -> PluginSuite {
let mut suite = PluginSuite::default();

suite.add_plugin::<BuiltinDojoPlugin>().add_inline_macro_plugin::<SelectorFromTagMacro>();
suite
.add_plugin::<BuiltinDojoPlugin>()
.add_inline_macro_plugin::<SelectorFromTagMacro>()
.add_inline_macro_plugin::<BytearrayHashMacro>();

suite
}
Expand Down
63 changes: 63 additions & 0 deletions crates/dojo/lang/src/inline_macros/bytearray_hash.rs
Original file line number Diff line number Diff line change
@@ -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![],
}
}
}
2 changes: 2 additions & 0 deletions crates/dojo/lang/src/inline_macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 8c484fd

Please sign in to comment.