Skip to content

Commit

Permalink
feat(dojo-core): add world test capabilities (#2323)
Browse files Browse the repository at this point in the history
* feat: add interfaces for testing set into the world

* fix: update tests

* fix: internalize the world test for better devX during testing

* feat: add qualified path to DojoModel manifest
  • Loading branch information
glihm authored Aug 21, 2024
1 parent 8db91e7 commit 0955638
Show file tree
Hide file tree
Showing 37 changed files with 312 additions and 80 deletions.
8 changes: 7 additions & 1 deletion crates/dojo-core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub mod model {
deploy_and_get_metadata
};

#[cfg(target: "test")]
pub use model::{ModelTest, ModelEntityTest};

pub mod metadata;
pub use metadata::{ResourceMetadata, ResourceMetadataTrait, resource_metadata};
pub(crate) use metadata::{initial_address, initial_class_hash};
Expand Down Expand Up @@ -57,11 +60,14 @@ pub mod world {
mod world_contract;
pub use world_contract::{
world, IWorld, IWorldDispatcher, IWorldDispatcherTrait, IWorldProvider,
IWorldProviderDispatcher, IWorldProviderDispatcherTrait, Resource
IWorldProviderDispatcher, IWorldProviderDispatcherTrait, Resource,
};
pub(crate) use world_contract::{
IUpgradeableWorld, IUpgradeableWorldDispatcher, IUpgradeableWorldDispatcherTrait
};

#[cfg(target: "test")]
pub use world_contract::{IWorldTest, IWorldTestDispatcher, IWorldTestDispatcherTrait};
}

#[cfg(test)]
Expand Down
12 changes: 12 additions & 0 deletions crates/dojo-core/src/model/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,15 @@ pub fn deploy_and_get_metadata(
let namespace_hash = model.namespace_hash();
Result::Ok((contract_address, name, selector, namespace, namespace_hash))
}

#[cfg(target: "test")]
pub trait ModelTest<T> {
fn set_test(self: @T, world: IWorldDispatcher);
fn delete_test(self: @T, world: IWorldDispatcher);
}

#[cfg(target: "test")]
pub trait ModelEntityTest<T> {
fn update_test(self: @T, world: IWorldDispatcher);
fn delete_test(self: @T, world: IWorldDispatcher);
}
16 changes: 12 additions & 4 deletions crates/dojo-core/src/utils/test.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,19 @@ pub fn deploy_with_world_address(class_hash: felt252, world: IWorldDispatcher) -
deploy_contract(class_hash, [world.contract_address.into()].span())
}

/// Spawns a test world registering namespaces and models.
///
/// # Arguments
///
/// * `namespaces` - Namespaces to register.
/// * `models` - Models to register.
///
/// # Returns
///
/// * World dispatcher
pub fn spawn_test_world(namespaces: Span<ByteArray>, models: Span<felt252>) -> IWorldDispatcher {
let salt = core::testing::get_available_gas();

// deploy world
let (world_address, _) = deploy_syscall(
world::TEST_CLASS_HASH.try_into().unwrap(),
salt.into(),
Expand All @@ -54,13 +63,13 @@ pub fn spawn_test_world(namespaces: Span<ByteArray>, models: Span<felt252>) -> I

let world = IWorldDispatcher { contract_address: world_address };

// register namespace
// Register all namespaces to ensure correct registration of models.
let mut namespaces = namespaces;
while let Option::Some(namespace) = namespaces.pop_front() {
world.register_namespace(namespace.clone());
};

// register models
// Register all models.
let mut index = 0;
loop {
if index == models.len() {
Expand All @@ -73,7 +82,6 @@ pub fn spawn_test_world(namespaces: Span<ByteArray>, models: Span<felt252>) -> I
world
}


#[derive(Drop)]
pub struct GasCounter {
pub start: u128,
Expand Down
159 changes: 111 additions & 48 deletions crates/dojo-core/src/world/world_contract.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ pub trait IWorld<T> {
fn revoke_writer(ref self: T, resource: felt252, contract: ContractAddress);
}

#[starknet::interface]
#[cfg(target: "test")]
pub trait IWorldTest<T> {
fn set_entity_test(
ref self: T,
model_selector: felt252,
index: ModelIndex,
values: Span<felt252>,
layout: Layout
);

fn delete_entity_test(ref self: T, model_selector: felt252, index: ModelIndex, layout: Layout);
}

#[starknet::interface]
pub trait IUpgradeableWorld<T> {
fn upgrade(ref self: T, new_class_hash: ClassHash);
Expand Down Expand Up @@ -303,6 +317,26 @@ pub mod world {
EventEmitter::emit(ref self, WorldSpawned { address: get_contract_address(), creator });
}

#[cfg(target: "test")]
#[abi(embed_v0)]
impl WorldTestImpl of super::IWorldTest<ContractState> {
fn set_entity_test(
ref self: ContractState,
model_selector: felt252,
index: ModelIndex,
values: Span<felt252>,
layout: Layout
) {
self.set_entity_internal(model_selector, index, values, layout);
}

fn delete_entity_test(
ref self: ContractState, model_selector: felt252, index: ModelIndex, layout: Layout
) {
self.delete_entity_internal(model_selector, index, layout);
}
}

#[abi(embed_v0)]
impl World of IWorld<ContractState> {
/// Returns the metadata of the resource.
Expand Down Expand Up @@ -762,36 +796,7 @@ pub mod world {
layout: Layout
) {
self.assert_model_write_access(model_selector);

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_selector
)) => {
self
.write_model_member(
model_selector, entity_id, member_selector, values, layout
);
EventEmitter::emit(
ref self,
StoreUpdateMember {
table: model_selector, entity_id, member_selector, values
}
);
}
}
self.set_entity_internal(model_selector, index, values, layout);
}

/// Deletes a record/entity of a model..
Expand All @@ -806,23 +811,7 @@ pub mod world {
ref self: ContractState, model_selector: felt252, index: ModelIndex, layout: Layout
) {
self.assert_model_write_access(model_selector);

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); }
}
self.delete_entity_internal(model_selector, index, layout);
}

/// Gets the base contract class hash.
Expand Down Expand Up @@ -934,7 +923,7 @@ pub mod world {
}

#[generate_trait]
impl Self of SelfTrait {
impl SelfImpl of SelfTrait {
#[inline(always)]
/// Indicates if the caller is the owner of the world.
fn is_caller_world_owner(self: @ContractState) -> bool {
Expand Down Expand Up @@ -1095,6 +1084,80 @@ pub mod world {
}
}

/// 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_internal(
ref self: ContractState,
model_selector: felt252,
index: ModelIndex,
values: Span<felt252>,
layout: Layout
) {
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_selector
)) => {
self
.write_model_member(
model_selector, entity_id, member_selector, values, layout
);
EventEmitter::emit(
ref self,
StoreUpdateMember {
table: model_selector, entity_id, member_selector, values
}
);
}
}
}

/// Deletes an entity for the given model, setting all the values to 0 in the given layout.
///
/// # Arguments
///
/// * `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_internal(
ref self: ContractState, model_selector: felt252, index: ModelIndex, layout: 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); }
}
}

/// Write a new entity.
///
/// # Arguments
Expand Down
1 change: 1 addition & 0 deletions crates/dojo-lang/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ fn get_dojo_model_artifacts(
abi: None,
members: model.members.clone(),
original_class_hash: class_hash,
qualified_path,
},
naming::get_filename_from_tag(&tag),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
kind = "Class"
class_hash = "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2"
original_class_hash = "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2"
class_hash = "0x3715f072aa1c07be724249fcda8b0322687f6c5c585eebc4402d162649c707c"
original_class_hash = "0x3715f072aa1c07be724249fcda8b0322687f6c5c585eebc4402d162649c707c"
abi = "manifests/dev/base/abis/dojo-world.json"
tag = "dojo-world"
manifest_name = "dojo-world"
62 changes: 62 additions & 0 deletions crates/dojo-lang/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,34 @@ pub impl $type_name$ModelEntityImpl of dojo::model::ModelEntity<$type_name$Entit
}
}
#[cfg(target: \"test\")]
pub impl $type_name$ModelEntityTestImpl of dojo::model::ModelEntityTest<$type_name$Entity> {
fn update_test(self: @$type_name$Entity, world: dojo::world::IWorldDispatcher) {
let world_test = dojo::world::IWorldTestDispatcher { contract_address: \
world.contract_address };
dojo::world::IWorldTestDispatcherTrait::set_entity_test(
world_test,
dojo::model::Model::<$type_name$>::selector(),
dojo::model::ModelIndex::Id(self.id()),
self.values(),
dojo::model::Model::<$type_name$>::layout()
);
}
fn delete_test(self: @$type_name$Entity, world: dojo::world::IWorldDispatcher) {
let world_test = dojo::world::IWorldTestDispatcher { contract_address: \
world.contract_address };
dojo::world::IWorldTestDispatcherTrait::delete_entity_test(
world_test,
dojo::model::Model::<$type_name$>::selector(),
dojo::model::ModelIndex::Id(self.id()),
dojo::model::Model::<$type_name$>::layout()
);
}
}
pub impl $type_name$ModelImpl of dojo::model::Model<$type_name$> {
fn get(world: dojo::world::IWorldDispatcher, keys: Span<felt252>) -> $type_name$ {
let mut values = dojo::world::IWorldDispatcherTrait::entity(
Expand Down Expand Up @@ -618,6 +646,40 @@ pub impl $type_name$ModelImpl of dojo::model::Model<$type_name$> {
}
}
#[cfg(target: \"test\")]
pub impl $type_name$ModelTestImpl of dojo::model::ModelTest<$type_name$> {
fn set_test(
self: @$type_name$,
world: dojo::world::IWorldDispatcher
) {
let world_test = dojo::world::IWorldTestDispatcher { contract_address: \
world.contract_address };
dojo::world::IWorldTestDispatcherTrait::set_entity_test(
world_test,
dojo::model::Model::<$type_name$>::selector(),
dojo::model::ModelIndex::Keys(dojo::model::Model::<$type_name$>::keys(self)),
dojo::model::Model::<$type_name$>::values(self),
dojo::model::Model::<$type_name$>::layout()
);
}
fn delete_test(
self: @$type_name$,
world: dojo::world::IWorldDispatcher
) {
let world_test = dojo::world::IWorldTestDispatcher { contract_address: \
world.contract_address };
dojo::world::IWorldTestDispatcherTrait::delete_entity_test(
world_test,
dojo::model::Model::<$type_name$>::selector(),
dojo::model::ModelIndex::Keys(dojo::model::Model::<$type_name$>::keys(self)),
dojo::model::Model::<$type_name$>::layout()
);
}
}
#[starknet::interface]
pub trait I$contract_name$<T> {
fn ensure_abi(self: @T, model: $type_name$);
Expand Down
1 change: 1 addition & 0 deletions crates/dojo-world/src/manifest/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub struct DojoModel {
pub original_class_hash: Felt,
pub abi: Option<AbiFormat>,
pub tag: String,
pub qualified_path: String,
}

#[serde_as]
Expand Down
2 changes: 1 addition & 1 deletion crates/sozo/ops/src/tests/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async fn test_model_ops() {
)
.await
.unwrap(),
Felt::from_hex("0x295714e4a1b077394d20ce9abe5c1d08efb47c6143533ad096f94496c8ba3ba")
Felt::from_hex("0x604735fb6510c558ba3ae21972fcbdb1b4234bedcbc990910bd7efd194e7db3")
.unwrap()
);

Expand Down
Loading

1 comment on commit 0955638

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.30.

Benchmark suite Current: 0955638 Previous: 8db91e7 Ratio
Concurrent.Simulate/Blockifier.1 6149349 ns/iter (± 611830) 3874082 ns/iter (± 256590) 1.59

This comment was automatically generated by workflow using github-action-benchmark.

CC: @kariy @glihm @tarrencev

Please sign in to comment.