From b8b26152d521b600c4afe13677c0b1151638b72a Mon Sep 17 00:00:00 2001 From: "remy.baranx@gmail.com" Date: Sun, 7 Jul 2024 17:38:54 +0200 Subject: [PATCH 1/4] Handle get()/set() get_field_name()/set_field_name() functions --- crates/dojo-core/src/lib.cairo | 2 + crates/dojo-core/src/model.cairo | 31 +- crates/dojo-core/src/model_test.cairo | 198 +++++++ crates/dojo-core/src/resource_metadata.cairo | 65 ++- crates/dojo-core/src/utils.cairo | 50 ++ crates/dojo-core/src/world.cairo | 307 +++++++---- crates/dojo-core/src/world_test.cairo | 170 +++--- crates/dojo-lang/src/inline_macros/delete.rs | 9 +- crates/dojo-lang/src/inline_macros/get.rs | 4 +- crates/dojo-lang/src/inline_macros/set.rs | 8 +- crates/dojo-lang/src/introspect/layout.rs | 2 +- .../manifests/dev/base/abis/dojo-world.json | 67 ++- .../manifests/dev/base/dojo-world.toml | 4 +- crates/dojo-lang/src/model.rs | 493 ++++++++++++++++-- crates/dojo-lang/src/semantics/test_data/get | 22 +- crates/dojo-lang/src/semantics/test_data/set | 92 +--- crates/dojo-world/src/contracts/abi/world.rs | 67 ++- crates/dojo-world/src/contracts/model.rs | 9 +- crates/dojo-world/src/contracts/naming.rs | 4 +- crates/sozo/ops/src/events.rs | 2 +- crates/torii/types-test/src/contracts.cairo | 2 +- examples/spawn-and-move/Scarb.toml | 2 +- .../dojo_examples-actions-40b6994c.json | 24 + .../manifests/dev/base/abis/dojo-world.json | 67 ++- .../dojo_examples-actions-40b6994c.toml | 4 +- .../dojo_examples-mock_token-31599eb2.toml | 4 +- .../manifests/dev/base/dojo-world.toml | 4 +- .../dojo_examples-actions-40b6994c.json | 24 + .../dev/deployment/abis/dojo-world.json | 67 ++- .../manifests/dev/deployment/manifest.json | 115 +++- .../manifests/dev/deployment/manifest.toml | 24 +- .../dojo_examples-actions-40b6994c.json | 24 + .../release/base/abis/dojo-world.json | 67 ++- .../dojo_examples-actions-40b6994c.toml | 4 +- .../dojo_examples-mock_token-31599eb2.toml | 4 +- .../manifests/release/base/dojo-world.toml | 4 +- examples/spawn-and-move/src/actions.cairo | 62 ++- examples/spawn-and-move/src/mock_token.cairo | 1 - 38 files changed, 1647 insertions(+), 462 deletions(-) create mode 100644 crates/dojo-core/src/model_test.cairo diff --git a/crates/dojo-core/src/lib.cairo b/crates/dojo-core/src/lib.cairo index 2aed7f15fe..627cf21739 100644 --- a/crates/dojo-core/src/lib.cairo +++ b/crates/dojo-core/src/lib.cairo @@ -7,6 +7,8 @@ mod database; mod database_test; mod interfaces; mod model; +#[cfg(test)] +mod model_test; mod contract; mod packing; #[cfg(test)] diff --git a/crates/dojo-core/src/model.cairo b/crates/dojo-core/src/model.cairo index d26d43f54a..3ebbd5e0bc 100644 --- a/crates/dojo-core/src/model.cairo +++ b/crates/dojo-core/src/model.cairo @@ -1,10 +1,32 @@ -use dojo::world::IWorldDispatcher; +use dojo::world::{IWorldDispatcher, ModelIndex}; use starknet::SyscallResult; +/// Trait that is implemented at Cairo level for each struct that is a model. +trait ModelEntity { + fn id(self: @T) -> felt252; + fn values(self: @T) -> Span; + fn from_values(entity_id: felt252, values: Span) -> T; + fn get(world: IWorldDispatcher, entity_id: felt252) -> T; + fn update(self: @T, world: IWorldDispatcher); + fn delete(self: @T, world: IWorldDispatcher); + fn get_member( + world: IWorldDispatcher, entity_id: felt252, member_id: felt252, + ) -> Span; + fn set_member(self: @T, world: IWorldDispatcher, member_id: felt252, values: Span,); +} + trait Model { - fn entity( - world: IWorldDispatcher, keys: Span, layout: dojo::database::introspect::Layout - ) -> T; + fn get(world: IWorldDispatcher, keys: Span) -> T; + // Note: `get` is implemented with a generated trait because it takes + // the list of model keys as separated parameters. + fn set(self: @T, world: IWorldDispatcher); + fn delete(self: @T, world: IWorldDispatcher); + + fn get_member( + world: IWorldDispatcher, keys: Span, member_id: felt252, + ) -> Span; + + fn set_member(self: @T, world: IWorldDispatcher, member_id: felt252, values: Span,); /// Returns the name of the model as it was written in Cairo code. fn name() -> ByteArray; @@ -25,6 +47,7 @@ trait Model { fn name_hash() -> felt252; fn namespace_hash() -> felt252; + fn entity_id(self: @T) -> felt252; fn keys(self: @T) -> Span; fn values(self: @T) -> Span; fn layout() -> dojo::database::introspect::Layout; diff --git a/crates/dojo-core/src/model_test.cairo b/crates/dojo-core/src/model_test.cairo new file mode 100644 index 0000000000..68a17d604d --- /dev/null +++ b/crates/dojo-core/src/model_test.cairo @@ -0,0 +1,198 @@ +use dojo::test_utils::{spawn_test_world}; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; + +// Utils +fn deploy_world() -> IWorldDispatcher { + spawn_test_world("dojo", array![]) +} + +#[derive(Copy, Drop, Serde)] +#[dojo::model] +struct Foo { + #[key] + k1: u8, + #[key] + k2: felt252, + v1: u128, + v2: u32 +} + +#[test] +fn test_id() { + let mvalues = FooEntity { __id: 1, v1: 3, v2: 4 }; + assert!(mvalues.id() == 1); +} + +#[test] +fn test_values() { + let mvalues = FooEntity { __id: 1, v1: 3, v2: 4 }; + let expected_values = array![3, 4].span(); + + let values = dojo::model::ModelEntity::::values(@mvalues); + assert!(expected_values == values); +} + +#[test] +fn test_from_values() { + let values = array![3, 4].span(); + + let model_entity = dojo::model::ModelEntity::::from_values(1, values); + assert!(model_entity.__id == 1 && model_entity.v1 == 3 && model_entity.v2 == 4); +} + +#[test] +#[should_panic(expected: "ModelEntity `FooEntity`: deserialization failed.")] +fn test_from_values_bad_data() { + let values = array![3].span(); + let _ = dojo::model::ModelEntity::::from_values(1, values); +} + +#[test] +fn test_get_and_update_entity() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + + let entity_id = foo.entity_id(); + let mut entity = FooEntityTrait::get(world, entity_id); + assert!(entity.__id == entity_id && entity.v1 == entity.v1 && entity.v2 == entity.v2); + + entity.v1 = 12; + entity.v2 = 18; + + entity.update(world); + + let read_values = FooEntityTrait::get(world, entity_id); + assert!(read_values.v1 == entity.v1 && read_values.v2 == entity.v2); +} + +#[test] +fn test_delete_entity() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + + let entity_id = foo.entity_id(); + let mut entity = FooEntityTrait::get(world, entity_id); + entity.delete(world); + + let read_values = FooEntityTrait::get(world, entity_id); + assert!(read_values.v1 == 0 && read_values.v2 == 0); +} + +#[test] +fn test_get_and_set_member_from_entity() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + + let v1_raw_value: Span = dojo::model::ModelEntity::< + FooEntity + >::get_member(world, foo.entity_id(), selector!("v1")); + + assert!(v1_raw_value.len() == 1); + assert!(*v1_raw_value.at(0) == 3); + + let entity = FooEntityTrait::get(world, foo.entity_id()); + entity.set_member(world, selector!("v1"), array![42].span()); + + let entity = FooEntityTrait::get(world, foo.entity_id()); + assert!(entity.v1 == 42); +} + +#[test] +fn test_get_and_set_field_name() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + + let v1 = FooEntityTrait::get_v1(world, foo.entity_id()); + assert!(foo.v1 == v1); + + let entity = FooEntityTrait::get(world, foo.entity_id()); + entity.set_v1(world, 42); + + let v1 = FooEntityTrait::get_v1(world, foo.entity_id()); + assert!(v1 == 42); +} + +#[test] +fn test_get_and_set_from_model() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + + let read_entity = FooTrait::get(world, foo.k1, foo.k2); + + assert!( + foo.k1 == read_entity.k1 + && foo.k2 == read_entity.k2 + && foo.v1 == read_entity.v1 + && foo.v2 == read_entity.v2 + ); +} + +#[test] +fn test_delete_from_model() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + foo.delete(world); + + let read_entity = FooTrait::get(world, foo.k1, foo.k2); + assert!( + read_entity.k1 == foo.k1 + && read_entity.k2 == foo.k2 + && read_entity.v1 == 0 + && read_entity.v2 == 0 + ); +} + +#[test] +fn test_get_and_set_member_from_model() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + let keys = array![foo.k1.into(), foo.k2.into()].span(); + foo.set(world); + + let v1_raw_value = dojo::model::Model::::get_member(world, keys, selector!("v1")); + + assert!(v1_raw_value.len() == 1); + assert!(*v1_raw_value.at(0) == 3); + + foo.set_member(world, selector!("v1"), array![42].span()); + let foo = FooTrait::get(world, foo.k1, foo.k2); + assert!(foo.v1 == 42); +} + +#[test] +fn test_get_and_set_field_name_from_model() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + + let foo = Foo { k1: 1, k2: 2, v1: 3, v2: 4 }; + foo.set(world); + + let v1 = FooTrait::get_v1(world, foo.k1, foo.k2); + assert!(v1 == 3); + + foo.set_v1(world, 42); + + let v1 = FooTrait::get_v1(world, foo.k1, foo.k2); + assert!(v1 == 42); +} + diff --git a/crates/dojo-core/src/resource_metadata.cairo b/crates/dojo-core/src/resource_metadata.cairo index 601f05f391..dde35752e1 100644 --- a/crates/dojo-core/src/resource_metadata.cairo +++ b/crates/dojo-core/src/resource_metadata.cairo @@ -3,8 +3,9 @@ //! Manually expand to ensure that dojo-core //! does not depend on dojo plugin to be built. //! -use dojo::world::{IWorldDispatcherTrait}; +use dojo::world::{IWorldDispatcherTrait, ModelIndex}; use dojo::model::Model; +use dojo::utils; fn initial_address() -> starknet::ContractAddress { starknet::contract_address_const::<0>() @@ -24,12 +25,8 @@ struct ResourceMetadata { } impl ResourceMetadataModel of dojo::model::Model { - fn entity( - world: dojo::world::IWorldDispatcher, - keys: Span, - layout: dojo::database::introspect::Layout - ) -> ResourceMetadata { - let values = world.entity(Self::selector(), keys, layout); + fn get(world: dojo::world::IWorldDispatcher, keys: Span) -> ResourceMetadata { + let values = world.entity(Self::selector(), ModelIndex::Keys(keys), Self::layout()); let mut serialized = core::array::ArrayTrait::new(); core::array::serialize_array_helper(keys, ref serialized); core::array::serialize_array_helper(values, ref serialized); @@ -45,6 +42,51 @@ impl ResourceMetadataModel of dojo::model::Model { core::option::OptionTrait::::unwrap(entity) } + fn set(self: @ResourceMetadata, world: dojo::world::IWorldDispatcher,) { + dojo::world::IWorldDispatcherTrait::set_entity( + world, Self::selector(), ModelIndex::Keys(self.keys()), self.values(), Self::layout() + ); + } + + fn delete(self: @ResourceMetadata, world: dojo::world::IWorldDispatcher,) { + world.delete_entity(Self::selector(), ModelIndex::Keys(self.keys()), Self::layout()); + } + + fn get_member( + world: dojo::world::IWorldDispatcher, keys: Span, member_id: felt252 + ) -> Span { + match utils::find_model_field_layout(Self::layout(), member_id) { + Option::Some(field_layout) => { + let entity_id = utils::entity_id_from_keys(keys); + world + .entity( + Self::selector(), ModelIndex::MemberId((entity_id, member_id)), field_layout + ) + }, + Option::None => panic_with_felt252('bad member id') + } + } + + fn set_member( + self: @ResourceMetadata, + world: dojo::world::IWorldDispatcher, + member_id: felt252, + values: Span + ) { + match utils::find_model_field_layout(Self::layout(), member_id) { + Option::Some(field_layout) => { + world + .set_entity( + Self::selector(), + ModelIndex::MemberId((self.entity_id(), member_id)), + values, + field_layout + ) + }, + Option::None => panic_with_felt252('bad member id') + } + } + #[inline(always)] fn name() -> ByteArray { "ResourceMetadata" @@ -74,11 +116,16 @@ impl ResourceMetadataModel of dojo::model::Model { } fn name_hash() -> felt252 { - dojo::utils::hash(@Self::name()) + utils::hash(@Self::name()) } fn namespace_hash() -> felt252 { - dojo::utils::hash(@Self::namespace()) + utils::hash(@Self::namespace()) + } + + #[inline(always)] + fn entity_id(self: @ResourceMetadata) -> felt252 { + poseidon::poseidon_hash_span(self.keys()) } #[inline(always)] diff --git a/crates/dojo-core/src/utils.cairo b/crates/dojo-core/src/utils.cairo index 92654ce538..860bbe5759 100644 --- a/crates/dojo-core/src/utils.cairo +++ b/crates/dojo-core/src/utils.cairo @@ -4,3 +4,53 @@ fn hash(data: @ByteArray) -> felt252 { Serde::serialize(data, ref serialized); poseidon::poseidon_hash_span(serialized.span()) } + +/// Computes the entity id from the keys. +/// +/// # Arguments +/// +/// * `keys` - The keys of the entity. +/// +/// # Returns +/// +/// The entity id. +fn entity_id_from_keys(keys: Span) -> felt252 { + poseidon::poseidon_hash_span(keys) +} + +/// Find the layout of a model field based on its selector. +/// +/// # Arguments +/// +/// * `model_layout` - The full model layout (must be a Layout::Struct). +/// * `member_selector` - The model field selector. +/// +/// # Returns +/// Some(Layout) if the field layout has been found, None otherwise. +fn find_model_field_layout( + model_layout: dojo::database::introspect::Layout, member_selector: felt252 +) -> Option { + match model_layout { + dojo::database::introspect::Layout::Struct(struct_layout) => { + let mut i = 0; + let layout = loop { + if i >= struct_layout.len() { + break Option::None; + } + + let field_layout = *struct_layout.at(i); + if field_layout.selector == member_selector { + break Option::Some(field_layout.layout); + } + i += 1; + }; + + layout + }, + _ => { + // should never happen as model layouts are always struct layouts. + panic_with_felt252('Unexpected model layout'); + Option::None + } + } +} diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index ef51eb0a4a..6882af8e4a 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -3,6 +3,14 @@ use traits::{Into, TryInto}; use option::OptionTrait; use dojo::resource_metadata::ResourceMetadata; +#[derive(Copy, Drop, Serde, Debug, PartialEq)] +enum ModelIndex { + Keys: Span, + Id: felt252, + // (entity_id, member_id) + MemberId: (felt252, felt252) +} + #[starknet::interface] trait IWorld { fn metadata(self: @T, resource_id: felt252) -> ResourceMetadata; @@ -17,19 +25,27 @@ trait IWorld { fn upgrade_contract(ref self: T, selector: felt252, class_hash: ClassHash) -> ClassHash; fn uuid(ref self: T) -> usize; fn emit(self: @T, keys: Array, values: Span); + fn entity( - self: @T, model: felt252, keys: Span, layout: dojo::database::introspect::Layout + self: @T, + model_selector: felt252, + index: ModelIndex, + layout: dojo::database::introspect::Layout ) -> Span; fn set_entity( ref self: T, - model: felt252, - keys: Span, + model_selector: felt252, + index: ModelIndex, values: Span, layout: dojo::database::introspect::Layout ); fn delete_entity( - ref self: T, model: felt252, keys: Span, layout: dojo::database::introspect::Layout + ref self: T, + model_selector: felt252, + index: ModelIndex, + layout: dojo::database::introspect::Layout ); + fn base(self: @T) -> ClassHash; /// In Dojo, there are 2 levels of authorization: `owner` and `writer`. @@ -74,6 +90,7 @@ mod Errors { const OWNER_ONLY_UPGRADE: felt252 = 'only owner can upgrade'; const OWNER_ONLY_UPDATE: felt252 = 'only owner can update'; const NAMESPACE_ALREADY_REGISTERED: felt252 = 'namespace already registered'; + const DELETE_ENTITY_MEMBER: felt252 = 'cannot delete entity member'; const UNEXPECTED_ERROR: felt252 = 'unexpected error'; } @@ -110,8 +127,9 @@ mod world { use dojo::world::{IWorldDispatcher, IWorld, IUpgradeableWorld}; use dojo::resource_metadata; use dojo::resource_metadata::ResourceMetadata; + use dojo::utils::entity_id_from_keys; - use super::Errors; + use super::{Errors, ModelIndex}; const WORLD: felt252 = 0; @@ -137,6 +155,7 @@ mod world { NamespaceRegistered: NamespaceRegistered, ModelRegistered: ModelRegistered, StoreSetRecord: StoreSetRecord, + StoreUpdateRecord: StoreUpdateRecord, StoreDelRecord: StoreDelRecord, WriterUpdated: WriterUpdated, OwnerUpdated: OwnerUpdated, @@ -204,10 +223,17 @@ mod world { values: Span, } + #[derive(Drop, starknet::Event)] + struct StoreUpdateRecord { + table: felt252, + entity_id: felt252, + values: Span, + } + #[derive(Drop, starknet::Event)] struct StoreDelRecord { table: felt252, - keys: Span, + entity_id: felt252, } #[derive(Drop, starknet::Event)] @@ -291,14 +317,15 @@ mod world { /// /// `resource_id` - The resource id. fn metadata(self: @ContractState, resource_id: felt252) -> ResourceMetadata { + let mut model = array![resource_id]; + let entity_id = entity_id_from_keys(model.span()); let mut data = self - ._read_model_data( + ._read_model_entity( dojo::model::Model::::selector(), - array![resource_id].span(), + entity_id, Model::::layout() ); - let mut model = array![resource_id]; core::array::serialize_array_helper(data, ref model); let mut model_span = model.span(); @@ -318,11 +345,11 @@ mod world { ); let model = Model::::selector(); - let keys = Model::::keys(@metadata); + let entity_id = Model::::entity_id(@metadata); let values = Model::::values(@metadata); let layout = Model::::layout(); - self._write_model_data(model, keys, values, layout); + self._write_model_entity(model, entity_id, values, layout); EventEmitter::emit( ref self, @@ -766,73 +793,115 @@ mod world { emit_event_syscall(keys.span(), values).unwrap_syscall(); } - /// Sets the model value for an entity. + /// Gets the values of a model record/entity/member. + /// Returns a zero initialized model value if the record/entity/member has not been set. /// /// # Arguments /// - /// * `model` - The selector of the model to be set. - /// * `keys` - The key to be used to find the entity. - /// * `value_names` - The name of model fields which are not a key. - /// * `values` - The value to be set. - /// * `layout` - The memory layout of the entity. + /// * `model_selector` - The selector of the model to be retrieved. + /// * `index` - The index of the record/entity/member to read. + /// * `layout` - The memory layout of the model. + /// + /// # Returns + /// + /// * `Span` - The serialized value of the model, zero initialized if not set. + fn entity( + self: @ContractState, + model_selector: felt252, + index: ModelIndex, + layout: dojo::database::introspect::Layout + ) -> Span { + match index { + ModelIndex::Keys(keys) => { + let entity_id = entity_id_from_keys(keys); + self._read_model_entity(model_selector, entity_id, layout) + }, + ModelIndex::Id(entity_id) => { + self._read_model_entity(model_selector, entity_id, layout) + }, + ModelIndex::MemberId(( + entity_id, member_id + )) => { self._read_model_member(model_selector, entity_id, member_id, layout) } + } + } + + /// Sets the model value for a model record/entity/member. + /// + /// # Arguments + /// + /// * `model_selector` - The selector of the model to be set. + /// * `index` - The index of the record/entity/member to write. + /// * `values` - The value to be set, serialized using the model layout format. + /// * `layout` - The memory layout of the model. fn set_entity( ref self: ContractState, - model: felt252, - keys: Span, + model_selector: felt252, + index: ModelIndex, values: Span, layout: dojo::database::introspect::Layout ) { assert( - self.can_write_model(model, get_caller_address()), Errors::NO_MODEL_WRITE_ACCESS + self.can_write_model(model_selector, get_caller_address()), + Errors::NO_MODEL_WRITE_ACCESS ); - self._write_model_data(model, keys, values, layout); - EventEmitter::emit(ref self, StoreSetRecord { table: model, keys, values }); + match index { + ModelIndex::Keys(keys) => { + let entity_id = entity_id_from_keys(keys); + self._write_model_entity(model_selector, entity_id, values, layout); + EventEmitter::emit( + ref self, StoreSetRecord { table: model_selector, keys, values } + ); + }, + ModelIndex::Id(entity_id) => { + self._write_model_entity(model_selector, entity_id, values, layout); + EventEmitter::emit( + ref self, StoreUpdateRecord { table: model_selector, entity_id, values } + ); + }, + ModelIndex::MemberId(( + entity_id, member_id + )) => { + self._write_model_member(model_selector, entity_id, member_id, values, layout) + } + } } - /// Deletes a model from an entity. + /// Deletes a record/entity of a model.. /// Deleting is setting all the values to 0 in the given layout. /// /// # Arguments /// - /// * `model` - The selector of the model to be deleted. - /// * `keys` - The key to be used to find the entity. - /// * `value_names` - The name of model fields which are not a key. - /// * `layout` - The memory layout of the entity. + /// * `model_selector` - The selector of the model to be deleted. + /// * `index` - The index of the record/entity to delete. + /// * `layout` - The memory layout of the model. fn delete_entity( ref self: ContractState, - model: felt252, - keys: Span, + model_selector: felt252, + index: ModelIndex, layout: dojo::database::introspect::Layout ) { assert( - self.can_write_model(model, get_caller_address()), Errors::NO_MODEL_WRITE_ACCESS + self.can_write_model(model_selector, get_caller_address()), + Errors::NO_MODEL_WRITE_ACCESS ); - self._delete_model_data(model, keys, layout); - EventEmitter::emit(ref self, StoreDelRecord { table: model, keys }); - } - - /// Gets the model value for an entity. Returns a zero initialized - /// model value if the entity has not been set. - /// - /// # Arguments - /// - /// * `model` - The selector of the model to be retrieved. - /// * `keys` - The keys used to find the entity. - /// * `value_names` - The name of model fields which are not a key. - /// * `layout` - The memory layout of the entity. - /// - /// # Returns - /// - /// * `Span` - The serialized value of the model, zero initialized if not set. - fn entity( - self: @ContractState, - model: felt252, - keys: Span, - layout: dojo::database::introspect::Layout - ) -> Span { - self._read_model_data(model, keys, layout) + match index { + ModelIndex::Keys(keys) => { + let entity_id = entity_id_from_keys(keys); + self._delete_model_entity(model_selector, entity_id, layout); + EventEmitter::emit( + ref self, StoreDelRecord { table: model_selector, entity_id } + ); + }, + ModelIndex::Id(entity_id) => { + self._delete_model_entity(model_selector, entity_id, layout); + EventEmitter::emit( + ref self, StoreDelRecord { table: model_selector, entity_id } + ); + }, + ModelIndex::MemberId(_) => { panic_with_felt252(Errors::DELETE_ENTITY_MEMBER); } + } } /// Gets the base contract class hash. @@ -1063,78 +1132,80 @@ mod world { || self.is_account_writer(resource_id) } - /// Write a new model record. + /// Write a new entity. /// /// # Arguments - /// * `model` - the model selector - /// * `keys` - the list of model keys to identify the record + /// * `model_selector` - the model selector + /// * `entity_id` - the id used to identify the record /// * `values` - the field values of the record /// * `layout` - the model layout - fn _write_model_data( + fn _write_model_entity( ref self: ContractState, - model: felt252, - keys: Span, + model_selector: felt252, + entity_id: felt252, values: Span, layout: dojo::database::introspect::Layout ) { - let model_key = poseidon::poseidon_hash_span(keys); let mut offset = 0; match layout { Layout::Fixed(layout) => { - Self::_write_fixed_layout(model, model_key, values, ref offset, layout); + Self::_write_fixed_layout( + model_selector, entity_id, values, ref offset, layout + ); }, Layout::Struct(layout) => { - Self::_write_struct_layout(model, model_key, values, ref offset, layout); + Self::_write_struct_layout( + model_selector, entity_id, values, ref offset, layout + ); }, _ => { panic!("Unexpected layout type for a model."); } }; } - /// Delete a model record. + /// Delete an entity. /// /// # Arguments - /// * `model` - the model selector - /// * `keys` - the list of model keys to identify the record + /// * `model_selector` - the model selector + /// * `entity_id` - the ID of the entity to remove. /// * `layout` - the model layout - fn _delete_model_data( + fn _delete_model_entity( ref self: ContractState, - model: felt252, - keys: Span, + model_selector: felt252, + entity_id: felt252, layout: dojo::database::introspect::Layout ) { - let model_key = poseidon::poseidon_hash_span(keys); - match layout { - Layout::Fixed(layout) => { Self::_delete_fixed_layout(model, model_key, layout); }, + Layout::Fixed(layout) => { + Self::_delete_fixed_layout(model_selector, entity_id, layout); + }, Layout::Struct(layout) => { - Self::_delete_struct_layout(model, model_key, layout); + Self::_delete_struct_layout(model_selector, entity_id, layout); }, _ => { panic!("Unexpected layout type for a model."); } }; } - /// Read a model record. + /// Read an entity. /// /// # Arguments - /// * `model` - the model selector - /// * `keys` - the list of model keys to identify the record + /// * `model_selector` - the model selector + /// * `entity_id` - the ID of the entity to read. /// * `layout` - the model layout - fn _read_model_data( + fn _read_model_entity( self: @ContractState, - model: felt252, - keys: Span, + model_selector: felt252, + entity_id: felt252, layout: dojo::database::introspect::Layout ) -> Span { - let model_key = poseidon::poseidon_hash_span(keys); let mut read_data = ArrayTrait::::new(); match layout { Layout::Fixed(layout) => { - Self::_read_fixed_layout(model, model_key, ref read_data, layout); + Self::_read_fixed_layout(model_selector, entity_id, ref read_data, layout); }, Layout::Struct(layout) => { - Self::_read_struct_layout(model, model_key, ref read_data, layout); + Self::_read_struct_layout(model_selector, entity_id, ref read_data, layout); }, _ => { panic!("Unexpected layout type for a model."); } }; @@ -1142,9 +1213,53 @@ mod world { read_data.span() } - /// Compute the full field key from parent key and current field key. - fn _field_key(parent_key: felt252, field_key: felt252) -> felt252 { - poseidon::poseidon_hash_span(array![parent_key, field_key].span()) + /// Read a model member value. + /// + /// # Arguments + /// * `model_selector` - the model selector + /// * `entity_id` - the ID of the entity for which to read a member. + /// * `member_id` - the selector of the model member to read. + /// * `layout` - the model layout + fn _read_model_member( + self: @ContractState, + model_selector: felt252, + entity_id: felt252, + member_id: felt252, + layout: dojo::database::introspect::Layout + ) -> Span { + let mut read_data = ArrayTrait::::new(); + Self::_read_layout( + model_selector, Self::_combine_key(entity_id, member_id), ref read_data, layout + ); + + read_data.span() + } + + /// Write a model member value. + /// + /// # Arguments + /// * `model_selector` - the model selector + /// * `entity_id` - the ID of the entity for which to write a member. + /// * `member_id` - the selector of the model member to write. + /// * `values` - the new member value. + /// * `layout` - the model layout + fn _write_model_member( + self: @ContractState, + model_selector: felt252, + entity_id: felt252, + member_id: felt252, + values: Span, + layout: dojo::database::introspect::Layout + ) { + let mut offset = 0; + Self::_write_layout( + model_selector, Self::_combine_key(entity_id, member_id), values, ref offset, layout + ) + } + + /// Combine parent and child keys to build one full key. + fn _combine_key(parent_key: felt252, child_key: felt252) -> felt252 { + poseidon::poseidon_hash_span(array![parent_key, child_key].span()) } /// Append some values to an array. @@ -1265,7 +1380,7 @@ mod world { if i >= array_len { break; } - let key = Self::_field_key(key, i.into()); + let key = Self::_combine_key(key, i.into()); Self::_write_layout(model, key, values, ref offset, item_layout); @@ -1323,7 +1438,7 @@ mod world { } let field_layout = *layout.at(i); - let field_key = Self::_field_key(key, field_layout.selector); + let field_key = Self::_combine_key(key, field_layout.selector); Self::_write_layout(model, field_key, values, ref offset, field_layout.layout); @@ -1353,7 +1468,7 @@ mod world { } let field_layout = *layout.at(i); - let key = Self::_field_key(key, i.into()); + let key = Self::_combine_key(key, i.into()); Self::_write_layout(model, key, values, ref offset, field_layout); @@ -1377,7 +1492,7 @@ mod world { offset += 1; // find the corresponding layout and then write the full variant - let variant_data_key = Self::_field_key(key, variant); + let variant_data_key = Self::_combine_key(key, variant); match Self::_find_variant_layout(variant, variant_layouts) { Option::Some(layout) => Self::_write_layout( @@ -1452,7 +1567,7 @@ mod world { } let field_layout = *layout.at(i); - let key = Self::_field_key(key, field_layout.selector); + let key = Self::_combine_key(key, field_layout.selector); Self::_delete_layout(model, key, field_layout.layout); @@ -1474,7 +1589,7 @@ mod world { } let field_layout = *layout.at(i); - let key = Self::_field_key(key, i.into()); + let key = Self::_combine_key(key, i.into()); Self::_delete_layout(model, key, field_layout); @@ -1494,7 +1609,7 @@ mod world { database::delete(model, key, array![251].span()); // find the corresponding layout and the delete the full variant - let variant_data_key = Self::_field_key(key, variant); + let variant_data_key = Self::_combine_key(key, variant); match Self::_find_variant_layout(variant, variant_layouts) { Option::Some(layout) => Self::_delete_layout(model, variant_data_key, layout), @@ -1572,7 +1687,7 @@ mod world { break; } - let field_key = Self::_field_key(key, i.into()); + let field_key = Self::_combine_key(key, i.into()); Self::_read_layout(model, field_key, ref read_data, item_layout); i += 1; @@ -1623,7 +1738,7 @@ mod world { } let field_layout = *layout.at(i); - let field_key = Self::_field_key(key, field_layout.selector); + let field_key = Self::_combine_key(key, field_layout.selector); Self::_read_layout(model, field_key, ref read_data, field_layout.layout); @@ -1648,7 +1763,7 @@ mod world { } let field_layout = *layout.at(i); - let field_key = Self::_field_key(key, i.into()); + let field_key = Self::_combine_key(key, i.into()); Self::_read_layout(model, field_key, ref read_data, field_layout); i += 1; @@ -1671,7 +1786,7 @@ mod world { read_data.append(variant); // find the corresponding layout and the read the variant data - let variant_data_key = Self::_field_key(key, variant); + let variant_data_key = Self::_combine_key(key, variant); match Self::_find_variant_layout(variant, variant_layouts) { Option::Some(layout) => Self::_read_layout( diff --git a/crates/dojo-core/src/world_test.cairo b/crates/dojo-core/src/world_test.cairo index cc46957bfe..952a0d5bb6 100644 --- a/crates/dojo-core/src/world_test.cairo +++ b/crates/dojo-core/src/world_test.cairo @@ -22,6 +22,7 @@ use dojo::config::component::Config::{ }; use dojo::model::Model; use dojo::benchmarks::{Character, GasCounterImpl}; +use dojo::utils::entity_id_from_keys; #[derive(Introspect, Copy, Drop, Serde)] enum OneEnum { @@ -324,7 +325,7 @@ mod bar { .read() .delete_entity( dojo::model::Model::::selector(), - array![get_caller_address().into()].span(), + dojo::model::ModelIndex::Keys(array![get_caller_address().into()].span()), dojo::model::Model::::layout() ); } @@ -974,6 +975,20 @@ fn test_can_call_init() { dojo_init.dojo_init(); } +#[test] +fn test_set_entity_by_id() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + let selector = dojo::model::Model::::selector(); + let entity_id = entity_id_from_keys(array![0x01234].span()); + let values = create_foo(); + let layout = dojo::model::Model::::layout(); + + world.set_entity(selector, dojo::model::ModelIndex::Id(entity_id), values, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Id(entity_id), layout); + assert_array(read_values, values); +} + #[test] fn test_set_entity_with_fixed_layout() { let world = deploy_world(); @@ -983,8 +998,8 @@ fn test_set_entity_with_fixed_layout() { let values = create_foo(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, get_key_test(), values, layout); - let read_values = world.entity(selector, keys, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(get_key_test()), values, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -998,9 +1013,9 @@ fn test_set_entity_with_struct_layout() { let values = create_struct_simple_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1014,9 +1029,9 @@ fn test_set_entity_with_struct_tuple_layout() { let values = create_struct_with_tuple(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1031,16 +1046,16 @@ fn test_set_entity_with_struct_enum_layout() { let layout = dojo::model::Model::::layout(); // test with the first variant - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); // then override with the second variant let values = create_struct_with_enum_second_variant(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1054,9 +1069,9 @@ fn test_set_entity_with_struct_simple_array_layout() { let values = create_struct_simple_array_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1070,9 +1085,9 @@ fn test_set_entity_with_struct_complex_array_layout() { let values = create_struct_complex_array_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1086,9 +1101,9 @@ fn test_set_entity_with_struct_layout_and_byte_array() { let values = create_struct_byte_array_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1102,9 +1117,9 @@ fn test_set_entity_with_nested_elements() { let values = create_struct_nested_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } @@ -1130,19 +1145,38 @@ fn test_set_entity_with_struct_generics_enum_layout() { let layout = dojo::model::Model::::layout(); // test with the first variant - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); // then override with the second variant let values = create_struct_generic_second_variant(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert_array(read_values, values); } +#[test] +fn test_delete_entity_by_id() { + let world = deploy_world(); + world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap()); + let selector = dojo::model::Model::::selector(); + let entity_id = entity_id_from_keys(get_key_test()); + let values = create_foo(); + let layout = dojo::model::Model::::layout(); + + world.set_entity(selector, dojo::model::ModelIndex::Id(entity_id), values, layout); + + world.delete_entity(selector, dojo::model::ModelIndex::Id(entity_id), layout); + + let read_values = world.entity(selector, dojo::model::ModelIndex::Id(entity_id), layout); + + assert!(read_values.len() == values.len()); + assert_empty_array(read_values); +} + #[test] fn test_delete_entity_with_fixed_layout() { let world = deploy_world(); @@ -1152,11 +1186,11 @@ fn test_delete_entity_with_fixed_layout() { let values = create_foo(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, get_key_test(), values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(get_key_test()), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == values.len()); assert_empty_array(read_values); @@ -1172,11 +1206,11 @@ fn test_delete_entity_with_simple_struct_layout() { let values = create_struct_simple_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == values.len()); assert_empty_array(read_values); @@ -1192,11 +1226,11 @@ fn test_delete_entity_with_struct_simple_array_layout() { let values = create_struct_simple_array_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); // array length set to 0, so the expected value span is shorter than the initial values let expected_values = array![0, 0, 0].span(); @@ -1216,11 +1250,11 @@ fn test_delete_entity_with_complex_array_struct_layout() { let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); // array length set to 0, so the expected value span is shorter than the initial values let expected_values = array![0, 0, 0, 0, 0, 0, 0, 0, 0, 0].span(); @@ -1239,12 +1273,12 @@ fn test_delete_entity_with_struct_tuple_layout() { let values = create_struct_with_tuple(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); let expected_values = array![0, 0].span(); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == expected_values.len()); assert_empty_array(read_values); @@ -1261,12 +1295,12 @@ fn test_delete_entity_with_struct_enum_layout() { let layout = dojo::model::Model::::layout(); // test with the first variant - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); let expected_values = array![0, 0, 0].span(); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == expected_values.len()); assert_empty_array(read_values); @@ -1282,12 +1316,12 @@ fn test_delete_entity_with_struct_layout_and_byte_array() { let values = create_struct_byte_array_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); let expected_values = array![0, 0, 0, 0].span(); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == expected_values.len()); assert_empty_array(read_values); @@ -1303,12 +1337,12 @@ fn test_delete_entity_with_nested_elements() { let values = create_struct_nested_model(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); let expected_values = array![0, 0, 0, 0, 0, 0, 0, 0, 0].span(); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == expected_values.len()); assert_empty_array(read_values); @@ -1324,12 +1358,12 @@ fn test_delete_entity_with_struct_generics_enum_layout() { let values = create_struct_generic_first_variant(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); - world.delete_entity(selector, keys, layout); + world.delete_entity(selector, dojo::model::ModelIndex::Keys(keys), layout); let expected_values = array![0, 0].span(); - let read_values = world.entity(selector, keys, layout); + let read_values = world.entity(selector, dojo::model::ModelIndex::Keys(keys), layout); assert!(read_values.len() == expected_values.len()); assert_empty_array(read_values); @@ -1348,7 +1382,7 @@ fn test_set_entity_with_unexpected_array_model_layout() { world .set_entity( dojo::model::Model::::selector(), - array![].span(), + dojo::model::ModelIndex::Keys(array![].span()), array![].span(), layout ); @@ -1367,7 +1401,7 @@ fn test_set_entity_with_unexpected_tuple_model_layout() { world .set_entity( dojo::model::Model::::selector(), - array![].span(), + dojo::model::ModelIndex::Keys(array![].span()), array![].span(), layout ); @@ -1385,7 +1419,9 @@ fn test_delete_entity_with_unexpected_array_model_layout() { world .delete_entity( - dojo::model::Model::::selector(), array![].span(), layout + dojo::model::Model::::selector(), + dojo::model::ModelIndex::Keys(array![].span()), + layout ); } @@ -1401,7 +1437,9 @@ fn test_delete_entity_with_unexpected_tuple_model_layout() { world .delete_entity( - dojo::model::Model::::selector(), array![].span(), layout + dojo::model::Model::::selector(), + dojo::model::ModelIndex::Keys(array![].span()), + layout ); } @@ -1415,7 +1453,12 @@ fn test_get_entity_with_unexpected_array_model_layout() { array![dojo::database::introspect::Introspect::::layout()].span() ); - world.entity(dojo::model::Model::::selector(), array![].span(), layout); + world + .entity( + dojo::model::Model::::selector(), + dojo::model::ModelIndex::Keys(array![].span()), + layout + ); } #[test] @@ -1428,7 +1471,12 @@ fn test_get_entity_with_unexpected_tuple_model_layout() { array![dojo::database::introspect::Introspect::::layout()].span() ); - world.entity(dojo::model::Model::::selector(), array![].span(), layout); + world + .entity( + dojo::model::Model::::selector(), + dojo::model::ModelIndex::Keys(array![].span()), + layout + ); } @@ -1442,7 +1490,7 @@ fn test_set_entity_with_bad_values_length_error_for_array_layout() { let keys = get_key_test(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, array![1].span(), layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), array![1].span(), layout); } #[test] @@ -1459,7 +1507,7 @@ fn test_set_entity_with_too_big_array_length() { .span(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); } #[test] @@ -1476,7 +1524,7 @@ fn test_set_entity_with_struct_layout_and_bad_byte_array_length() { .span(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); } #[test] @@ -1490,7 +1538,7 @@ fn test_set_entity_with_struct_layout_and_bad_value_length_for_byte_array() { let values: Span = array![1, 3, 'first', 'second', 'third', 'pending'].span(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, keys, values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(keys), values, layout); } fn write_foo_record(world: dojo::world::IWorldDispatcher) { @@ -1498,7 +1546,7 @@ fn write_foo_record(world: dojo::world::IWorldDispatcher) { let values = create_foo(); let layout = dojo::model::Model::::layout(); - world.set_entity(selector, get_key_test(), values, layout); + world.set_entity(selector, dojo::model::ModelIndex::Keys(get_key_test()), values, layout); } #[test] diff --git a/crates/dojo-lang/src/inline_macros/delete.rs b/crates/dojo-lang/src/inline_macros/delete.rs index 51b3e56229..efbcc543c4 100644 --- a/crates/dojo-lang/src/inline_macros/delete.rs +++ b/crates/dojo-lang/src/inline_macros/delete.rs @@ -145,12 +145,9 @@ impl InlineMacroExprPlugin for DeleteMacro { builder.add_str(&format!( " - let __delete_macro_value__ = {}; - {}.delete_entity( - dojo::model::Model::instance_selector(@__delete_macro_value__), - dojo::model::Model::keys(@__delete_macro_value__), - dojo::model::Model::instance_layout(@__delete_macro_value__) - );", + let __delete_model_instance__ = {}; + dojo::model::Model::delete(@__delete_model_instance__, {}); + ", entity, world.as_syntax_node().get_text(db), )); diff --git a/crates/dojo-lang/src/inline_macros/get.rs b/crates/dojo-lang/src/inline_macros/get.rs index 91cba669fd..e37c9dc83e 100644 --- a/crates/dojo-lang/src/inline_macros/get.rs +++ b/crates/dojo-lang/src/inline_macros/get.rs @@ -109,9 +109,7 @@ impl InlineMacroExprPlugin for GetMacro { builder.add_str(&format!( "\n - let __{model}_layout__ = dojo::model::Model::<{model}>::layout(); - let __{model}: {model} = dojo::model::Model::entity({}, __get_macro_keys__, \ - __{model}_layout__);\n", + let __{model}: {model} = dojo::model::Model::get({}, __get_macro_keys__);\n", world.as_syntax_node().get_text(db), )); } diff --git a/crates/dojo-lang/src/inline_macros/set.rs b/crates/dojo-lang/src/inline_macros/set.rs index 493384644e..7c7c5d3925 100644 --- a/crates/dojo-lang/src/inline_macros/set.rs +++ b/crates/dojo-lang/src/inline_macros/set.rs @@ -160,12 +160,8 @@ impl InlineMacroExprPlugin for SetMacro { builder.add_str(&format!( " let __set_model_instance__ = {}; - {}.set_entity( - dojo::model::Model::instance_selector(@__set_model_instance__), - dojo::model::Model::keys(@__set_model_instance__), - dojo::model::Model::values(@__set_model_instance__), - dojo::model::Model::instance_layout(@__set_model_instance__), - );", + dojo::model::Model::set(@__set_model_instance__, {}); + ", entity, world.as_syntax_node().get_text(db), )); diff --git a/crates/dojo-lang/src/introspect/layout.rs b/crates/dojo-lang/src/introspect/layout.rs index 216b00678c..9a8b929965 100644 --- a/crates/dojo-lang/src/introspect/layout.rs +++ b/crates/dojo-lang/src/introspect/layout.rs @@ -27,7 +27,7 @@ pub fn build_field_layouts( } let field_name = m.name(db).text(db); - let field_selector = get_selector_from_name(field_name.as_str()).unwrap().to_string(); + let field_selector = get_selector_from_name(&field_name.to_string()).unwrap(); let field_layout = get_layout_from_type_clause(db, diagnostics, &m.type_clause(db)); Some(format!( "dojo::database::introspect::FieldLayout {{ diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/abis/dojo-world.json b/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/abis/dojo-world.json index 25197b7bbb..fe02b53f14 100644 --- a/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/abis/dojo-world.json +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/abis/dojo-world.json @@ -46,6 +46,24 @@ } ] }, + { + "type": "enum", + "name": "dojo::world::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, { "type": "struct", "name": "core::array::Span::", @@ -298,12 +316,12 @@ "name": "entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -322,12 +340,12 @@ "name": "set_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "values", @@ -346,12 +364,12 @@ "name": "delete_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -937,7 +955,7 @@ }, { "type": "event", - "name": "dojo::world::world::StoreDelRecord", + "name": "dojo::world::world::StoreUpdateRecord", "kind": "struct", "members": [ { @@ -946,12 +964,34 @@ "kind": "data" }, { - "name": "keys", + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "values", "type": "core::array::Span::", "kind": "data" } ] }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::WriterUpdated", @@ -1111,6 +1151,11 @@ "type": "dojo::world::world::StoreSetRecord", "kind": "nested" }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world::StoreUpdateRecord", + "kind": "nested" + }, { "name": "StoreDelRecord", "type": "dojo::world::world::StoreDelRecord", diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml b/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml index 69caa32e86..850343b0cd 100644 --- a/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" -original_class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" +class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" abi = "manifests/dev/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world" diff --git a/crates/dojo-lang/src/model.rs b/crates/dojo-lang/src/model.rs index 72730edfeb..82128144ca 100644 --- a/crates/dojo-lang/src/model.rs +++ b/crates/dojo-lang/src/model.rs @@ -3,7 +3,9 @@ use std::collections::HashMap; use cairo_lang_defs::patcher::RewriteNode; use cairo_lang_defs::plugin::PluginDiagnostic; use cairo_lang_diagnostics::Severity; -use cairo_lang_syntax::node::ast::{ArgClause, Expr, ItemStruct, OptionArgListParenthesized}; +use cairo_lang_syntax::node::ast::{ + ArgClause, Expr, ItemStruct, Member as MemberAst, OptionArgListParenthesized, +}; use cairo_lang_syntax::node::db::SyntaxGroup; use cairo_lang_syntax::node::helpers::QueryAttrs; use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode}; @@ -12,6 +14,7 @@ use convert_case::{Case, Casing}; use dojo_world::contracts::naming; use dojo_world::manifest::Member; use dojo_world::metadata::{is_name_valid, NamespaceConfig}; +use starknet::core::utils::get_selector_from_name; use crate::plugin::{DojoAuxData, Model, DOJO_MODEL_ATTR}; @@ -249,19 +252,48 @@ pub fn handle_model_struct( ), }; + let mut members: Vec = vec![]; + let mut members_values: Vec = vec![]; + let mut param_keys: Vec = vec![]; + let mut serialized_keys: Vec = vec![]; + let mut serialized_param_keys: Vec = vec![]; + let mut serialized_values: Vec = vec![]; + let mut field_accessors: Vec = vec![]; + let mut entity_field_accessors: Vec = vec![]; let elements = struct_ast.members(db).elements(db); - let members: &Vec<_> = &elements - .iter() - .map(|member| Member { - name: member.name(db).text(db).to_string(), - ty: member.type_clause(db).ty(db).as_syntax_node().get_text(db).trim().to_string(), - key: member.has_attr(db, "key"), - }) - .collect::<_>(); - let keys: Vec<_> = members.iter().filter(|m| m.key).collect::<_>(); + elements.iter().for_each(|member_ast| { + let member = Member { + name: member_ast.name(db).text(db).to_string(), + ty: member_ast.type_clause(db).ty(db).as_syntax_node().get_text(db).trim().to_string(), + key: member_ast.has_attr(db, "key"), + }; + + if member.key { + validate_key_member(&member, db, member_ast, &mut diagnostics); + serialized_keys.push(serialize_member_ty(&member, true)); + serialized_param_keys.push(serialize_member_ty(&member, false)); + param_keys.push(format!("{}: {}", member.name, member.ty)); + } else { + serialized_values.push(serialize_member_ty(&member, true)); + members_values.push(RewriteNode::Text(format!("{}: {},\n", member.name, member.ty))); + } + + members.push(member); + }); + let param_keys = param_keys.join(", "); + + members.iter().filter(|m| !m.key).for_each(|member| { + field_accessors.push(generate_field_accessors( + model_name.clone(), + param_keys.clone(), + serialized_param_keys.clone(), + member, + )); + entity_field_accessors.push(generate_entity_field_accessors(model_name.clone(), member)); + }); - if keys.is_empty() { + if serialized_keys.is_empty() { diagnostics.push(PluginDiagnostic { message: "Model must define at least one #[key] attribute".into(), stable_ptr: struct_ast.name(db).stable_ptr().untyped(), @@ -269,7 +301,7 @@ pub fn handle_model_struct( }); } - if keys.len() == members.len() { + if serialized_values.is_empty() { diagnostics.push(PluginDiagnostic { message: "Model must define at least one member that is not a key".into(), stable_ptr: struct_ast.name(db).stable_ptr().untyped(), @@ -277,59 +309,155 @@ pub fn handle_model_struct( }); } - for k in &keys { - if k.ty == "u256" { - diagnostics.push(PluginDiagnostic { - message: "Key is only supported for core types that are 1 felt long once \ - serialized. `u256` is a struct of 2 u128, hence not supported." - .into(), - stable_ptr: struct_ast.name(db).stable_ptr().untyped(), - severity: Severity::Error, - }); - } + aux_data.models.push(Model { + name: model_name.clone(), + namespace: model_namespace.clone(), + members, + }); + + ( + RewriteNode::interpolate_patched( + " +#[derive(Drop, Serde)] +pub struct $type_name$Entity { + __id: felt252, // private field + $members_values$ +} + +#[generate_trait] +impl $type_name$EntityImpl of $type_name$EntityTrait { + fn get(world: dojo::world::IWorldDispatcher, entity_id: felt252) -> $type_name$Entity { + $type_name$ModelEntityImpl::get(world, entity_id) } - let serialize_member = |m: &Member, include_key: bool| { - if m.key && !include_key { - return None; - } + $entity_field_accessors$ +} - if m.ty == "felt252" { - return Some(RewriteNode::Text(format!( - "core::array::ArrayTrait::append(ref serialized, *self.{});", - m.name - ))); +#[generate_trait] +impl $type_name$Impl of $type_name$Trait { + fn entity_id_from_keys($param_keys$) -> felt252 { + let mut serialized = core::array::ArrayTrait::new(); + $serialized_param_keys$ + core::poseidon::poseidon_hash_span(serialized.span()) + } + + fn get(world: dojo::world::IWorldDispatcher, $param_keys$) -> $type_name$ { + let mut serialized = core::array::ArrayTrait::new(); + $serialized_param_keys$ + + dojo::model::Model::<$type_name$>::get(world, serialized.span()) + } + + $field_accessors$ +} + +impl $type_name$ModelEntityImpl of dojo::model::ModelEntity<$type_name$Entity> { + fn id(self: @$type_name$Entity) -> felt252 { + *self.__id + } + + fn values(self: @$type_name$Entity) -> Span { + let mut serialized = core::array::ArrayTrait::new(); + $serialized_values$ + core::array::ArrayTrait::span(@serialized) + } + + fn from_values(entity_id: felt252, values: Span) -> $type_name$Entity { + let mut serialized = array![entity_id]; + let mut i = 0; + loop { + if i >= values.len() { break; } + serialized.append(*values.at(i)); + i += 1; + }; + let mut serialized = core::array::ArrayTrait::span(@serialized); + + let entity_values = core::serde::Serde::<$type_name$Entity>::deserialize(ref serialized); + if core::option::OptionTrait::<$type_name$Entity>::is_none(@entity_values) { + panic!( + \"ModelEntity `$type_name$Entity`: deserialization failed.\" + ); } + core::option::OptionTrait::<$type_name$Entity>::unwrap(entity_values) + } - Some(RewriteNode::Text(format!( - "core::serde::Serde::serialize(self.{}, ref serialized);", - m.name - ))) - }; + fn get(world: dojo::world::IWorldDispatcher, entity_id: felt252) -> $type_name$Entity { + let values = dojo::world::IWorldDispatcherTrait::entity( + world, + dojo::model::Model::<$type_name$>::selector(), + dojo::world::ModelIndex::Id(entity_id), + dojo::model::Model::<$type_name$>::layout() + ); + Self::from_values(entity_id, values) + } - let serialized_keys: Vec<_> = - keys.iter().filter_map(|m| serialize_member(m, true)).collect::<_>(); + fn update(self: @$type_name$Entity, world: dojo::world::IWorldDispatcher) { + dojo::world::IWorldDispatcherTrait::set_entity( + world, + dojo::model::Model::<$type_name$>::selector(), + dojo::world::ModelIndex::Id(self.id()), + self.values(), + dojo::model::Model::<$type_name$>::layout() + ); + } - let serialized_values: Vec<_> = - members.iter().filter_map(|m| serialize_member(m, false)).collect::<_>(); + fn delete(self: @$type_name$Entity, world: dojo::world::IWorldDispatcher) { + dojo::world::IWorldDispatcherTrait::delete_entity( + world, + dojo::model::Model::<$type_name$>::selector(), + dojo::world::ModelIndex::Id(self.id()), + dojo::model::Model::<$type_name$>::layout() + ); + } - aux_data.models.push(Model { - name: model_name.clone(), - namespace: model_namespace.clone(), - members: members.to_vec(), - }); + fn get_member( + world: dojo::world::IWorldDispatcher, + entity_id: felt252, + member_id: felt252, + ) -> Span { + match dojo::utils::find_model_field_layout(dojo::model::Model::<$type_name$>::layout(), \ + member_id) { + Option::Some(field_layout) => { + dojo::world::IWorldDispatcherTrait::entity( + world, + dojo::model::Model::<$type_name$>::selector(), + dojo::world::ModelIndex::MemberId((entity_id, member_id)), + field_layout + ) + }, + Option::None => core::panic_with_felt252('bad member id') + } + } - ( - RewriteNode::interpolate_patched( - " -impl $type_name$Model of dojo::model::Model<$type_name$> { - fn entity(world: dojo::world::IWorldDispatcher, keys: Span, layout: \ - dojo::database::introspect::Layout) -> $type_name$ { + fn set_member( + self: @$type_name$Entity, + world: dojo::world::IWorldDispatcher, + member_id: felt252, + values: Span, + ) { + match dojo::utils::find_model_field_layout(dojo::model::Model::<$type_name$>::layout(), \ + member_id) { + Option::Some(field_layout) => { + dojo::world::IWorldDispatcherTrait::set_entity( + world, + dojo::model::Model::<$type_name$>::selector(), + dojo::world::ModelIndex::MemberId((self.id(), member_id)), + values, + field_layout + ) + }, + Option::None => core::panic_with_felt252('bad member id') + } + } +} + +impl $type_name$ModelImpl of dojo::model::Model<$type_name$> { + fn get(world: dojo::world::IWorldDispatcher, keys: Span) -> $type_name$ { let values = dojo::world::IWorldDispatcherTrait::entity( world, Self::selector(), - keys, - layout + dojo::world::ModelIndex::Keys(keys), + Self::layout() ); // TODO: Generate method to deserialize from keys / values directly to avoid @@ -351,6 +479,70 @@ impl $type_name$Model of dojo::model::Model<$type_name$> { core::option::OptionTrait::<$type_name$>::unwrap(entity) } + fn set( + self: @$type_name$, + world: dojo::world::IWorldDispatcher + ) { + dojo::world::IWorldDispatcherTrait::set_entity( + world, + Self::selector(), + dojo::world::ModelIndex::Keys(Self::keys(self)), + Self::values(self), + Self::layout() + ); + } + + fn delete( + self: @$type_name$, + world: dojo::world::IWorldDispatcher + ) { + dojo::world::IWorldDispatcherTrait::delete_entity( + world, + Self::selector(), + dojo::world::ModelIndex::Keys(Self::keys(self)), + Self::layout() + ); + } + + fn get_member( + world: dojo::world::IWorldDispatcher, + keys: Span, + member_id: felt252 + ) -> Span { + match dojo::utils::find_model_field_layout(Self::layout(), member_id) { + Option::Some(field_layout) => { + let entity_id = dojo::utils::entity_id_from_keys(keys); + dojo::world::IWorldDispatcherTrait::entity( + world, + Self::selector(), + dojo::world::ModelIndex::MemberId((entity_id, member_id)), + field_layout + ) + }, + Option::None => core::panic_with_felt252('bad member id') + } + } + + fn set_member( + self: @$type_name$, + world: dojo::world::IWorldDispatcher, + member_id: felt252, + values: Span + ) { + match dojo::utils::find_model_field_layout(Self::layout(), member_id) { + Option::Some(field_layout) => { + dojo::world::IWorldDispatcherTrait::set_entity( + world, + Self::selector(), + dojo::world::ModelIndex::MemberId((self.entity_id(), member_id)), + values, + field_layout + ) + }, + Option::None => core::panic_with_felt252('bad member id') + } + } + #[inline(always)] fn name() -> ByteArray { \"$type_name$\" @@ -390,7 +582,12 @@ impl $type_name$Model of dojo::model::Model<$type_name$> { fn namespace_hash() -> felt252 { $model_namespace_hash$ } - + + #[inline(always)] + fn entity_id(self: @$type_name$) -> felt252 { + core::poseidon::poseidon_hash_span(self.keys()) + } + #[inline(always)] fn keys(self: @$type_name$) -> Span { let mut serialized = core::array::ArrayTrait::new(); @@ -515,8 +712,196 @@ mod $contract_name$ { RewriteNode::Text(model_namespace_hash.to_string()), ), ("model_tag".to_string(), RewriteNode::Text(model_tag.clone())), + ("members_values".to_string(), RewriteNode::new_modified(members_values)), + ("param_keys".to_string(), RewriteNode::Text(param_keys)), + ( + "serialized_param_keys".to_string(), + RewriteNode::new_modified(serialized_param_keys), + ), + ("field_accessors".to_string(), RewriteNode::new_modified(field_accessors)), + ( + "entity_field_accessors".to_string(), + RewriteNode::new_modified(entity_field_accessors), + ), ]), ), diagnostics, ) } + +/// Validates that the key member is valid. +/// # Arguments +/// +/// * member: The member to validate. +/// * diagnostics: The diagnostics to push to, if the member is an invalid key. +fn validate_key_member( + member: &Member, + db: &dyn SyntaxGroup, + member_ast: &MemberAst, + diagnostics: &mut Vec, +) { + if member.ty == "u256" { + diagnostics.push(PluginDiagnostic { + message: "Key is only supported for core types that are 1 felt long once serialized. \ + `u256` is a struct of 2 u128, hence not supported." + .into(), + stable_ptr: member_ast.name(db).stable_ptr().untyped(), + severity: Severity::Error, + }); + } +} + +/// Creates a [`RewriteNode`] for the member type serialization. +/// +/// # Arguments +/// +/// * member: The member to serialize. +fn serialize_member_ty(member: &Member, with_self: bool) -> RewriteNode { + match member.ty.as_str() { + "felt252" => RewriteNode::Text(format!( + "core::array::ArrayTrait::append(ref serialized, {}{});\n", + if with_self { "*self." } else { "" }, + member.name + )), + _ => RewriteNode::Text(format!( + "core::serde::Serde::serialize({}{}, ref serialized);\n", + if with_self { "self." } else { "@" }, + member.name + )), + } +} + +/// Generates field accessors (`get_[field_name]` and `set_[field_name]`) for every +/// fields of a model. +/// +/// # Arguments +/// +/// * `model_name` - the model name. +/// * `param_keys` - coma separated model keys with the format `KEY_NAME: KEY_TYPE`. +/// * `serialized_param_keys` - code to serialize model keys in a `serialized` felt252 array. +/// * `member` - information about the field for which to generate accessors. +/// +/// # Returns +/// A [`RewriteNode`] containing accessors code. +fn generate_field_accessors( + model_name: String, + param_keys: String, + serialized_param_keys: Vec, + member: &Member, +) -> RewriteNode { + RewriteNode::interpolate_patched( + " + fn get_$field_name$(world: dojo::world::IWorldDispatcher, $param_keys$) -> $field_type$ { + let mut serialized = core::array::ArrayTrait::new(); + $serialized_param_keys$ + + let values = dojo::model::Model::<$model_name$>::get_member( + world, + serialized.span(), + $field_selector$ + ); + + let mut serialized = core::array::ArrayTrait::new(); + core::array::serialize_array_helper(values, ref serialized); + let mut serialized = core::array::ArrayTrait::span(@serialized); + + let field_value = core::serde::Serde::<$field_type$>::deserialize(ref serialized); + + if core::option::OptionTrait::<$field_type$>::is_none(@field_value) { + panic!( + \"Field `$model_name$::$field_name$`: deserialization failed.\" + ); + } + + core::option::OptionTrait::<$field_type$>::unwrap(field_value) + } + + fn set_$field_name$(self: @$model_name$, world: dojo::world::IWorldDispatcher, value: \ + $field_type$) { + let mut serialized = core::array::ArrayTrait::new(); + core::serde::Serde::serialize(@value, ref serialized); + + self.set_member( + world, + $field_selector$, + serialized.span() + ); + } + ", + &UnorderedHashMap::from([ + ("model_name".to_string(), RewriteNode::Text(model_name)), + ( + "field_selector".to_string(), + RewriteNode::Text( + get_selector_from_name(&member.name).expect("invalid member name").to_string(), + ), + ), + ("field_name".to_string(), RewriteNode::Text(member.name.clone())), + ("field_type".to_string(), RewriteNode::Text(member.ty.clone())), + ("param_keys".to_string(), RewriteNode::Text(param_keys)), + ("serialized_param_keys".to_string(), RewriteNode::new_modified(serialized_param_keys)), + ]), + ) +} + +/// Generates field accessors (`get_[field_name]` and `set_[field_name]`) for every +/// fields of a model entity. +/// +/// # Arguments +/// +/// * `model_name` - the model name. +/// * `member` - information about the field for which to generate accessors. +/// +/// # Returns +/// A [`RewriteNode`] containing accessors code. +fn generate_entity_field_accessors(model_name: String, member: &Member) -> RewriteNode { + RewriteNode::interpolate_patched( + " + fn get_$field_name$(world: dojo::world::IWorldDispatcher, entity_id: felt252) -> $field_type$ \ + { + let values = dojo::model::ModelEntity::<$model_name$Entity>::get_member( + world, + entity_id, + $field_selector$ + ); + + let mut serialized = core::array::ArrayTrait::new(); + core::array::serialize_array_helper(values, ref serialized); + let mut serialized = core::array::ArrayTrait::span(@serialized); + + let field_value = core::serde::Serde::<$field_type$>::deserialize(ref serialized); + + if core::option::OptionTrait::<$field_type$>::is_none(@field_value) { + panic!( + \"Field `$model_name$::$field_name$`: deserialization failed.\" + ); + } + + core::option::OptionTrait::<$field_type$>::unwrap(field_value) + } + + fn set_$field_name$(self: @$model_name$Entity, world: dojo::world::IWorldDispatcher, value: \ + $field_type$) { + let mut serialized = core::array::ArrayTrait::new(); + core::serde::Serde::serialize(@value, ref serialized); + + self.set_member( + world, + $field_selector$, + serialized.span() + ); + } +", + &UnorderedHashMap::from([ + ("model_name".to_string(), RewriteNode::Text(model_name)), + ( + "field_selector".to_string(), + RewriteNode::Text( + get_selector_from_name(&member.name).expect("invalid member name").to_string(), + ), + ), + ("field_name".to_string(), RewriteNode::Text(member.name.clone())), + ("field_type".to_string(), RewriteNode::Text(member.ty.clone())), + ]), + ) +} diff --git a/crates/dojo-lang/src/semantics/test_data/get b/crates/dojo-lang/src/semantics/test_data/get index 5f90c0d359..13e45b47a7 100644 --- a/crates/dojo-lang/src/semantics/test_data/get +++ b/crates/dojo-lang/src/semantics/test_data/get @@ -194,21 +194,6 @@ Block( ), }, ), - Let( - StatementLet { - pattern: Variable( - __Health_layout__, - ), - expr: FunctionCall( - ExprFunctionCall { - function: ?7::layout, - args: [], - coupon_arg: None, - ty: dojo::database::introspect::Layout, - }, - ), - }, - ), Let( StatementLet { pattern: Variable( @@ -216,7 +201,7 @@ Block( ), expr: FunctionCall( ExprFunctionCall { - function: ?8::entity, + function: ?7::get, args: [ Value( Var( @@ -228,11 +213,6 @@ Block( LocalVarId(test::__get_macro_keys__), ), ), - Value( - Var( - LocalVarId(test::__Health_layout__), - ), - ), ], coupon_arg: None, ty: test::Health, diff --git a/crates/dojo-lang/src/semantics/test_data/set b/crates/dojo-lang/src/semantics/test_data/set index d41133d396..03cd1da1b9 100644 --- a/crates/dojo-lang/src/semantics/test_data/set +++ b/crates/dojo-lang/src/semantics/test_data/set @@ -119,95 +119,21 @@ Block( StatementExpr { expr: FunctionCall( ExprFunctionCall { - function: dojo::world::IWorldDispatcherImpl::set_entity, + function: ?6::set, args: [ Value( - Var( - LocalVarId(test::world), - ), - ), - Value( - FunctionCall( - ExprFunctionCall { - function: ?7::instance_selector, - args: [ - Value( - Snapshot( - ExprSnapshot { - inner: Var( - LocalVarId(test::__set_model_instance__), - ), - ty: @test::Health, - }, - ), - ), - ], - coupon_arg: None, - ty: core::felt252, - }, - ), - ), - Value( - FunctionCall( - ExprFunctionCall { - function: ?8::keys, - args: [ - Value( - Snapshot( - ExprSnapshot { - inner: Var( - LocalVarId(test::__set_model_instance__), - ), - ty: @test::Health, - }, - ), - ), - ], - coupon_arg: None, - ty: core::array::Span::, - }, - ), - ), - Value( - FunctionCall( - ExprFunctionCall { - function: ?9::values, - args: [ - Value( - Snapshot( - ExprSnapshot { - inner: Var( - LocalVarId(test::__set_model_instance__), - ), - ty: @test::Health, - }, - ), - ), - ], - coupon_arg: None, - ty: core::array::Span::, + Snapshot( + ExprSnapshot { + inner: Var( + LocalVarId(test::__set_model_instance__), + ), + ty: @test::Health, }, ), ), Value( - FunctionCall( - ExprFunctionCall { - function: ?10::instance_layout, - args: [ - Value( - Snapshot( - ExprSnapshot { - inner: Var( - LocalVarId(test::__set_model_instance__), - ), - ty: @test::Health, - }, - ), - ), - ], - coupon_arg: None, - ty: dojo::database::introspect::Layout, - }, + Var( + LocalVarId(test::world), ), ), ], diff --git a/crates/dojo-world/src/contracts/abi/world.rs b/crates/dojo-world/src/contracts/abi/world.rs index a3a75a5ee2..f4a86c7a37 100644 --- a/crates/dojo-world/src/contracts/abi/world.rs +++ b/crates/dojo-world/src/contracts/abi/world.rs @@ -52,6 +52,24 @@ abigen!( } ] }, + { + "type": "enum", + "name": "dojo::world::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, { "type": "struct", "name": "core::array::Span::", @@ -304,12 +322,12 @@ abigen!( "name": "entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -328,12 +346,12 @@ abigen!( "name": "set_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "values", @@ -352,12 +370,12 @@ abigen!( "name": "delete_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -943,7 +961,7 @@ abigen!( }, { "type": "event", - "name": "dojo::world::world::StoreDelRecord", + "name": "dojo::world::world::StoreUpdateRecord", "kind": "struct", "members": [ { @@ -952,12 +970,34 @@ abigen!( "kind": "data" }, { - "name": "keys", + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "values", "type": "core::array::Span::", "kind": "data" } ] }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::WriterUpdated", @@ -1117,6 +1157,11 @@ abigen!( "type": "dojo::world::world::StoreSetRecord", "kind": "nested" }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world::StoreUpdateRecord", + "kind": "nested" + }, { "name": "StoreDelRecord", "type": "dojo::world::world::StoreDelRecord", diff --git a/crates/dojo-world/src/contracts/model.rs b/crates/dojo-world/src/contracts/model.rs index de38198885..1026a2c19f 100644 --- a/crates/dojo-world/src/contracts/model.rs +++ b/crates/dojo-world/src/contracts/model.rs @@ -25,6 +25,9 @@ pub mod abigen { pub mod model { pub use crate::contracts::abi::model::*; } + pub mod world { + pub use crate::contracts::abi::world::*; + } } #[derive(Debug, thiserror::Error)] @@ -126,7 +129,11 @@ where let raw_layout = self.model_reader.layout().raw_call().await?; let layout = Layout::cairo_deserialize(raw_layout.as_slice(), 0)?; - Ok(self.world_reader.entity(&self.selector(), &keys.to_vec(), &layout).call().await?) + Ok(self + .world_reader + .entity(&self.selector(), &abigen::world::ModelIndex::Keys(keys.to_vec()), &layout) + .call() + .await?) } pub async fn entity(&self, keys: &[Felt]) -> Result { diff --git a/crates/dojo-world/src/contracts/naming.rs b/crates/dojo-world/src/contracts/naming.rs index 4255450643..93bd561b3a 100644 --- a/crates/dojo-world/src/contracts/naming.rs +++ b/crates/dojo-world/src/contracts/naming.rs @@ -68,8 +68,8 @@ pub fn get_filename_from_tag(tag: &str) -> String { format!("{tag}{TAG_SEPARATOR}{selector}") } -pub fn compute_bytearray_hash(namespace: &str) -> Felt { - let ba = ByteArray::from_string(namespace).unwrap(); +pub fn compute_bytearray_hash(value: &str) -> Felt { + let ba = ByteArray::from_string(value).unwrap(); poseidon_hash_many(&ByteArray::cairo_serialize(&ba)) } diff --git a/crates/sozo/ops/src/events.rs b/crates/sozo/ops/src/events.rs index 4092613eb0..cd182b3df7 100644 --- a/crates/sozo/ops/src/events.rs +++ b/crates/sozo/ops/src/events.rs @@ -279,7 +279,7 @@ mod tests { let result = extract_events(&manifest, &project_dir, &target_dir).unwrap(); // we are just collecting all events from manifest file so just verifying count should work - assert_eq!(result.len(), 16); + assert_eq!(result.len(), 17); } #[test] diff --git a/crates/torii/types-test/src/contracts.cairo b/crates/torii/types-test/src/contracts.cairo index df0ef8be39..88a24731ce 100644 --- a/crates/torii/types-test/src/contracts.cairo +++ b/crates/torii/types-test/src/contracts.cairo @@ -10,7 +10,7 @@ trait IRecords { mod records { use starknet::{ContractAddress, get_caller_address}; use types_test::models::{ - Record, RecordSibling, Subrecord, Nested, NestedMore, NestedMost, Depth + Record, RecordTrait, RecordSibling, RecordSiblingTrait, Subrecord, SubrecordTrait, Nested, NestedMore, NestedMost, Depth }; use types_test::{seed, random}; use super::IRecords; diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 2c7ec1784d..7eddc0150a 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -43,7 +43,7 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0x6162896d1d7ab204c7ccac6dd5f8e9e7c25ecd5ae4fcb4ad32e57786bb46e03" private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x221d9c1ca42fe008cd124d38d5987d5bd3751adf43bec52d1fbb41288203cd5" +world_address = "0x2f7a4447d78fa066cead76981c04deb1fc9cb3c700bd246b697425e900641a0" [profile.release.tool.dojo] # for more info on how `merge-strategy` works see: diff --git a/examples/spawn-and-move/manifests/dev/base/abis/contracts/dojo_examples-actions-40b6994c.json b/examples/spawn-and-move/manifests/dev/base/abis/contracts/dojo_examples-actions-40b6994c.json index 1e5a2ea426..7c75c59707 100644 --- a/examples/spawn-and-move/manifests/dev/base/abis/contracts/dojo_examples-actions-40b6994c.json +++ b/examples/spawn-and-move/manifests/dev/base/abis/contracts/dojo_examples-actions-40b6994c.json @@ -274,6 +274,30 @@ ], "state_mutability": "view" }, + { + "type": "function", + "name": "update_player_name", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_player_name_value", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, { "type": "function", "name": "reset_player_config", diff --git a/examples/spawn-and-move/manifests/dev/base/abis/dojo-world.json b/examples/spawn-and-move/manifests/dev/base/abis/dojo-world.json index 25197b7bbb..fe02b53f14 100644 --- a/examples/spawn-and-move/manifests/dev/base/abis/dojo-world.json +++ b/examples/spawn-and-move/manifests/dev/base/abis/dojo-world.json @@ -46,6 +46,24 @@ } ] }, + { + "type": "enum", + "name": "dojo::world::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, { "type": "struct", "name": "core::array::Span::", @@ -298,12 +316,12 @@ "name": "entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -322,12 +340,12 @@ "name": "set_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "values", @@ -346,12 +364,12 @@ "name": "delete_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -937,7 +955,7 @@ }, { "type": "event", - "name": "dojo::world::world::StoreDelRecord", + "name": "dojo::world::world::StoreUpdateRecord", "kind": "struct", "members": [ { @@ -946,12 +964,34 @@ "kind": "data" }, { - "name": "keys", + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "values", "type": "core::array::Span::", "kind": "data" } ] }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::WriterUpdated", @@ -1111,6 +1151,11 @@ "type": "dojo::world::world::StoreSetRecord", "kind": "nested" }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world::StoreUpdateRecord", + "kind": "nested" + }, { "name": "StoreDelRecord", "type": "dojo::world::world::StoreDelRecord", diff --git a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-actions-40b6994c.toml b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-actions-40b6994c.toml index 677007342b..d570cea3bc 100644 --- a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-actions-40b6994c.toml +++ b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-actions-40b6994c.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d" -original_class_hash = "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d" +class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" +original_class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" base_class_hash = "0x0" abi = "manifests/dev/base/abis/contracts/dojo_examples-actions-40b6994c.json" reads = [] diff --git a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-mock_token-31599eb2.toml b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-mock_token-31599eb2.toml index 137baf0aaa..295d2d78f1 100644 --- a/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-mock_token-31599eb2.toml +++ b/examples/spawn-and-move/manifests/dev/base/contracts/dojo_examples-mock_token-31599eb2.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef" -original_class_hash = "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef" +class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" +original_class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" base_class_hash = "0x0" abi = "manifests/dev/base/abis/contracts/dojo_examples-mock_token-31599eb2.json" reads = [] diff --git a/examples/spawn-and-move/manifests/dev/base/dojo-world.toml b/examples/spawn-and-move/manifests/dev/base/dojo-world.toml index 69caa32e86..850343b0cd 100644 --- a/examples/spawn-and-move/manifests/dev/base/dojo-world.toml +++ b/examples/spawn-and-move/manifests/dev/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" -original_class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" +class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" abi = "manifests/dev/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world" diff --git a/examples/spawn-and-move/manifests/dev/deployment/abis/contracts/dojo_examples-actions-40b6994c.json b/examples/spawn-and-move/manifests/dev/deployment/abis/contracts/dojo_examples-actions-40b6994c.json index 1e5a2ea426..7c75c59707 100644 --- a/examples/spawn-and-move/manifests/dev/deployment/abis/contracts/dojo_examples-actions-40b6994c.json +++ b/examples/spawn-and-move/manifests/dev/deployment/abis/contracts/dojo_examples-actions-40b6994c.json @@ -274,6 +274,30 @@ ], "state_mutability": "view" }, + { + "type": "function", + "name": "update_player_name", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_player_name_value", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, { "type": "function", "name": "reset_player_config", diff --git a/examples/spawn-and-move/manifests/dev/deployment/abis/dojo-world.json b/examples/spawn-and-move/manifests/dev/deployment/abis/dojo-world.json index 25197b7bbb..fe02b53f14 100644 --- a/examples/spawn-and-move/manifests/dev/deployment/abis/dojo-world.json +++ b/examples/spawn-and-move/manifests/dev/deployment/abis/dojo-world.json @@ -46,6 +46,24 @@ } ] }, + { + "type": "enum", + "name": "dojo::world::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, { "type": "struct", "name": "core::array::Span::", @@ -298,12 +316,12 @@ "name": "entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -322,12 +340,12 @@ "name": "set_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "values", @@ -346,12 +364,12 @@ "name": "delete_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -937,7 +955,7 @@ }, { "type": "event", - "name": "dojo::world::world::StoreDelRecord", + "name": "dojo::world::world::StoreUpdateRecord", "kind": "struct", "members": [ { @@ -946,12 +964,34 @@ "kind": "data" }, { - "name": "keys", + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "values", "type": "core::array::Span::", "kind": "data" } ] }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::WriterUpdated", @@ -1111,6 +1151,11 @@ "type": "dojo::world::world::StoreSetRecord", "kind": "nested" }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world::StoreUpdateRecord", + "kind": "nested" + }, { "name": "StoreDelRecord", "type": "dojo::world::world::StoreDelRecord", diff --git a/examples/spawn-and-move/manifests/dev/deployment/manifest.json b/examples/spawn-and-move/manifests/dev/deployment/manifest.json index d29ac5c4cb..f3816370a6 100644 --- a/examples/spawn-and-move/manifests/dev/deployment/manifest.json +++ b/examples/spawn-and-move/manifests/dev/deployment/manifest.json @@ -1,8 +1,8 @@ { "world": { "kind": "WorldContract", - "class_hash": "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381", - "original_class_hash": "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381", + "class_hash": "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44", + "original_class_hash": "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44", "abi": [ { "type": "impl", @@ -51,6 +51,24 @@ } ] }, + { + "type": "enum", + "name": "dojo::world::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, { "type": "struct", "name": "core::array::Span::", @@ -303,12 +321,12 @@ "name": "entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -327,12 +345,12 @@ "name": "set_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "values", @@ -351,12 +369,12 @@ "name": "delete_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -942,7 +960,7 @@ }, { "type": "event", - "name": "dojo::world::world::StoreDelRecord", + "name": "dojo::world::world::StoreUpdateRecord", "kind": "struct", "members": [ { @@ -951,12 +969,34 @@ "kind": "data" }, { - "name": "keys", + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "values", "type": "core::array::Span::", "kind": "data" } ] }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::WriterUpdated", @@ -1116,6 +1156,11 @@ "type": "dojo::world::world::StoreSetRecord", "kind": "nested" }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world::StoreUpdateRecord", + "kind": "nested" + }, { "name": "StoreDelRecord", "type": "dojo::world::world::StoreDelRecord", @@ -1144,8 +1189,8 @@ ] } ], - "address": "0x221d9c1ca42fe008cd124d38d5987d5bd3751adf43bec52d1fbb41288203cd5", - "transaction_hash": "0x1de80bae5a65c23715b7ca20ecc775b6bb62a844d8569acf91ff2d3b93f1c9c", + "address": "0x2f7a4447d78fa066cead76981c04deb1fc9cb3c700bd246b697425e900641a0", + "transaction_hash": "0x2587e7362c8ca0f8478bf1256e8fdd40b85f70adfb22d886293c21da653b3e6", "block_number": 3, "seed": "dojo_examples", "metadata": { @@ -1165,9 +1210,9 @@ "contracts": [ { "kind": "DojoContract", - "address": "0x64839f1b5e1958354ee257bbd7c07e0403ff6098145a92ddb9b093fc35662c4", - "class_hash": "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d", - "original_class_hash": "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d", + "address": "0x3f6ef508b9837b327ae5704e719094eae81c1f0086cec8e5b83e25b5e9d071b", + "class_hash": "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be", + "original_class_hash": "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", "abi": [ { @@ -1445,6 +1490,30 @@ ], "state_mutability": "view" }, + { + "type": "function", + "name": "update_player_name", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_player_name_value", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, { "type": "function", "name": "reset_player_config", @@ -1572,7 +1641,7 @@ }, { "kind": "DojoContract", - "address": "0x5c4e7bb5f60d58c22c96aba611cb2f53462dcd59f7ade5d1675be5f8c20226b", + "address": "0x26ed07397dc7462adc94b2b070fec2bfa2b0e36aaa6bcafadc8c8cef8e46020", "class_hash": "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674", "original_class_hash": "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", @@ -1809,9 +1878,9 @@ }, { "kind": "DojoContract", - "address": "0x4b1371f328d8d86f6007f4ee8bd7d8a2f1c9f482531266b2df294dc55044d20", - "class_hash": "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef", - "original_class_hash": "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef", + "address": "0x2470881191edf8260e9f6cec7244e117ea2d0f5d48c76ec9e990222c2b7a7a6", + "class_hash": "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028", + "original_class_hash": "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", "abi": [ { @@ -2028,7 +2097,7 @@ }, { "kind": "DojoContract", - "address": "0x3ba8ab3546affa46b8094e7822459537c6e348af82fb3f35a32f42f820f3bfe", + "address": "0x7f21ab47087c7fc6e9dae445cf2c7d8e0b8c651cb850d81a7e8eccc636f1b75", "class_hash": "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7", "original_class_hash": "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", diff --git a/examples/spawn-and-move/manifests/dev/deployment/manifest.toml b/examples/spawn-and-move/manifests/dev/deployment/manifest.toml index 83fc9287a8..e2cb2f190c 100644 --- a/examples/spawn-and-move/manifests/dev/deployment/manifest.toml +++ b/examples/spawn-and-move/manifests/dev/deployment/manifest.toml @@ -1,10 +1,10 @@ [world] kind = "WorldContract" -class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" -original_class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" +class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" abi = "manifests/dev/deployment/abis/dojo-world.json" -address = "0x221d9c1ca42fe008cd124d38d5987d5bd3751adf43bec52d1fbb41288203cd5" -transaction_hash = "0x1de80bae5a65c23715b7ca20ecc775b6bb62a844d8569acf91ff2d3b93f1c9c" +address = "0x2f7a4447d78fa066cead76981c04deb1fc9cb3c700bd246b697425e900641a0" +transaction_hash = "0x2587e7362c8ca0f8478bf1256e8fdd40b85f70adfb22d886293c21da653b3e6" block_number = 3 seed = "dojo_examples" manifest_name = "dojo-world" @@ -23,9 +23,9 @@ manifest_name = "dojo-base" [[contracts]] kind = "DojoContract" -address = "0x64839f1b5e1958354ee257bbd7c07e0403ff6098145a92ddb9b093fc35662c4" -class_hash = "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d" -original_class_hash = "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d" +address = "0x3f6ef508b9837b327ae5704e719094eae81c1f0086cec8e5b83e25b5e9d071b" +class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" +original_class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" abi = "manifests/dev/deployment/abis/contracts/dojo_examples-actions-40b6994c.json" reads = [] @@ -40,7 +40,7 @@ manifest_name = "dojo_examples-actions-40b6994c" [[contracts]] kind = "DojoContract" -address = "0x5c4e7bb5f60d58c22c96aba611cb2f53462dcd59f7ade5d1675be5f8c20226b" +address = "0x26ed07397dc7462adc94b2b070fec2bfa2b0e36aaa6bcafadc8c8cef8e46020" class_hash = "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674" original_class_hash = "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" @@ -54,9 +54,9 @@ manifest_name = "dojo_examples-dungeon-6620e0e6" [[contracts]] kind = "DojoContract" -address = "0x4b1371f328d8d86f6007f4ee8bd7d8a2f1c9f482531266b2df294dc55044d20" -class_hash = "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef" -original_class_hash = "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef" +address = "0x2470881191edf8260e9f6cec7244e117ea2d0f5d48c76ec9e990222c2b7a7a6" +class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" +original_class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" abi = "manifests/dev/deployment/abis/contracts/dojo_examples-mock_token-31599eb2.json" reads = [] @@ -68,7 +68,7 @@ manifest_name = "dojo_examples-mock_token-31599eb2" [[contracts]] kind = "DojoContract" -address = "0x3ba8ab3546affa46b8094e7822459537c6e348af82fb3f35a32f42f820f3bfe" +address = "0x7f21ab47087c7fc6e9dae445cf2c7d8e0b8c651cb850d81a7e8eccc636f1b75" class_hash = "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7" original_class_hash = "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" diff --git a/examples/spawn-and-move/manifests/release/base/abis/contracts/dojo_examples-actions-40b6994c.json b/examples/spawn-and-move/manifests/release/base/abis/contracts/dojo_examples-actions-40b6994c.json index 1e5a2ea426..7c75c59707 100644 --- a/examples/spawn-and-move/manifests/release/base/abis/contracts/dojo_examples-actions-40b6994c.json +++ b/examples/spawn-and-move/manifests/release/base/abis/contracts/dojo_examples-actions-40b6994c.json @@ -274,6 +274,30 @@ ], "state_mutability": "view" }, + { + "type": "function", + "name": "update_player_name", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "update_player_name_value", + "inputs": [ + { + "name": "name", + "type": "core::byte_array::ByteArray" + } + ], + "outputs": [], + "state_mutability": "external" + }, { "type": "function", "name": "reset_player_config", diff --git a/examples/spawn-and-move/manifests/release/base/abis/dojo-world.json b/examples/spawn-and-move/manifests/release/base/abis/dojo-world.json index 25197b7bbb..fe02b53f14 100644 --- a/examples/spawn-and-move/manifests/release/base/abis/dojo-world.json +++ b/examples/spawn-and-move/manifests/release/base/abis/dojo-world.json @@ -46,6 +46,24 @@ } ] }, + { + "type": "enum", + "name": "dojo::world::ModelIndex", + "variants": [ + { + "name": "Keys", + "type": "core::array::Span::" + }, + { + "name": "Id", + "type": "core::felt252" + }, + { + "name": "MemberId", + "type": "(core::felt252, core::felt252)" + } + ] + }, { "type": "struct", "name": "core::array::Span::", @@ -298,12 +316,12 @@ "name": "entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -322,12 +340,12 @@ "name": "set_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "values", @@ -346,12 +364,12 @@ "name": "delete_entity", "inputs": [ { - "name": "model", + "name": "model_selector", "type": "core::felt252" }, { - "name": "keys", - "type": "core::array::Span::" + "name": "index", + "type": "dojo::world::ModelIndex" }, { "name": "layout", @@ -937,7 +955,7 @@ }, { "type": "event", - "name": "dojo::world::world::StoreDelRecord", + "name": "dojo::world::world::StoreUpdateRecord", "kind": "struct", "members": [ { @@ -946,12 +964,34 @@ "kind": "data" }, { - "name": "keys", + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "values", "type": "core::array::Span::", "kind": "data" } ] }, + { + "type": "event", + "name": "dojo::world::world::StoreDelRecord", + "kind": "struct", + "members": [ + { + "name": "table", + "type": "core::felt252", + "kind": "data" + }, + { + "name": "entity_id", + "type": "core::felt252", + "kind": "data" + } + ] + }, { "type": "event", "name": "dojo::world::world::WriterUpdated", @@ -1111,6 +1151,11 @@ "type": "dojo::world::world::StoreSetRecord", "kind": "nested" }, + { + "name": "StoreUpdateRecord", + "type": "dojo::world::world::StoreUpdateRecord", + "kind": "nested" + }, { "name": "StoreDelRecord", "type": "dojo::world::world::StoreDelRecord", diff --git a/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-actions-40b6994c.toml b/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-actions-40b6994c.toml index eab0fb52ab..0a2661f564 100644 --- a/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-actions-40b6994c.toml +++ b/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-actions-40b6994c.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d" -original_class_hash = "0x2c975b0d681861edd6caad5fbc0b42dc3e8b034b6c0b0a7628fb3a7bedf1e8d" +class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" +original_class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" base_class_hash = "0x0" abi = "manifests/release/base/abis/contracts/dojo_examples-actions-40b6994c.json" reads = [] diff --git a/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-mock_token-31599eb2.toml b/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-mock_token-31599eb2.toml index 1ead2c6821..8e068df249 100644 --- a/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-mock_token-31599eb2.toml +++ b/examples/spawn-and-move/manifests/release/base/contracts/dojo_examples-mock_token-31599eb2.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef" -original_class_hash = "0x97cd4fb3acfe9e6a024589ea34db2fc587586d699ef3732ce627e1771158ef" +class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" +original_class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" base_class_hash = "0x0" abi = "manifests/release/base/abis/contracts/dojo_examples-mock_token-31599eb2.json" reads = [] diff --git a/examples/spawn-and-move/manifests/release/base/dojo-world.toml b/examples/spawn-and-move/manifests/release/base/dojo-world.toml index a66555ad64..75eaba9b51 100644 --- a/examples/spawn-and-move/manifests/release/base/dojo-world.toml +++ b/examples/spawn-and-move/manifests/release/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" -original_class_hash = "0xa2a68c8a36082692ff56aece7e4e14dfa533b7c565c749ae8045000e1bf381" +class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" abi = "manifests/release/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world" diff --git a/examples/spawn-and-move/src/actions.cairo b/examples/spawn-and-move/src/actions.cairo index bc95a5899d..da2b56ce5b 100644 --- a/examples/spawn-and-move/src/actions.cairo +++ b/examples/spawn-and-move/src/actions.cairo @@ -6,6 +6,8 @@ trait IActions { fn move(ref world: IWorldDispatcher, direction: Direction); fn set_player_config(ref world: IWorldDispatcher, name: ByteArray); fn get_player_position(world: @IWorldDispatcher) -> Position; + fn update_player_name(ref world: IWorldDispatcher, name: ByteArray); + fn update_player_name_value(ref world: IWorldDispatcher, name: ByteArray); fn reset_player_config(ref world: IWorldDispatcher); fn set_player_server_profile(ref world: IWorldDispatcher, server_id: u32, name: ByteArray); #[cfg(feature: 'dungeon')] @@ -25,7 +27,8 @@ mod actions { use starknet::{ContractAddress, get_caller_address}; use dojo_examples::models::{ - Position, Moves, Direction, Vec2, PlayerConfig, PlayerItem, ServerProfile + Position, Moves, Direction, Vec2, PlayerConfig, PlayerItem, ServerProfile, PositionTrait, + MovesTrait, MovesEntityTrait, PlayerConfigTrait, PlayerConfigEntityTrait }; use dojo_examples::utils::next_position; @@ -84,11 +87,28 @@ mod actions { fn move(ref world: IWorldDispatcher, direction: Direction) { let player = get_caller_address(); - let (mut position, mut moves) = get!(world, player, (Position, Moves)); + + // instead of using the `get!` macro, you can directly use + // the Trait::get method + let mut position = PositionTrait::get(world, player); + + // you can also get entity values by entity ID with the `EntityTrait` trait. + // Note that it returns a `Entity` struct which contains + // model values and the entity ID. + let move_id = MovesTrait::entity_id_from_keys(player); + let mut moves = MovesEntityTrait::get(world, move_id); + moves.remaining -= 1; moves.last_direction = direction; let next = next_position(position, direction); - set!(world, (moves, next)); + + // instead of using the `set!` macro, you can directly use + // the Trait::set method + next.set(world); + + // you can also update entity values by entity ID with the `EntityTrait` trait. + moves.update(world); + emit!(world, (Moved { player, direction })); } @@ -107,9 +127,11 @@ mod actions { fn reset_player_config(ref world: IWorldDispatcher) { let player = get_caller_address(); - let (position, moves, config) = get!(world, player, (Position, Moves, PlayerConfig)); + let (position, moves) = get!(world, player, (Position, Moves)); + let config = PlayerConfigTrait::get(world, player); - delete!(world, (position, moves, config)); + delete!(world, (position, moves)); + config.delete(world); let (position, moves, config) = get!(world, player, (Position, Moves, PlayerConfig)); @@ -144,6 +166,26 @@ mod actions { IDungeonDispatcher { contract_address: dungeon_address }.enter(); } + + fn update_player_name(ref world: IWorldDispatcher, name: ByteArray) { + let player = get_caller_address(); + let config = PlayerConfigTrait::get(world, player); + config.set_name(world, name.clone()); + + let new_name = PlayerConfigTrait::get_name(world, player); + assert(new_name == name, 'unable to change name'); + } + + fn update_player_name_value(ref world: IWorldDispatcher, name: ByteArray) { + let player = get_caller_address(); + let config_id = PlayerConfigTrait::entity_id_from_keys(player); + + let config = PlayerConfigEntityTrait::get(world, config_id); + config.set_name(world, name.clone()); + + let new_name = PlayerConfigEntityTrait::get_name(world, config_id); + assert(new_name == name, 'unable to change name'); + } } // The `generate_trait` attribute is not compatible with `world` parameter expansion. @@ -196,6 +238,12 @@ mod tests { // System calls actions_system.spawn(); let initial_moves = get!(world, caller, Moves); + let initial_position = get!(world, caller, Position); + + assert( + initial_position.vec.x == 10 && initial_position.vec.y == 10, 'wrong initial position' + ); + actions_system.move(Direction::Right(())); let moves = get!(world, caller, Moves); @@ -205,7 +253,7 @@ mod tests { assert(moves.last_direction.into() == right_dir_felt, 'last direction is wrong'); let new_position = get!(world, caller, Position); - assert(new_position.vec.x == 11, 'position x is wrong'); - assert(new_position.vec.y == 10, 'position y is wrong'); + 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'); } } diff --git a/examples/spawn-and-move/src/mock_token.cairo b/examples/spawn-and-move/src/mock_token.cairo index 75b4c00e16..bd34916233 100644 --- a/examples/spawn-and-move/src/mock_token.cairo +++ b/examples/spawn-and-move/src/mock_token.cairo @@ -5,7 +5,6 @@ mod mock_token { fn dojo_init(world: @IWorldDispatcher) { let account: ContractAddress = get_caller_address(); - set!(world, MockToken { account: account, amount: 1000 }); } } From 7caf21a797131cdb2c01c21170781336f2113626 Mon Sep 17 00:00:00 2001 From: glihm Date: Mon, 15 Jul 2024 14:25:48 -0600 Subject: [PATCH 2/4] fix: ensure torii delete is accepting the entity_id --- crates/torii/core/src/processors/store_del_record.rs | 9 +++++---- crates/torii/core/src/sql.rs | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/torii/core/src/processors/store_del_record.rs b/crates/torii/core/src/processors/store_del_record.rs index 7917ff1093..ea71577b92 100644 --- a/crates/torii/core/src/processors/store_del_record.rs +++ b/crates/torii/core/src/processors/store_del_record.rs @@ -7,7 +7,7 @@ use starknet::providers::Provider; use tracing::info; use super::EventProcessor; -use crate::processors::{MODEL_INDEX, NUM_KEYS_INDEX}; +use crate::processors::MODEL_INDEX; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_del_record"; @@ -57,10 +57,11 @@ where "Store delete record." ); - let keys_start = NUM_KEYS_INDEX + 1; - let keys = event.data[keys_start..].to_vec(); + let entity_id = event.data[MODEL_INDEX + 1]; let entity = model.schema().await?; - db.delete_entity(keys, entity).await?; + + db.delete_entity(entity_id, entity).await?; + Ok(()) } } diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 125a050f1d..f1e7bdd001 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -271,8 +271,8 @@ impl Sql { Ok(()) } - pub async fn delete_entity(&mut self, keys: Vec, entity: Ty) -> Result<()> { - let entity_id = format!("{:#x}", poseidon_hash_many(&keys)); + pub async fn delete_entity(&mut self, entity_id: Felt, entity: Ty) -> Result<()> { + let entity_id = format!("{:#x}", entity_id); let path = vec![entity.name()]; // delete entity models data self.build_delete_entity_queries_recursive(path, &entity_id, &entity); From 3477231b97fd5e7cd8e6ca16897e5c7d6145c0a3 Mon Sep 17 00:00:00 2001 From: glihm Date: Mon, 15 Jul 2024 18:09:08 -0600 Subject: [PATCH 3/4] feat: add support into Torii for StoreUpdateRecord --- bin/torii/src/main.rs | 2 + crates/dojo-core/src/world.cairo | 3 +- crates/torii/core/src/processors/mod.rs | 2 + .../core/src/processors/store_del_record.rs | 4 +- .../src/processors/store_update_record.rs | 84 +++++++++++++++++++ crates/torii/core/src/sql.rs | 42 +++++++++- crates/torii/core/src/sql_test.rs | 75 +++++++++++++++++ 7 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 crates/torii/core/src/processors/store_update_record.rs diff --git a/bin/torii/src/main.rs b/bin/torii/src/main.rs index fd0849842f..27731e05a4 100644 --- a/bin/torii/src/main.rs +++ b/bin/torii/src/main.rs @@ -33,6 +33,7 @@ use torii_core::processors::register_model::RegisterModelProcessor; use torii_core::processors::store_del_record::StoreDelRecordProcessor; use torii_core::processors::store_set_record::StoreSetRecordProcessor; use torii_core::processors::store_transaction::StoreTransactionProcessor; +use torii_core::processors::store_update_record::StoreUpdateRecordProcessor; use torii_core::simple_broker::SimpleBroker; use torii_core::sql::Sql; use torii_core::types::Model; @@ -170,6 +171,7 @@ async fn main() -> anyhow::Result<()> { Box::new(MetadataUpdateProcessor), Box::new(StoreDelRecordProcessor), Box::new(EventMessageProcessor), + Box::new(StoreUpdateRecordProcessor), ], transaction: vec![Box::new(StoreTransactionProcessor)], ..Processors::default() diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index 6882af8e4a..74c298e62f 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -862,7 +862,8 @@ mod world { ModelIndex::MemberId(( entity_id, member_id )) => { - self._write_model_member(model_selector, entity_id, member_id, values, layout) + self._write_model_member(model_selector, entity_id, member_id, values, layout); + // TODO: here we need a new event update and see how Torii can process that. } } } diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index fcc30e0e1a..6f53b567dc 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -12,9 +12,11 @@ pub mod register_model; pub mod store_del_record; pub mod store_set_record; pub mod store_transaction; +pub mod store_update_record; const MODEL_INDEX: usize = 0; const NUM_KEYS_INDEX: usize = 1; +const ENTITY_ID_INDEX: usize = 1; #[async_trait] pub trait EventProcessor

diff --git a/crates/torii/core/src/processors/store_del_record.rs b/crates/torii/core/src/processors/store_del_record.rs index ea71577b92..6afb577a5d 100644 --- a/crates/torii/core/src/processors/store_del_record.rs +++ b/crates/torii/core/src/processors/store_del_record.rs @@ -7,7 +7,7 @@ use starknet::providers::Provider; use tracing::info; use super::EventProcessor; -use crate::processors::MODEL_INDEX; +use crate::processors::{ENTITY_ID_INDEX, MODEL_INDEX}; use crate::sql::Sql; pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_del_record"; @@ -57,7 +57,7 @@ where "Store delete record." ); - let entity_id = event.data[MODEL_INDEX + 1]; + let entity_id = event.data[ENTITY_ID_INDEX]; let entity = model.schema().await?; db.delete_entity(entity_id, entity).await?; diff --git a/crates/torii/core/src/processors/store_update_record.rs b/crates/torii/core/src/processors/store_update_record.rs new file mode 100644 index 0000000000..9fdc3a03c7 --- /dev/null +++ b/crates/torii/core/src/processors/store_update_record.rs @@ -0,0 +1,84 @@ +use anyhow::{Context, Error, Ok, Result}; +use async_trait::async_trait; +use dojo_world::contracts::model::ModelReader; +use dojo_world::contracts::naming; +use dojo_world::contracts::world::WorldContractReader; +use num_traits::ToPrimitive; +use starknet::core::types::{Event, TransactionReceiptWithBlockInfo}; +use starknet::providers::Provider; +use tracing::info; + +use super::EventProcessor; +use crate::processors::{ENTITY_ID_INDEX, MODEL_INDEX}; +use crate::sql::Sql; + +pub(crate) const LOG_TARGET: &str = "torii_core::processors::store_update_record"; + +#[derive(Default, Debug)] +pub struct StoreUpdateRecordProcessor; + +#[async_trait] +impl

EventProcessor

for StoreUpdateRecordProcessor +where + P: Provider + Send + Sync + std::fmt::Debug, +{ + fn event_key(&self) -> String { + "StoreUpdateRecord".to_string() + } + + fn validate(&self, event: &Event) -> bool { + if event.keys.len() > 1 { + info!( + target: LOG_TARGET, + event_key = %>::event_key(self), + invalid_keys = %>::event_keys_as_string(self, event), + "Invalid event keys." + ); + return false; + } + true + } + + async fn process( + &self, + _world: &WorldContractReader

, + db: &mut Sql, + _block_number: u64, + block_timestamp: u64, + _transaction_receipt: &TransactionReceiptWithBlockInfo, + event_id: &str, + event: &Event, + ) -> Result<(), Error> { + let selector = event.data[MODEL_INDEX]; + let entity_id = event.data[ENTITY_ID_INDEX]; + + let model = db.model(selector).await?; + + info!( + target: LOG_TARGET, + name = %model.name(), + entity_id = format!("{:#x}", entity_id), + "Store update record.", + ); + + let values_start = ENTITY_ID_INDEX + 1; + let values_end: usize = + values_start + event.data[values_start].to_usize().context("invalid usize")?; + + // Skip the length to only get the values as they will be deserialized. + let values = event.data[values_start + 1..=values_end].to_vec(); + + let tag = naming::get_tag(model.namespace(), model.name()); + + // Keys are read from the db, since we don't have access to them when only + // the entity id is passed. + let keys = db.get_entity_keys(entity_id, &tag).await?; + let mut keys_and_unpacked = [keys, values].concat(); + + let mut entity = model.schema().await?; + entity.deserialize(&mut keys_and_unpacked)?; + + db.set_entity(entity, event_id, block_timestamp).await?; + Ok(()) + } +} diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index f1e7bdd001..69edbc3d70 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -9,7 +9,7 @@ use dojo_world::contracts::abi::model::Layout; use dojo_world::contracts::naming::compute_selector_from_names; use dojo_world::metadata::WorldMetadata; use sqlx::pool::PoolConnection; -use sqlx::{Pool, Sqlite}; +use sqlx::{Pool, Row, Sqlite}; use starknet::core::types::{Event, Felt, InvokeTransaction, Transaction}; use starknet_crypto::poseidon_hash_many; @@ -343,6 +343,46 @@ impl Sql { } } + /// Retrieves the keys definition for a given model. + /// The key definition is currently implemented as (`name`, `type`). + pub async fn get_entity_keys_def(&self, model_tag: &str) -> Result> { + let query = sqlx::query_as::<_, (String, String)>( + "SELECT name, type FROM model_members WHERE id = ? AND key = true", + ) + .bind(model_tag); + + let mut conn: PoolConnection = self.pool.acquire().await?; + let rows: Vec<(String, String)> = query.fetch_all(&mut *conn).await?; + Ok(rows.iter().map(|(name, ty)| (name.to_string(), ty.to_string())).collect()) + } + + /// Retrieves the keys for a given entity. + /// The keys are returned in the same order as the keys definition. + pub async fn get_entity_keys(&self, entity_id: Felt, model_tag: &str) -> Result> { + let entity_id = format!("{:#x}", entity_id); + let keys_def = self.get_entity_keys_def(model_tag).await?; + + let keys_names = + keys_def.iter().map(|(name, _)| format!("external_{}", name)).collect::>(); + + let sql = format!("SELECT {} FROM [{}] WHERE id = ?", keys_names.join(", "), model_tag); + let query = sqlx::query(sql.as_str()).bind(entity_id); + + let mut conn: PoolConnection = self.pool.acquire().await?; + + let mut keys: Vec = vec![]; + let result = query.fetch_all(&mut *conn).await?; + + for row in result { + for (i, _) in row.columns().iter().enumerate() { + let value: String = row.try_get(i)?; + keys.push(Felt::from_hex(&value)?); + } + } + + Ok(keys) + } + pub async fn entity(&self, model: String, key: Felt) -> Result> { let query = sqlx::query_as::<_, (i32, String, String)>("SELECT * FROM ? WHERE id = ?") .bind(model) diff --git a/crates/torii/core/src/sql_test.rs b/crates/torii/core/src/sql_test.rs index b0eae334a1..f3f61bd2bf 100644 --- a/crates/torii/core/src/sql_test.rs +++ b/crates/torii/core/src/sql_test.rs @@ -297,6 +297,81 @@ async fn test_load_from_remote_del() { db.execute().await.unwrap(); } +#[tokio::test(flavor = "multi_thread")] +async fn test_get_entity_keys() { + let options = + SqliteConnectOptions::from_str("sqlite::memory:").unwrap().create_if_missing(true); + let pool = SqlitePoolOptions::new().max_connections(5).connect_with(options).await.unwrap(); + sqlx::migrate!("../migrations").run(&pool).await.unwrap(); + + let setup = CompilerTestSetup::from_examples("../../dojo-core", "../../../examples/"); + let config = setup.build_test_config("spawn-and-move", Profile::DEV); + + let ws = scarb::ops::read_workspace(config.manifest_path(), &config).unwrap(); + + let sequencer = KatanaRunner::new().expect("Failed to start runner."); + let account = sequencer.account(0); + + let migration_output = migration::migrate( + &ws, + None, + sequencer.url().to_string(), + &account, + "dojo_examples", + false, + TxnConfig::init_wait(), + None, + ) + .await + .unwrap() + .unwrap(); + + // spawn + account + .execute_v1(vec![Call { + to: migration_output + .contracts + .first() + .expect("shouldn't be empty") + .as_ref() + .expect("should be deployed") + .contract_address, + selector: get_selector_from_name("spawn").unwrap(), + calldata: vec![], + }]) + .send_with_cfg(&TxnConfig::init_wait()) + .await + .unwrap(); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let world_reader = WorldContractReader::new(migration_output.world_address, account.provider()); + + let mut db = Sql::new( + pool.clone(), + world_reader.address, + account + .provider() + .get_class_hash_at(BlockId::Tag(BlockTag::Pending), world_reader.address) + .await + .unwrap(), + ) + .await + .unwrap(); + + let _ = bootstrap_engine(world_reader, db.clone(), account.provider()).await; + + let keys = db.get_entity_keys_def("dojo_examples-Moves").await.unwrap(); + assert_eq!(keys, vec![("player".to_string(), "ContractAddress".to_string()),]); + + let entity_id = poseidon_hash_many(&[account.address()]); + + let keys = db.get_entity_keys(entity_id, "dojo_examples-Moves").await.unwrap(); + assert_eq!(keys, vec![account.address()]); + + db.execute().await.unwrap(); +} + /// Count the number of rows in a table. /// /// # Arguments From acc31a707507b367045de944899734195cd6ed43 Mon Sep 17 00:00:00 2001 From: glihm Date: Mon, 15 Jul 2024 19:25:19 -0600 Subject: [PATCH 4/4] fix: fix tests --- crates/dojo-core/src/world.cairo | 2 +- .../manifests/dev/base/dojo-world.toml | 4 ++-- examples/spawn-and-move/Scarb.toml | 2 +- .../manifests/dev/base/dojo-world.toml | 4 ++-- .../manifests/dev/deployment/manifest.json | 16 ++++++++-------- .../manifests/dev/deployment/manifest.toml | 16 ++++++++-------- .../manifests/release/base/dojo-world.toml | 4 ++-- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/crates/dojo-core/src/world.cairo b/crates/dojo-core/src/world.cairo index 74c298e62f..0c88c9ac15 100644 --- a/crates/dojo-core/src/world.cairo +++ b/crates/dojo-core/src/world.cairo @@ -863,7 +863,7 @@ mod world { entity_id, member_id )) => { self._write_model_member(model_selector, entity_id, member_id, values, layout); - // TODO: here we need a new event update and see how Torii can process that. + // TODO: here we need a new event update and see how Torii can process that. } } } diff --git a/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml b/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml index 850343b0cd..e79e888f02 100644 --- a/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml +++ b/crates/dojo-lang/src/manifest_test_data/compiler_cairo/manifests/dev/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" -original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" +original_class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" abi = "manifests/dev/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world" diff --git a/examples/spawn-and-move/Scarb.toml b/examples/spawn-and-move/Scarb.toml index 7eddc0150a..39f63432ab 100644 --- a/examples/spawn-and-move/Scarb.toml +++ b/examples/spawn-and-move/Scarb.toml @@ -43,7 +43,7 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0x6162896d1d7ab204c7ccac6dd5f8e9e7c25ecd5ae4fcb4ad32e57786bb46e03" private_key = "0x1800000000300000180000000000030000000000003006001800006600" -world_address = "0x2f7a4447d78fa066cead76981c04deb1fc9cb3c700bd246b697425e900641a0" +world_address = "0x5377ecb8b7b6ce3f17daf9064a5f5ee7f6642c3e72579b45478a828007db355" [profile.release.tool.dojo] # for more info on how `merge-strategy` works see: diff --git a/examples/spawn-and-move/manifests/dev/base/dojo-world.toml b/examples/spawn-and-move/manifests/dev/base/dojo-world.toml index 850343b0cd..e79e888f02 100644 --- a/examples/spawn-and-move/manifests/dev/base/dojo-world.toml +++ b/examples/spawn-and-move/manifests/dev/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" -original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" +original_class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" abi = "manifests/dev/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world" diff --git a/examples/spawn-and-move/manifests/dev/deployment/manifest.json b/examples/spawn-and-move/manifests/dev/deployment/manifest.json index f3816370a6..1fd7c0ef24 100644 --- a/examples/spawn-and-move/manifests/dev/deployment/manifest.json +++ b/examples/spawn-and-move/manifests/dev/deployment/manifest.json @@ -1,8 +1,8 @@ { "world": { "kind": "WorldContract", - "class_hash": "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44", - "original_class_hash": "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44", + "class_hash": "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557", + "original_class_hash": "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557", "abi": [ { "type": "impl", @@ -1189,8 +1189,8 @@ ] } ], - "address": "0x2f7a4447d78fa066cead76981c04deb1fc9cb3c700bd246b697425e900641a0", - "transaction_hash": "0x2587e7362c8ca0f8478bf1256e8fdd40b85f70adfb22d886293c21da653b3e6", + "address": "0x5377ecb8b7b6ce3f17daf9064a5f5ee7f6642c3e72579b45478a828007db355", + "transaction_hash": "0x59c745b80871acdab7ad1c29703cac9aa949f4326b32037943512adc0a19ab2", "block_number": 3, "seed": "dojo_examples", "metadata": { @@ -1210,7 +1210,7 @@ "contracts": [ { "kind": "DojoContract", - "address": "0x3f6ef508b9837b327ae5704e719094eae81c1f0086cec8e5b83e25b5e9d071b", + "address": "0x5c92c8995272a6ae073392c6878fe80cd71ae0be4931bce75f3dbfe24c208a8", "class_hash": "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be", "original_class_hash": "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", @@ -1641,7 +1641,7 @@ }, { "kind": "DojoContract", - "address": "0x26ed07397dc7462adc94b2b070fec2bfa2b0e36aaa6bcafadc8c8cef8e46020", + "address": "0x44e1b43ee34f816374612ae2df4a668e76a1aa723de8d7b67166e10a7225112", "class_hash": "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674", "original_class_hash": "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", @@ -1878,7 +1878,7 @@ }, { "kind": "DojoContract", - "address": "0x2470881191edf8260e9f6cec7244e117ea2d0f5d48c76ec9e990222c2b7a7a6", + "address": "0x6840cf5bebab9c134ddc4d34f6b021684610d7b83d663fc46e2e10d3569ea15", "class_hash": "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028", "original_class_hash": "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", @@ -2097,7 +2097,7 @@ }, { "kind": "DojoContract", - "address": "0x7f21ab47087c7fc6e9dae445cf2c7d8e0b8c651cb850d81a7e8eccc636f1b75", + "address": "0x512bcb4058b044f689269d8a1c23e653c04baee2029f7e5d3f4ec053e6c5c91", "class_hash": "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7", "original_class_hash": "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7", "base_class_hash": "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4", diff --git a/examples/spawn-and-move/manifests/dev/deployment/manifest.toml b/examples/spawn-and-move/manifests/dev/deployment/manifest.toml index e2cb2f190c..513b453b78 100644 --- a/examples/spawn-and-move/manifests/dev/deployment/manifest.toml +++ b/examples/spawn-and-move/manifests/dev/deployment/manifest.toml @@ -1,10 +1,10 @@ [world] kind = "WorldContract" -class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" -original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" +original_class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" abi = "manifests/dev/deployment/abis/dojo-world.json" -address = "0x2f7a4447d78fa066cead76981c04deb1fc9cb3c700bd246b697425e900641a0" -transaction_hash = "0x2587e7362c8ca0f8478bf1256e8fdd40b85f70adfb22d886293c21da653b3e6" +address = "0x5377ecb8b7b6ce3f17daf9064a5f5ee7f6642c3e72579b45478a828007db355" +transaction_hash = "0x59c745b80871acdab7ad1c29703cac9aa949f4326b32037943512adc0a19ab2" block_number = 3 seed = "dojo_examples" manifest_name = "dojo-world" @@ -23,7 +23,7 @@ manifest_name = "dojo-base" [[contracts]] kind = "DojoContract" -address = "0x3f6ef508b9837b327ae5704e719094eae81c1f0086cec8e5b83e25b5e9d071b" +address = "0x5c92c8995272a6ae073392c6878fe80cd71ae0be4931bce75f3dbfe24c208a8" class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" original_class_hash = "0x19d49d8d67dd2e6f73c93eb886105d445ac6983a49950831a325872f8c662be" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" @@ -40,7 +40,7 @@ manifest_name = "dojo_examples-actions-40b6994c" [[contracts]] kind = "DojoContract" -address = "0x26ed07397dc7462adc94b2b070fec2bfa2b0e36aaa6bcafadc8c8cef8e46020" +address = "0x44e1b43ee34f816374612ae2df4a668e76a1aa723de8d7b67166e10a7225112" class_hash = "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674" original_class_hash = "0x14b3096b82a761f63dd47277c2b5ac18925dea43586418483939a2f1f57f674" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" @@ -54,7 +54,7 @@ manifest_name = "dojo_examples-dungeon-6620e0e6" [[contracts]] kind = "DojoContract" -address = "0x2470881191edf8260e9f6cec7244e117ea2d0f5d48c76ec9e990222c2b7a7a6" +address = "0x6840cf5bebab9c134ddc4d34f6b021684610d7b83d663fc46e2e10d3569ea15" class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" original_class_hash = "0x761d18a3557d98b3962ebb2c9ddae89ad586ce81de7e86c5fd1e1b4f9d0028" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" @@ -68,7 +68,7 @@ manifest_name = "dojo_examples-mock_token-31599eb2" [[contracts]] kind = "DojoContract" -address = "0x7f21ab47087c7fc6e9dae445cf2c7d8e0b8c651cb850d81a7e8eccc636f1b75" +address = "0x512bcb4058b044f689269d8a1c23e653c04baee2029f7e5d3f4ec053e6c5c91" class_hash = "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7" original_class_hash = "0x479bfb12dcba5398d77303e7a665fc3fedb16f2d7f9cb1f5d7e2beb3b7e2ba7" base_class_hash = "0x26a4f5d2d9638877a2648297339275df5eaab0adb3cdf0010887c2dbf2be4" diff --git a/examples/spawn-and-move/manifests/release/base/dojo-world.toml b/examples/spawn-and-move/manifests/release/base/dojo-world.toml index 75eaba9b51..5891e40ff4 100644 --- a/examples/spawn-and-move/manifests/release/base/dojo-world.toml +++ b/examples/spawn-and-move/manifests/release/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" -original_class_hash = "0xcee5f3a6d253f5afc741d10cd0c482767907e473b2cba27a64bdce05934a44" +class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" +original_class_hash = "0x26a8f65e0b0ba2a6d2f9e93d4f72f75eabebb853ea7413e134b3902ea352557" abi = "manifests/release/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world"