Skip to content

Commit

Permalink
Handle get()/set() get_field_name()/set_field_name() functions
Browse files Browse the repository at this point in the history
  • Loading branch information
remybar committed Jul 15, 2024
1 parent 307653b commit b8b2615
Show file tree
Hide file tree
Showing 38 changed files with 1,647 additions and 462 deletions.
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
31 changes: 27 additions & 4 deletions crates/dojo-core/src/model.cairo
Original file line number Diff line number Diff line change
@@ -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<T> {
fn id(self: @T) -> felt252;
fn values(self: @T) -> Span<felt252>;
fn from_values(entity_id: felt252, values: Span<felt252>) -> 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<felt252>;
fn set_member(self: @T, world: IWorldDispatcher, member_id: felt252, values: Span<felt252>,);
}

trait Model<T> {
fn entity(
world: IWorldDispatcher, keys: Span<felt252>, layout: dojo::database::introspect::Layout
) -> T;
fn get(world: IWorldDispatcher, keys: Span<felt252>) -> 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<felt252>, member_id: felt252,
) -> Span<felt252>;

fn set_member(self: @T, world: IWorldDispatcher, member_id: felt252, values: Span<felt252>,);

/// Returns the name of the model as it was written in Cairo code.
fn name() -> ByteArray;
Expand All @@ -25,6 +47,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
198 changes: 198 additions & 0 deletions crates/dojo-core/src/model_test.cairo
Original file line number Diff line number Diff line change
@@ -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::<FooEntity>::values(@mvalues);
assert!(expected_values == values);
}

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

let model_entity = dojo::model::ModelEntity::<FooEntity>::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::<FooEntity>::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<felt252> = 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::<Foo>::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);
}

65 changes: 56 additions & 9 deletions crates/dojo-core/src/resource_metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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>()
Expand All @@ -24,12 +25,8 @@ struct ResourceMetadata {
}

impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {
fn entity(
world: dojo::world::IWorldDispatcher,
keys: Span<felt252>,
layout: dojo::database::introspect::Layout
) -> ResourceMetadata {
let values = world.entity(Self::selector(), keys, layout);
fn get(world: dojo::world::IWorldDispatcher, keys: Span<felt252>) -> 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);
Expand All @@ -45,6 +42,51 @@ impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {
core::option::OptionTrait::<ResourceMetadata>::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<felt252>, member_id: felt252
) -> Span<felt252> {
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<felt252>
) {
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"
Expand Down Expand Up @@ -74,11 +116,16 @@ impl ResourceMetadataModel of dojo::model::Model<ResourceMetadata> {
}

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)]
Expand Down
Loading

0 comments on commit b8b2615

Please sign in to comment.