Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate contracts for Dojo events #2431

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bin/sozo/tests/test_migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ async fn migrate_dry_run() {

assert!(output.contains("Migration Strategy"));
assert!(output.contains("# Base Contract"));
assert!(output.contains("# Models (10)"));
assert!(output.contains("# Models (8)"));
assert!(output.contains("# Events (2)"));
assert!(output.contains("# World"));
assert!(output.contains("# Contracts (4)"));
}
Expand Down
4 changes: 2 additions & 2 deletions crates/dojo-bindgen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ fn filter_model_tokens(tokens: &TokenizedAbi) -> TokenizedAbi {
// All types from introspect module can also be removed as the clients does not rely on them.
// Events are also always empty at model contract level.
fn skip_token(token: &Token) -> bool {
if token.type_path().starts_with("dojo::model::introspect") {
if token.type_path().starts_with("dojo::meta::introspect") {
return true;
}

Expand Down Expand Up @@ -246,7 +246,7 @@ mod tests {
)
.unwrap();

assert_eq!(data.models.len(), 10);
assert_eq!(data.models.len(), 8);

assert_eq!(data.world.name, "dojo_example");

Expand Down
65 changes: 65 additions & 0 deletions crates/dojo-core/src/event/event.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use starknet::SyscallResult;

use dojo::meta::Layout;
use dojo::meta::introspect::Ty;
use dojo::world::IWorldDispatcher;

pub trait Event<T> {
fn name() -> ByteArray;
fn namespace() -> ByteArray;
fn tag() -> ByteArray;

fn version() -> u8;

fn selector() -> felt252;
fn instance_selector(self: @T) -> felt252;

fn name_hash() -> felt252;
fn namespace_hash() -> felt252;

fn layout() -> Layout;
fn schema(self: @T) -> Ty;

fn packed_size() -> Option<usize>;
fn unpacked_size() -> Option<usize>;
}

#[starknet::interface]
pub trait IEvent<T> {
fn name(self: @T) -> ByteArray;
fn namespace(self: @T) -> ByteArray;
fn tag(self: @T) -> ByteArray;

fn version(self: @T) -> u8;

fn selector(self: @T) -> felt252;
fn name_hash(self: @T) -> felt252;
fn namespace_hash(self: @T) -> felt252;

fn packed_size(self: @T) -> Option<usize>;
fn unpacked_size(self: @T) -> Option<usize>;

fn layout(self: @T) -> Layout;
fn schema(self: @T) -> Ty;
}

/// Deploys an event with the given [`ClassHash`] and retrieves it's name.
/// Currently, the event is expected to already be declared by `sozo`.
///
/// # Arguments
///
/// * `salt` - A salt used to uniquely deploy the event.
/// * `class_hash` - Class Hash of the event.
pub fn deploy_and_get_metadata(
salt: felt252, class_hash: starknet::ClassHash
) -> SyscallResult<(starknet::ContractAddress, ByteArray, felt252, ByteArray, felt252)> {
let (contract_address, _) = starknet::syscalls::deploy_syscall(
class_hash, salt, [].span(), false,
)?;
let event = IEventDispatcher { contract_address };
let name = event.name();
let selector = event.selector();
let namespace = event.namespace();
let namespace_hash = event.namespace_hash();
Result::Ok((contract_address, name, selector, namespace, namespace_hash))
}
16 changes: 14 additions & 2 deletions crates/dojo-core/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@ pub mod contract {
pub mod upgradeable;
}

pub mod model {
pub mod meta {
pub mod introspect;
pub mod layout;
pub use layout::{Layout, FieldLayout};
}

pub mod event {
pub mod event;
pub use event::{
Event, IEvent, IEventDispatcher, IEventDispatcherTrait, deploy_and_get_metadata
};
}

pub mod model {
pub mod model;
pub use model::{
Model, ModelIndex, ModelEntity, IModel, IModelDispatcher, IModelDispatcherTrait,
Expand Down Expand Up @@ -72,8 +81,11 @@ pub mod world {

#[cfg(test)]
mod tests {
mod model {
mod meta {
mod introspect;
}

mod model {
mod model;
}
mod storage {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dojo::model::{Layout, FieldLayout};
use dojo::meta::{Layout, FieldLayout};

#[derive(Copy, Drop, Serde)]
pub enum Ty {
Expand Down Expand Up @@ -240,9 +240,9 @@ pub impl Introspect_option<T, +Introspect<T>> of Introspect<Option<T>> {
fn layout() -> Layout {
Layout::Enum(
[
dojo::model::FieldLayout { // Some
dojo::meta::FieldLayout { // Some
selector: 0, layout: Introspect::<T>::layout() },
dojo::model::FieldLayout { // None
dojo::meta::FieldLayout { // None
selector: 1, layout: Layout::Fixed([].span()) },
].span()
)
Expand Down
24 changes: 12 additions & 12 deletions crates/dojo-core/src/model/metadata.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use core::byte_array::ByteArray;
use core::poseidon::poseidon_hash_span;
use core::serde::Serde;

use dojo::model::introspect::{Introspect, Ty, Struct, Member};
use dojo::model::{Model, ModelIndex, Layout, FieldLayout};
use dojo::meta::{Layout, FieldLayout};
use dojo::meta::introspect::{Introspect, Ty, Struct, Member};
use dojo::model::{Model, ModelIndex};
use dojo::utils;
use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait};

Expand All @@ -32,13 +33,9 @@ pub struct ResourceMetadata {

#[generate_trait]
pub impl ResourceMetadataImpl of ResourceMetadataTrait {
fn from_values(resource_id: felt252, ref values: Span<felt252>) -> ResourceMetadata {
let metadata_uri = Serde::<ByteArray>::deserialize(ref values);
if metadata_uri.is_none() {
panic!("Model `ResourceMetadata`: metadata_uri deserialization failed.");
}

ResourceMetadata { resource_id, metadata_uri: metadata_uri.unwrap() }
fn from_values(resource_id: felt252, ref values: Span<felt252>) -> Option<ResourceMetadata> {
let metadata_uri = Serde::<ByteArray>::deserialize(ref values)?;
Option::Some(ResourceMetadata { resource_id, metadata_uri })
}
}

Expand All @@ -49,7 +46,10 @@ pub impl ResourceMetadataModel of Model<ResourceMetadata> {
};

let mut values = world.entity(Self::selector(), ModelIndex::Keys(keys), Self::layout());
ResourceMetadataTrait::from_values(*keys.at(0), ref values)
match ResourceMetadataTrait::from_values(*keys.at(0), ref values) {
Option::Some(x) => x,
Option::None => { panic!("Model `ResourceMetadata`: deserialization failed.") }
}
Comment on lines +49 to +52
Copy link

Choose a reason for hiding this comment

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

Consider handling deserialization failure without panicking, sensei.

Currently, the code panics if deserialization fails. To enhance resilience, consider propagating the Option::None to the caller instead of panicking.

Apply this diff to modify the error handling:

 fn get(world: IWorldDispatcher, keys: Span<felt252>) -> Option<ResourceMetadata> {
     if keys.len() != 1 {
-        panic!("Model `ResourceMetadata`: bad keys length.");
+        return Option::None;
     };
     let mut values = world.entity(Self::selector(), ModelIndex::Keys(keys), Self::layout());
     match ResourceMetadataTrait::from_values(*keys.at(0), ref values) {
         Option::Some(x) => Option::Some(x),
-        Option::None => { panic!("Model `ResourceMetadata`: deserialization failed.") }
+        Option::None => Option::None,
     }
 }

Committable suggestion was skipped due to low confidence.

}

fn set_model(self: @ResourceMetadata, world: IWorldDispatcher,) {
Expand Down Expand Up @@ -198,8 +198,8 @@ pub mod resource_metadata {
use super::ResourceMetadata;
use super::ResourceMetadataModel;

use dojo::model::introspect::{Introspect, Ty};
use dojo::model::Layout;
use dojo::meta::introspect::{Introspect, Ty};
use dojo::meta::Layout;

#[storage]
struct Storage {}
Expand Down
6 changes: 3 additions & 3 deletions crates/dojo-core/src/model/model.cairo
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use starknet::SyscallResult;

use dojo::model::Layout;
use dojo::model::introspect::Ty;
use dojo::meta::Layout;
use dojo::meta::introspect::Ty;
use dojo::world::IWorldDispatcher;

#[derive(Copy, Drop, Serde, Debug, PartialEq)]
Expand All @@ -16,7 +16,7 @@ pub enum ModelIndex {
pub trait ModelEntity<T> {
fn id(self: @T) -> felt252;
fn values(self: @T) -> Span<felt252>;
fn from_values(entity_id: felt252, ref values: Span<felt252>) -> T;
fn from_values(entity_id: felt252, ref values: Span<felt252>) -> Option<T>;
// Get is always used with the trait path, which results in no ambiguity for the compiler.
fn get(world: IWorldDispatcher, entity_id: felt252) -> T;
// Update and delete can be used directly on the entity, which results in ambiguity.
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo-core/src/storage/layout.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use dojo::model::{Layout, FieldLayout};
use dojo::meta::{Layout, FieldLayout};
use dojo::utils::{combine_key, find_field_layout};

use super::database;
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo-core/src/tests/base.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ mod invalid_model_world {
#[test]
#[available_gas(6000000)]
#[should_panic(
expected: ("Resource `dojo-invalid_model` is already registered", 'ENTRYPOINT_FAILED',)
expected: ("Model `dojo-invalid_model` is already registered", 'ENTRYPOINT_FAILED',)
)]
fn test_deploy_from_world_invalid_model() {
let world = deploy_world();
Expand Down
5 changes: 3 additions & 2 deletions crates/dojo-core/src/tests/benchmarks.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ use starknet::storage_access::{
};
use starknet::syscalls::{storage_read_syscall, storage_write_syscall};

use dojo::model::{Model, Layout, ModelIndex};
use dojo::model::introspect::Introspect;
use dojo::meta::Layout;
use dojo::model::{Model, ModelIndex};
use dojo::meta::introspect::Introspect;
use dojo::storage::{database, storage};
use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use dojo::model::introspect::Introspect;
use dojo::model::{Layout, FieldLayout};
use dojo::meta::introspect::Introspect;
use dojo::meta::{Layout, FieldLayout};

#[derive(Drop, Introspect)]
struct Base {
Expand Down
6 changes: 4 additions & 2 deletions crates/dojo-core/src/tests/model/model.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ fn test_from_values() {
let mut values = [3, 4].span();

let model_entity = ModelEntity::<FooEntity>::from_values(1, ref values);
assert!(model_entity.is_some());
let model_entity = model_entity.unwrap();
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 mut values = [3].span();
let _ = ModelEntity::<FooEntity>::from_values(1, ref values);
let res = ModelEntity::<FooEntity>::from_values(1, ref values);
assert!(res.is_none());
}

#[test]
Expand Down
5 changes: 3 additions & 2 deletions crates/dojo-core/src/tests/world/entities.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use core::array::{ArrayTrait, SpanTrait};

use starknet::{contract_address_const, ContractAddress};

use dojo::model::{ModelIndex, Layout, FieldLayout, Model};
use dojo::model::introspect::Introspect;
use dojo::meta::{Layout, FieldLayout};
use dojo::model::{ModelIndex, Model};
use dojo::meta::introspect::Introspect;
use dojo::storage::database::MAX_ARRAY_LENGTH;
use dojo::utils::entity_id_from_keys;
use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait, world};
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo-core/src/tests/world/resources.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ fn test_upgrade_model_from_model_writer() {
}

#[test]
#[should_panic(expected: ("Resource `dojo-Foo` is already registered", 'ENTRYPOINT_FAILED',))]
#[should_panic(expected: ("Model `dojo-Foo` is already registered", 'ENTRYPOINT_FAILED',))]
fn test_upgrade_model_from_random_account() {
let bob = starknet::contract_address_const::<0xb0b>();
let alice = starknet::contract_address_const::<0xa11ce>();
Expand Down
2 changes: 1 addition & 1 deletion crates/dojo-core/src/utils/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::option::Option;
use core::poseidon::poseidon_hash_span;
use core::serde::Serde;

use dojo::model::{Layout, FieldLayout};
use dojo::meta::{Layout, FieldLayout};

/// Compute the poseidon hash of a serialized ByteArray
pub fn bytearray_hash(data: @ByteArray) -> felt252 {
Expand Down
12 changes: 10 additions & 2 deletions crates/dojo-core/src/world/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ pub fn no_namespace_write_access(caller: ContractAddress, namespace: @ByteArray)
format!("Caller `{:?}` has no write access on namespace `{}`", caller, namespace)
}

pub fn event_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray {
format!("Event `{}-{}` is already registered", namespace, name)
}

pub fn event_not_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray {
format!("Event `{}-{}` is not registered", namespace, name)
}

pub fn model_already_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray {
format!("Resource `{}-{}` is already registered", namespace, name)
format!("Model `{}-{}` is already registered", namespace, name)
}

pub fn model_not_registered(namespace: @ByteArray, name: @ByteArray) -> ByteArray {
format!("Resource `{}-{}` is not registered", namespace, name)
format!("Model `{}-{}` is not registered", namespace, name)
}

pub fn resource_not_registered(resource: felt252) -> ByteArray {
Expand Down
Loading
Loading