Skip to content

Commit

Permalink
handle entity ID
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed Jul 8, 2024
1 parent 7c09e4a commit 52051d4
Show file tree
Hide file tree
Showing 25 changed files with 3,955 additions and 192 deletions.
2 changes: 1 addition & 1 deletion crates/dojo-core/src/base_test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ mod invalid_model {
fn selector(self: @ContractState) -> felt252 {
// NOTE: Need to update this value if address changes
// Pre-computed address of a contract deployed through the world.
0x21b19f95ff0f382a069dc7034f95584b300133665ee506789c76ba729e42b66
0x562c876783650ced854d9571abba1259cc28b5256fa0994f3d1c4cd2b6c4488
}

fn namespace(self: @ContractState) -> ByteArray {
Expand Down
2 changes: 2 additions & 0 deletions crates/dojo-core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod database;
mod database_test;
mod interfaces;
mod model;
#[cfg(test)]
mod model_test;
mod contract;
mod packing;
#[cfg(test)]
Expand Down
22 changes: 22 additions & 0 deletions crates/dojo-core/src/model.cairo
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
use dojo::world::IWorldDispatcher;
use starknet::SyscallResult;

/// Trait that is implemented at Cairo level for each struct that is a model.
trait ModelValues<T> {
fn values(self: @T) -> Span<felt252>;
fn from_values(values: Span<felt252>) -> T;
fn get(world: IWorldDispatcher, id: felt252) -> T;
fn update(self: @T, world: IWorldDispatcher, id: felt252);
fn delete(self: @T, world: IWorldDispatcher, id: felt252);
}

trait Model<T> {
fn entity(
world: IWorldDispatcher, keys: Span<felt252>, layout: dojo::database::introspect::Layout
) -> T;

fn set_entity(
world: IWorldDispatcher,
keys: Span<felt252>,
values: Span<felt252>,
layout: dojo::database::introspect::Layout
);

fn delete_entity(
world: IWorldDispatcher, keys: Span<felt252>, layout: dojo::database::introspect::Layout
);


/// Returns the name of the model as it was written in Cairo code.
fn name() -> ByteArray;

Expand All @@ -25,6 +46,7 @@ trait Model<T> {
fn name_hash() -> felt252;
fn namespace_hash() -> felt252;

fn entity_id(self: @T) -> felt252;
fn keys(self: @T) -> Span<felt252>;
fn values(self: @T) -> Span<felt252>;
fn layout() -> dojo::database::introspect::Layout;
Expand Down
126 changes: 126 additions & 0 deletions crates/dojo-core/src/model_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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_values() {
let mvalues = FooValues { v1: 3, v2: 4 };
let expected_values = array![3, 4].span();

let values = FooModelValues::values(@mvalues);
assert!(expected_values == values);
}

#[test]
fn test_from_values() {
let values = array![3, 4].span();

let model_values = FooModelValues::from_values(values);
assert!(model_values.v1 == 3 && model_values.v2 == 4);
}

#[test]
#[should_panic(expected: "ModelValues `FooValues`: deserialization failed.")]
fn test_from_values_bad_data() {
let values = array![3].span();
let _ = FooModelValues::from_values(values);
}

#[test]
fn test_get_and_update_values() {
let world = deploy_world();
world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap());

let entity = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
let entity_id = entity.entity_id();
dojo::model::Model::<
Foo
>::set_entity(world, entity.keys(), entity.values(), entity.instance_layout());

let mut entity_values = FooModelValues::get(world, entity_id);
assert!(entity.v1 == entity_values.v1 && entity.v2 == entity_values.v2);

entity_values.v1 = 12;
entity_values.v2 = 18;

entity_values.update(world, entity_id);

let read_values = FooModelValues::get(world, entity_id);
assert!(read_values.v1 == entity_values.v1 && read_values.v2 == entity_values.v2);
}

#[test]
fn test_delete_values() {
let world = deploy_world();
world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap());

let entity = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
let entity_id = entity.entity_id();
dojo::model::Model::<
Foo
>::set_entity(world, entity.keys(), entity.values(), entity.instance_layout());

let mut entity_values = FooModelValues::get(world, entity_id);
entity_values.delete(world, entity_id);

let read_values = FooModelValues::get(world, entity_id);
assert!(read_values.v1 == 0 && read_values.v2 == 0);
}

#[test]
fn test_entity_and_set_entity() {
let world = deploy_world();
world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap());

let entity = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
dojo::model::Model::<
Foo
>::set_entity(world, entity.keys(), entity.values(), entity.instance_layout());
let read_entity = dojo::model::Model::<
Foo
>::entity(world, entity.keys(), entity.instance_layout());

assert!(
entity.k1 == read_entity.k1
&& entity.k2 == read_entity.k2
&& entity.v1 == read_entity.v1
&& entity.v2 == read_entity.v2
);
}

#[test]
fn test_delete_entity() {
let world = deploy_world();
world.register_model(foo::TEST_CLASS_HASH.try_into().unwrap());

let entity = Foo { k1: 1, k2: 2, v1: 3, v2: 4 };
dojo::model::Model::<
Foo
>::set_entity(world, entity.keys(), entity.values(), entity.instance_layout());
dojo::model::Model::<Foo>::delete_entity(world, entity.keys(), entity.instance_layout());

let read_entity = dojo::model::Model::<
Foo
>::entity(world, entity.keys(), entity.instance_layout());
assert!(
read_entity.k1 == entity.k1
&& read_entity.k2 == entity.k2
&& read_entity.v1 == 0
&& read_entity.v2 == 0
);
}
25 changes: 25 additions & 0 deletions crates/dojo-core/src/resource_metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {
core::option::OptionTrait::<ResourceMetadata>::unwrap(entity)
}

fn set_entity(
world: dojo::world::IWorldDispatcher,
keys: Span<felt252>,
values: Span<felt252>,
layout: dojo::database::introspect::Layout
) {
dojo::world::IWorldDispatcherTrait::set_entity(
world, Self::selector(), keys, values, layout
);
}

fn delete_entity(
world: dojo::world::IWorldDispatcher,
keys: Span<felt252>,
layout: dojo::database::introspect::Layout
) {
dojo::world::IWorldDispatcherTrait::delete_entity(world, Self::selector(), keys, layout);
}


#[inline(always)]
fn name() -> ByteArray {
"ResourceMetadata"
Expand Down Expand Up @@ -81,6 +101,11 @@ impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {
dojo::utils::hash(@Self::namespace())
}

#[inline(always)]
fn entity_id(self: @ResourceMetadata) -> felt252 {
poseidon::poseidon_hash_span(self.keys())
}

#[inline(always)]
fn keys(self: @ResourceMetadata) -> Span<felt252> {
let mut serialized = core::array::ArrayTrait::new();
Expand Down
13 changes: 13 additions & 0 deletions crates/dojo-core/src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ 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>) -> felt252 {
poseidon::poseidon_hash_span(keys)
}
Loading

0 comments on commit 52051d4

Please sign in to comment.