From 13836518416f6fda75777780309ee7717aa99ab4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 19 Feb 2024 11:50:24 +0100 Subject: [PATCH 01/10] refacto: using cainome parser in order to parse events in sozo --- Cargo.lock | 1 + bin/sozo/Cargo.toml | 2 + bin/sozo/src/commands/events.rs | 69 +++++--- bin/sozo/src/ops/events.rs | 300 +++++++++++++++++++++++--------- 4 files changed, 262 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5687a0b51e..ab7fcb776c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10931,6 +10931,7 @@ dependencies = [ "anyhow", "assert_fs", "async-trait", + "cainome 0.1.5 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.2)", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", diff --git a/bin/sozo/Cargo.toml b/bin/sozo/Cargo.toml index 752f36e41f..99524455d3 100644 --- a/bin/sozo/Cargo.toml +++ b/bin/sozo/Cargo.toml @@ -44,6 +44,8 @@ tracing-log = "0.1.3" tracing.workspace = true url.workspace = true +cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.2.2" } + [dev-dependencies] assert_fs = "1.0.10" dojo-test-utils = { workspace = true, features = [ "build-examples" ] } diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index ca9edd556d..e6cf2d0701 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; use anyhow::{anyhow, Result}; -use cairo_lang_starknet::abi::{self, Event, Item}; +use cainome::parser::tokens::Token; +use cainome::parser::AbiParser; +use cairo_lang_starknet::abi; use clap::Parser; use dojo_world::manifest::Manifest; use dojo_world::metadata::dojo_metadata_from_workspace; use scarb::core::Config; +use serde_json; use starknet::core::utils::starknet_keccak; use super::options::starknet::StarknetOptions; @@ -73,50 +76,68 @@ impl EventsArgs { } } -fn extract_events(manifest: &Manifest) -> HashMap> { - fn inner_helper(events: &mut HashMap>, abi: abi::Contract) { - for item in abi.into_iter() { - if let Item::Event(e) = item { - match e.kind { - abi::EventKind::Struct { .. } => { - let event_name = starknet_keccak( - e.name - .split("::") - .last() - .expect("valid fully qualified name") - .as_bytes(), - ); - let vec = events.entry(event_name.to_string()).or_default(); - vec.push(e.clone()); +fn is_event(token: &Token) -> bool { + match token { + Token::Composite(composite) => composite.is_event, + _ => false, + } +} + +fn extract_events(manifest: &Manifest) -> HashMap> { + //println!("manifest {:?}", manifest.world.abi.clone().unwrap()); + + // Helper function to process ABI and populate events_map + fn process_abi(abi: &abi::Contract, events_map: &mut HashMap>) { + match serde_json::to_string(abi) { + Ok(abi_str) => match AbiParser::tokens_from_abi_string(&abi_str, &HashMap::new()) { + Ok(tokens) => { + for token in tokens.structs { + if is_event(&token) { + //println!("°°°°°°°°°°°°"); + //println!("Token Name: {:?}", token.type_name()); + //println!("Token: {:?}", token); + //println!("°°°°°°°°°°°°"); + + let event_name = starknet_keccak(token.type_name().as_bytes()); + //println!("Event Name: {} {}", event_name, token.type_name()); + + let vec = events_map.entry(event_name.to_string()).or_default(); + vec.push(token.clone()); + } } - abi::EventKind::Enum { .. } => (), } - } + Err(e) => println!("Error parsing ABI: {}", e), + }, + Err(e) => println!("Error serializing Contract to JSON: {}", e), } } let mut events_map = HashMap::new(); - if let Some(abi) = manifest.world.abi.clone() { - inner_helper(&mut events_map, abi); + // Iterate over all ABIs in the manifest and process them + if let Some(abi) = manifest.world.abi.as_ref() { + process_abi(abi, &mut events_map); } - if let Some(abi) = manifest.executor.abi.clone() { - inner_helper(&mut events_map, abi); + if let Some(abi) = manifest.executor.abi.as_ref() { + process_abi(abi, &mut events_map); } for contract in &manifest.contracts { if let Some(abi) = contract.abi.clone() { - inner_helper(&mut events_map, abi); + process_abi(&abi, &mut events_map); } } for model in &manifest.contracts { if let Some(abi) = model.abi.clone() { - inner_helper(&mut events_map, abi); + process_abi(&abi, &mut events_map); } } + //println!("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + //println!("Events Map 2: {:?}", events_map); + //println!("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); events_map } diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index 6abb64e09a..08f20d0413 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -1,19 +1,18 @@ use std::collections::{HashMap, VecDeque}; +use crate::commands::events::EventsArgs; use anyhow::Result; -use cairo_lang_starknet::abi::{Event, EventKind}; -use cairo_lang_starknet::plugin::events::EventFieldKind; +use cainome::parser::tokens::{CompositeInnerKind, CoreBasic, Token}; use dojo_world::metadata::Environment; +use starknet::core::types::FieldElement; use starknet::core::types::{BlockId, EventFilter}; use starknet::core::utils::{parse_cairo_short_string, starknet_keccak}; use starknet::providers::Provider; -use crate::commands::events::EventsArgs; - pub async fn execute( args: EventsArgs, env_metadata: Option, - events_map: Option>>, + events_map: Option>>, ) -> Result<()> { let EventsArgs { chunk_size, @@ -38,10 +37,12 @@ pub async fn execute( let res = provider.get_events(event_filter, continuation_token, chunk_size).await?; + //print_events_map(events_map.clone()); + if let Some(events_map) = events_map { parse_and_print_events(res, events_map)?; } else { - println!("{}", serde_json::to_string_pretty(&res)?); + println!("[serde_json] {}", serde_json::to_string_pretty(&res)?); } Ok(()) @@ -49,115 +50,242 @@ pub async fn execute( fn parse_and_print_events( res: starknet::core::types::EventsPage, - events_map: HashMap>, + events_map: HashMap>, ) -> Result<()> { println!("Continuation token: {:?}", res.continuation_token); println!("----------------------------------------------"); for event in res.events { + //println!("----------------------------------------------"); + //println!("[parse_and_print_events]"); + //println!("{}", serde_json::to_string_pretty(&event)?); if let Some(e) = parse_event(event.clone(), &events_map) { println!("{e}"); } else { - // Couldn't parse event - println!("{}", serde_json::to_string_pretty(&event)?); + println!("Couldn't parse event - {}", serde_json::to_string_pretty(&event)?); + println!("-----> {}", event.keys[0].to_string()); } } Ok(()) } +fn print_events_map(events_map: Option>>) { + match events_map { + Some(map) => { + for (key, events) in map { + println!("[print_events_map] Key: {}", key); + for event in events { + // Using {:?} to print the Debug representation of Event + // Ensure that Event implements Debug trait + println!("[print_events_map] {:?}", event); + println!(""); + } + println!("----------------------------------------------"); + } + } + None => println!("No events map."), + } +} + +fn parse_core_basic( + cb: &CoreBasic, + value: &FieldElement, + is_nested: bool, +) -> Result { + match cb.type_name().as_str() { + "felt252" => { + if is_nested { + Ok(format!("\"{:#x}\"", value)) + } else { + match parse_cairo_short_string(value) { + Ok(parsed) => Ok(parsed), + Err(_) => Ok(format!("\"{:#x}\"", value)), + } + } + } + "bool" => { + if *value == FieldElement::ZERO { + Ok("false".to_string()) + } else { + Ok("true".to_string()) + } + } + "ClassHash" | "ContractAddress" => Ok(format!("{:#x}", value)), + "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" | "i128" => { + Ok(value.to_string()) + } + _ => Err(format!("Unsupported CoreBasic type: {}", cb.type_name())), + } +} + fn parse_event( event: starknet::core::types::EmittedEvent, - events_map: &HashMap>, + events_map: &HashMap>, ) -> Option { - let keys = event.keys; - let event_hash = keys[0].to_string(); - let events = events_map.get(&event_hash)?; + //println!("Event: {:?}", event.clone()); + + let mut data = VecDeque::from(event.data.clone()); + let mut keys = VecDeque::from(event.keys.clone()); + let event_hash = keys.pop_front()?; + //println!("Event hash: {}", event_hash.clone()); + + let events = events_map.get(&event_hash.to_string())?; + //println!("Events: {:?}", events.clone()); 'outer: for e in events { - let mut ret = format!("Event name: {}\n", e.name); - let mut data = VecDeque::from(event.data.clone()); + let mut ret = format!("Event name: {}\n", e.type_path()); - // Length is two only when its custom event - if keys.len() == 2 { - let name = parse_cairo_short_string(&keys[1]).ok()?; - ret.push_str(&format!("Model name: {}\n", name)); - } + //println!("data: {:?}", data.clone()); + + if let Token::Composite(composite) = e { + //println!("Composite: {:?}", composite); + + for inner in &composite.inners { + //println!("Inner: {:?}", inner); - match &e.kind { - EventKind::Struct { members } => { - for field in members { - if field.kind != EventFieldKind::DataSerde { - continue; + let result: Result<_, &'static str> = match inner.kind { + CompositeInnerKind::Data => data.pop_front().ok_or("Missing data value"), + CompositeInnerKind::Key => keys.pop_front().ok_or("Missing key value"), + _ => Err("Unsupported inner kind encountered"), + }; + + let value = match result { + Ok(val) => val, + Err(e) => { + println!("{}", e); + continue 'outer; } - match field.ty.as_str() { - "core::starknet::contract_address::ContractAddress" - | "core::starknet::class_hash::ClassHash" => { - let value = match data.pop_front() { - Some(addr) => addr, - None => continue 'outer, - }; - ret.push_str(&format!("{}: {:#x}\n", field.name, value)); - } - "core::felt252" => { - let value = match data.pop_front() { - Some(addr) => addr, - None => continue 'outer, - }; - let value = match parse_cairo_short_string(&value) { - Ok(v) => v, - Err(_) => format!("{:#x}", value), - }; - ret.push_str(&format!("{}: {}\n", field.name, value)); - } - "core::integer::u8" => { - let value = match data.pop_front() { - Some(addr) => addr, - None => continue 'outer, - }; - let num = match value.to_string().parse::() { - Ok(num) => num, - Err(_) => continue 'outer, - }; - - ret.push_str(&format!("{}: {}\n", field.name, num)); - } - "dojo_examples::systems::move::Direction" => { - let value = match data.pop_front() { - Some(addr) => addr, - None => continue 'outer, - }; - ret.push_str(&format!("{}: {}\n", field.name, value)); + }; + //println!("Value: {}", value.to_string()); + + let formatted_value = match &inner.token { + Token::CoreBasic(ref cb) => match parse_core_basic(cb, &value, false) { + Ok(parsed_value) => parsed_value, + Err(e) => { + println!("Error parsing CoreBasic: {}", e); + continue 'outer; } - "core::array::Span::" => { - let length = match data.pop_front() { - Some(addr) => addr, - None => continue 'outer, - }; - let length = match length.to_string().parse::() { - Ok(len) => len, - Err(_) => continue 'outer, - }; - ret.push_str(&format!("{}: ", field.name)); - if data.len() >= length { - ret.push_str(&format!( - "{:?}\n", - data.drain(..length) - .map(|e| format!("{:#x}", e)) - .collect::>() - )); + }, + Token::Array(ref array) => { + let length = match value.to_string().parse::() { + Ok(len) => len, + Err(e) => { + println!("Error parsing length to usize: {}", e); + continue 'outer; + } + }; + //println!("Length: {}", length); + + let cb = if let Token::CoreBasic(ref cb) = *array.inner { + cb + } else { + println!("Inner token of array is not CoreBasic"); + continue 'outer; + }; + + let mut elements = Vec::new(); + for _ in 0..length { + if let Some(element_value) = data.pop_front() { + //println!("Element value: {}", element_value.to_string()); + match parse_core_basic(cb, &element_value, true) { + Ok(element_str) => elements.push(element_str), + Err(e) => { + println!( + "Error parsing CoreBasic for array element: {}", + e + ); + continue 'outer; + } + }; } else { + println!("Missing array element value"); continue 'outer; } } - _ => { - return None; - } + + format!("[{}]", elements.join(", ")) } - } - return Some(ret); + _ => { + // Default case for unsupported Token types. + println!("Unsupported token type encountered"); + "Unsupported token type".to_string() + } + }; + ret.push_str(&format!("{}: {}\n", inner.name, formatted_value)); } - EventKind::Enum { .. } => unreachable!("shouldn't reach here"), + return Some(ret); } } None } + +#[cfg(test)] +mod tests { + use cainome::parser::tokens::{Array, Composite, CompositeInner, CompositeType}; + use starknet::core::types::EmittedEvent; + + use super::*; + + #[test] + fn test_array() { + let composite = Composite { + type_path: "dojo::world::world::StoreDelRecord".to_string(), + inners: vec![ + CompositeInner { + index: 0, + name: "table".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { type_path: "core::felt252".to_string() }), + }, + CompositeInner { + index: 1, + name: "keys".to_string(), + kind: CompositeInnerKind::Data, + token: Token::Array(Array { + type_path: "core::array::Span::".to_string(), + inner: Box::new(Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_string(), + })), + }), + }, + ], + generic_args: vec![], + r#type: CompositeType::Struct, + is_event: true, + alias: None, + }; + let tokenized_composite = Token::Composite(composite); + + let mut events_map = HashMap::new(); + events_map.insert( + starknet_keccak("StoreDelRecord".as_bytes()).to_string(), + vec![tokenized_composite], + ); + + let event = EmittedEvent { + keys: vec![starknet_keccak("StoreDelRecord".as_bytes())], + data: vec![ + FieldElement::from_hex_be("0x54657374").expect("Invalid hex"), + FieldElement::from(3u128), + FieldElement::from_hex_be("0x5465737431").expect("Invalid hex"), + FieldElement::from_hex_be("0x5465737432").expect("Invalid hex"), + FieldElement::from_hex_be("0x5465737433").expect("Invalid hex"), + ], + from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), + block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + block_number: 1, + transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + }; + + // Construct the expected output string + let expected_output = + format!("Event name: StoreDelRecord\ntable: Test\nkeys: [Test1, Test2, Test3]\n"); + + // Parse the event and check the result + let actual_output = parse_event(event.clone(), &events_map) + .unwrap_or_else(|| "Couldn't parse event".to_string()); + + // Assert that the actual output matches the expected output + assert_eq!(actual_output, expected_output); + } +} From cce2b65a98117761aa8abd97fa7ac242778e0495 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 23 Feb 2024 10:37:52 +0100 Subject: [PATCH 02/10] refacto: clean logs + add tests --- bin/sozo/src/commands/events.rs | 12 -- bin/sozo/src/ops/events.rs | 345 ++++++++++++++++++++++++-------- 2 files changed, 265 insertions(+), 92 deletions(-) diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index f503580822..e4dd528614 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -84,22 +84,13 @@ fn is_event(token: &Token) -> bool { } fn extract_events(manifest: &Manifest) -> HashMap> { - //println!("manifest {:?}", manifest.world.abi.clone().unwrap()); - - // Helper function to process ABI and populate events_map fn process_abi(abi: &abi::Contract, events_map: &mut HashMap>) { match serde_json::to_string(abi) { Ok(abi_str) => match AbiParser::tokens_from_abi_string(&abi_str, &HashMap::new()) { Ok(tokens) => { for token in tokens.structs { if is_event(&token) { - //println!("°°°°°°°°°°°°"); - //println!("Token Name: {:?}", token.type_name()); - //println!("Token: {:?}", token); - //println!("°°°°°°°°°°°°"); - let event_name = starknet_keccak(token.type_name().as_bytes()); - //println!("Event Name: {} {}", event_name, token.type_name()); let vec = events_map.entry(event_name.to_string()).or_default(); vec.push(token.clone()); @@ -131,9 +122,6 @@ fn extract_events(manifest: &Manifest) -> HashMap> { } } - //println!("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - //println!("Events Map 2: {:?}", events_map); - //println!("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); events_map } diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index 08f20d0413..aaff209f82 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -37,12 +37,8 @@ pub async fn execute( let res = provider.get_events(event_filter, continuation_token, chunk_size).await?; - //print_events_map(events_map.clone()); - if let Some(events_map) = events_map { parse_and_print_events(res, events_map)?; - } else { - println!("[serde_json] {}", serde_json::to_string_pretty(&res)?); } Ok(()) @@ -55,51 +51,26 @@ fn parse_and_print_events( println!("Continuation token: {:?}", res.continuation_token); println!("----------------------------------------------"); for event in res.events { - //println!("----------------------------------------------"); - //println!("[parse_and_print_events]"); - //println!("{}", serde_json::to_string_pretty(&event)?); if let Some(e) = parse_event(event.clone(), &events_map) { println!("{e}"); - } else { - println!("Couldn't parse event - {}", serde_json::to_string_pretty(&event)?); - println!("-----> {}", event.keys[0].to_string()); } } Ok(()) } -fn print_events_map(events_map: Option>>) { - match events_map { - Some(map) => { - for (key, events) in map { - println!("[print_events_map] Key: {}", key); - for event in events { - // Using {:?} to print the Debug representation of Event - // Ensure that Event implements Debug trait - println!("[print_events_map] {:?}", event); - println!(""); - } - println!("----------------------------------------------"); - } - } - None => println!("No events map."), - } -} - fn parse_core_basic( cb: &CoreBasic, value: &FieldElement, - is_nested: bool, + include_felt_string: bool, ) -> Result { match cb.type_name().as_str() { "felt252" => { - if is_nested { - Ok(format!("\"{:#x}\"", value)) - } else { - match parse_cairo_short_string(value) { - Ok(parsed) => Ok(parsed), - Err(_) => Ok(format!("\"{:#x}\"", value)), + let hex = format!("{:#x}", value); + match parse_cairo_short_string(value) { + Ok(parsed) if !parsed.is_empty() && include_felt_string => { + Ok(format!("{} \"{}\"", parsed, hex)) } + _ => Ok(format!("\"{}\"", hex)), } } "bool" => { @@ -121,27 +92,17 @@ fn parse_event( event: starknet::core::types::EmittedEvent, events_map: &HashMap>, ) -> Option { - //println!("Event: {:?}", event.clone()); - let mut data = VecDeque::from(event.data.clone()); let mut keys = VecDeque::from(event.keys.clone()); let event_hash = keys.pop_front()?; - //println!("Event hash: {}", event_hash.clone()); let events = events_map.get(&event_hash.to_string())?; - //println!("Events: {:?}", events.clone()); 'outer: for e in events { let mut ret = format!("Event name: {}\n", e.type_path()); - //println!("data: {:?}", data.clone()); - if let Token::Composite(composite) = e { - //println!("Composite: {:?}", composite); - for inner in &composite.inners { - //println!("Inner: {:?}", inner); - let result: Result<_, &'static str> = match inner.kind { CompositeInnerKind::Data => data.pop_front().ok_or("Missing data value"), CompositeInnerKind::Key => keys.pop_front().ok_or("Missing key value"), @@ -150,65 +111,41 @@ fn parse_event( let value = match result { Ok(val) => val, - Err(e) => { - println!("{}", e); - continue 'outer; - } + Err(_) => continue 'outer, }; - //println!("Value: {}", value.to_string()); let formatted_value = match &inner.token { - Token::CoreBasic(ref cb) => match parse_core_basic(cb, &value, false) { + Token::CoreBasic(ref cb) => match parse_core_basic(cb, &value, true) { Ok(parsed_value) => parsed_value, - Err(e) => { - println!("Error parsing CoreBasic: {}", e); - continue 'outer; - } + Err(_) => continue 'outer, }, Token::Array(ref array) => { let length = match value.to_string().parse::() { Ok(len) => len, - Err(e) => { - println!("Error parsing length to usize: {}", e); - continue 'outer; - } + Err(_) => continue 'outer, }; - //println!("Length: {}", length); let cb = if let Token::CoreBasic(ref cb) = *array.inner { cb } else { - println!("Inner token of array is not CoreBasic"); continue 'outer; }; let mut elements = Vec::new(); for _ in 0..length { if let Some(element_value) = data.pop_front() { - //println!("Element value: {}", element_value.to_string()); - match parse_core_basic(cb, &element_value, true) { + match parse_core_basic(cb, &element_value, false) { Ok(element_str) => elements.push(element_str), - Err(e) => { - println!( - "Error parsing CoreBasic for array element: {}", - e - ); - continue 'outer; - } + Err(_) => continue 'outer, }; } else { - println!("Missing array element value"); continue 'outer; } } format!("[{}]", elements.join(", ")) } - _ => { - // Default case for unsupported Token types. - println!("Unsupported token type encountered"); - "Unsupported token type".to_string() - } + _ => continue 'outer, }; ret.push_str(&format!("{}: {}\n", inner.name, formatted_value)); } @@ -226,6 +163,126 @@ mod tests { use super::*; + #[test] + fn test_core_basic() { + let composite = Composite { + type_path: "dojo::world::world::TestEvent".to_string(), + inners: vec![ + CompositeInner { + index: 0, + name: "felt252".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { type_path: "core::felt252".to_string() }), + }, + CompositeInner { + index: 1, + name: "bool".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { type_path: "core::bool".to_string() }), + }, + CompositeInner { + index: 2, + name: "u8".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u8".to_string(), + }), + }, + CompositeInner { + index: 3, + name: "u16".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u16".to_string(), + }), + }, + CompositeInner { + index: 4, + name: "u32".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u32".to_string(), + }), + }, + CompositeInner { + index: 5, + name: "u64".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u64".to_string(), + }), + }, + CompositeInner { + index: 6, + name: "u128".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u128".to_string(), + }), + }, + CompositeInner { + index: 7, + name: "usize".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::usize".to_string(), + }), + }, + CompositeInner { + index: 8, + name: "class_hash".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { type_path: "core::ClassHash".to_string() }), + }, + CompositeInner { + index: 9, + name: "contract_address".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::ContractAddress".to_string(), + }), + }, + ], + generic_args: vec![], + r#type: CompositeType::Struct, + is_event: true, + alias: None, + }; + let tokenized_composite = Token::Composite(composite); + + let mut events_map = HashMap::new(); + events_map + .insert(starknet_keccak("TestEvent".as_bytes()).to_string(), vec![tokenized_composite]); + + let event = EmittedEvent { + keys: vec![starknet_keccak("TestEvent".as_bytes())], + data: vec![ + FieldElement::from_hex_be("0x5465737431").expect("Invalid hex"), + FieldElement::from(1u8), // bool true + FieldElement::from(1u8), + FieldElement::from(2u16), + FieldElement::from(3u32), + FieldElement::from(4u64), + FieldElement::from(5u128), + FieldElement::from(6usize), + FieldElement::from_hex_be("0x54657374").expect("Invalid hex"), + FieldElement::from_hex_be("0x54657374").expect("Invalid hex"), + ], + from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), + block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + block_number: 1, + transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + }; + + let expected_output = + format!("Event name: dojo::world::world::TestEvent\nfelt252: Test1 \"0x5465737431\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n"); + + let actual_output = parse_event(event.clone(), &events_map) + .unwrap_or_else(|| "Couldn't parse event".to_string()); + + assert_eq!(actual_output, expected_output); + } + #[test] fn test_array() { let composite = Composite { @@ -277,15 +334,143 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), }; - // Construct the expected output string let expected_output = - format!("Event name: StoreDelRecord\ntable: Test\nkeys: [Test1, Test2, Test3]\n"); + format!("Event name: dojo::world::world::StoreDelRecord\ntable: Test \"0x54657374\"\nkeys: [\"0x5465737431\", \"0x5465737432\", \"0x5465737433\"]\n"); + + let actual_output = parse_event(event.clone(), &events_map) + .unwrap_or_else(|| "Couldn't parse event".to_string()); + + assert_eq!(actual_output, expected_output); + } + + #[test] + fn test_custom_event() { + let composite = Composite { + type_path: "dojo::world::world::CustomEvent".to_string(), + inners: vec![ + CompositeInner { + index: 0, + name: "key_1".to_string(), + kind: CompositeInnerKind::Key, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u32".to_string(), + }), + }, + CompositeInner { + index: 1, + name: "key_2".to_string(), + kind: CompositeInnerKind::Key, + token: Token::CoreBasic(CoreBasic { type_path: "core::felt252".to_string() }), + }, + CompositeInner { + index: 2, + name: "data_1".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u8".to_string(), + }), + }, + CompositeInner { + index: 3, + name: "data_2".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { + type_path: "core::integer::u8".to_string(), + }), + }, + ], + generic_args: vec![], + r#type: CompositeType::Struct, + is_event: true, + alias: None, + }; + let tokenized_composite = Token::Composite(composite); + + let mut events_map = HashMap::new(); + events_map.insert( + starknet_keccak("CustomEvent".as_bytes()).to_string(), + vec![tokenized_composite], + ); + + let event = EmittedEvent { + keys: vec![ + starknet_keccak("CustomEvent".as_bytes()), + FieldElement::from(3u128), + FieldElement::from_hex_be("0x5465737431").expect("Invalid hex"), + ], + data: vec![FieldElement::from(1u128), FieldElement::from(2u128)], + from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), + block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + block_number: 1, + transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + }; + + let expected_output = + format!("Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: Test1 \"0x5465737431\"\ndata_1: 1\ndata_2: 2\n"); + + let actual_output = parse_event(event.clone(), &events_map) + .unwrap_or_else(|| "Couldn't parse event".to_string()); + + assert_eq!(actual_output, expected_output); + } + + #[test] + fn test_zero_felt() { + let composite = Composite { + type_path: "dojo::world::world::StoreDelRecord".to_string(), + inners: vec![ + CompositeInner { + index: 0, + name: "table".to_string(), + kind: CompositeInnerKind::Data, + token: Token::CoreBasic(CoreBasic { type_path: "core::felt252".to_string() }), + }, + CompositeInner { + index: 1, + name: "keys".to_string(), + kind: CompositeInnerKind::Data, + token: Token::Array(Array { + type_path: "core::array::Span::".to_string(), + inner: Box::new(Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_string(), + })), + }), + }, + ], + generic_args: vec![], + r#type: CompositeType::Struct, + is_event: true, + alias: None, + }; + let tokenized_composite = Token::Composite(composite); + + let mut events_map = HashMap::new(); + events_map.insert( + starknet_keccak("StoreDelRecord".as_bytes()).to_string(), + vec![tokenized_composite], + ); + + let event = EmittedEvent { + keys: vec![starknet_keccak("StoreDelRecord".as_bytes())], + data: vec![ + FieldElement::from_hex_be("0x0").expect("Invalid hex"), + FieldElement::from(3u128), + FieldElement::from_hex_be("0x0").expect("Invalid hex"), + FieldElement::from_hex_be("0x1").expect("Invalid hex"), + FieldElement::from_hex_be("0x2").expect("Invalid hex"), + ], + from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), + block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + block_number: 1, + transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + }; + + let expected_output = + format!("Event name: dojo::world::world::StoreDelRecord\ntable: \"0x0\"\nkeys: [\"0x0\", \"0x1\", \"0x2\"]\n"); - // Parse the event and check the result let actual_output = parse_event(event.clone(), &events_map) .unwrap_or_else(|| "Couldn't parse event".to_string()); - // Assert that the actual output matches the expected output assert_eq!(actual_output, expected_output); } } From a785914db5f82ee30c6772f3db0147158ac25237 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Feb 2024 17:22:45 +0100 Subject: [PATCH 03/10] fix: clippy error --- bin/sozo/src/ops/events.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index aaff209f82..84be30579b 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -275,7 +275,7 @@ mod tests { }; let expected_output = - format!("Event name: dojo::world::world::TestEvent\nfelt252: Test1 \"0x5465737431\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n"); + "Event name: dojo::world::world::TestEvent\nfelt252: Test1 \"0x5465737431\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n".to_string(); let actual_output = parse_event(event.clone(), &events_map) .unwrap_or_else(|| "Couldn't parse event".to_string()); @@ -335,7 +335,7 @@ mod tests { }; let expected_output = - format!("Event name: dojo::world::world::StoreDelRecord\ntable: Test \"0x54657374\"\nkeys: [\"0x5465737431\", \"0x5465737432\", \"0x5465737433\"]\n"); + "Event name: dojo::world::world::StoreDelRecord\ntable: Test \"0x54657374\"\nkeys: [\"0x5465737431\", \"0x5465737432\", \"0x5465737433\"]\n".to_string(); let actual_output = parse_event(event.clone(), &events_map) .unwrap_or_else(|| "Couldn't parse event".to_string()); @@ -406,7 +406,7 @@ mod tests { }; let expected_output = - format!("Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: Test1 \"0x5465737431\"\ndata_1: 1\ndata_2: 2\n"); + "Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: Test1 \"0x5465737431\"\ndata_1: 1\ndata_2: 2\n".to_string(); let actual_output = parse_event(event.clone(), &events_map) .unwrap_or_else(|| "Couldn't parse event".to_string()); @@ -465,8 +465,7 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), }; - let expected_output = - format!("Event name: dojo::world::world::StoreDelRecord\ntable: \"0x0\"\nkeys: [\"0x0\", \"0x1\", \"0x2\"]\n"); + let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: \"0x0\"\nkeys: [\"0x0\", \"0x1\", \"0x2\"]\n".to_string(); let actual_output = parse_event(event.clone(), &events_map) .unwrap_or_else(|| "Couldn't parse event".to_string()); From 5a4a5c294d95ceff9a42fcb6299ff629c5f98138 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 26 Feb 2024 16:33:46 +0100 Subject: [PATCH 04/10] refacto: best error handling --- bin/sozo/src/commands/events.rs | 24 +++-- bin/sozo/src/ops/events.rs | 156 ++++++++++++++++++-------------- 2 files changed, 101 insertions(+), 79 deletions(-) diff --git a/bin/sozo/src/commands/events.rs b/bin/sozo/src/commands/events.rs index e4dd528614..2232b51919 100644 --- a/bin/sozo/src/commands/events.rs +++ b/bin/sozo/src/commands/events.rs @@ -59,7 +59,7 @@ impl EventsArgs { return Err(anyhow!("Run scarb migrate before running this command")); } - Some(extract_events(&Manifest::load_from_path(manifest_path)?)) + Some(extract_events(&Manifest::load_from_path(manifest_path)?)?) } else { None }; @@ -83,8 +83,11 @@ fn is_event(token: &Token) -> bool { } } -fn extract_events(manifest: &Manifest) -> HashMap> { - fn process_abi(abi: &abi::Contract, events_map: &mut HashMap>) { +fn extract_events(manifest: &Manifest) -> Result>> { + fn process_abi( + abi: &abi::Contract, + events_map: &mut HashMap>, + ) -> Result<()> { match serde_json::to_string(abi) { Ok(abi_str) => match AbiParser::tokens_from_abi_string(&abi_str, &HashMap::new()) { Ok(tokens) => { @@ -97,32 +100,33 @@ fn extract_events(manifest: &Manifest) -> HashMap> { } } } - Err(e) => println!("Error parsing ABI: {}", e), + Err(e) => return Err(anyhow!("Error parsing ABI: {}", e)), }, - Err(e) => println!("Error serializing Contract to JSON: {}", e), + Err(e) => return Err(anyhow!("Error serializing Contract to JSON: {}", e)), } + Ok(()) } let mut events_map = HashMap::new(); // Iterate over all ABIs in the manifest and process them if let Some(abi) = manifest.world.abi.as_ref() { - process_abi(abi, &mut events_map); + process_abi(abi, &mut events_map)?; } for contract in &manifest.contracts { if let Some(abi) = contract.abi.clone() { - process_abi(&abi, &mut events_map); + process_abi(&abi, &mut events_map)?; } } for model in &manifest.contracts { if let Some(abi) = model.abi.clone() { - process_abi(&abi, &mut events_map); + process_abi(&abi, &mut events_map)?; } } - events_map + Ok(events_map) } #[cfg(test)] @@ -140,7 +144,7 @@ mod test { #[test] fn extract_events_work_as_expected() { let manifest = Manifest::load_from_path("./tests/test_data/manifest.json").unwrap(); - let result = extract_events(&manifest); + let result = extract_events(&manifest).unwrap(); // we are just collection all events from manifest file so just verifying count should work assert!(result.len() == 13); diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index 84be30579b..c95a7444b4 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -1,8 +1,8 @@ use std::collections::{HashMap, VecDeque}; use crate::commands::events::EventsArgs; -use anyhow::Result; -use cainome::parser::tokens::{CompositeInnerKind, CoreBasic, Token}; +use anyhow::{anyhow, Result}; +use cainome::parser::tokens::{CompositeInner, CompositeInnerKind, CoreBasic, Token}; use dojo_world::metadata::Environment; use starknet::core::types::FieldElement; use starknet::core::types::{BlockId, EventFilter}; @@ -51,8 +51,16 @@ fn parse_and_print_events( println!("Continuation token: {:?}", res.continuation_token); println!("----------------------------------------------"); for event in res.events { - if let Some(e) = parse_event(event.clone(), &events_map) { - println!("{e}"); + match parse_event(event.clone(), &events_map) { + Ok(Some(e)) => { + println!("{e}"); + } + Ok(None) => { + println!("No matching event found for {:?}", event); + } + Err(e) => { + println!("Error parsing event: {}", e); + } } } Ok(()) @@ -62,7 +70,7 @@ fn parse_core_basic( cb: &CoreBasic, value: &FieldElement, include_felt_string: bool, -) -> Result { +) -> Result { match cb.type_name().as_str() { "felt252" => { let hex = format!("{:#x}", value); @@ -84,76 +92,78 @@ fn parse_core_basic( "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" | "i128" => { Ok(value.to_string()) } - _ => Err(format!("Unsupported CoreBasic type: {}", cb.type_name())), + _ => Err(anyhow!("Unsupported CoreBasic type: {}", cb.type_name())), } } fn parse_event( event: starknet::core::types::EmittedEvent, events_map: &HashMap>, -) -> Option { +) -> Result> { let mut data = VecDeque::from(event.data.clone()); let mut keys = VecDeque::from(event.keys.clone()); - let event_hash = keys.pop_front()?; + let event_hash = keys.pop_front().ok_or(anyhow!("Event hash missing"))?; - let events = events_map.get(&event_hash.to_string())?; - - 'outer: for e in events { - let mut ret = format!("Event name: {}\n", e.type_path()); + let events = + events_map.get(&event_hash.to_string()).ok_or(anyhow!("Events for hash not found"))?; + for e in events { if let Token::Composite(composite) = e { - for inner in &composite.inners { - let result: Result<_, &'static str> = match inner.kind { - CompositeInnerKind::Data => data.pop_front().ok_or("Missing data value"), - CompositeInnerKind::Key => keys.pop_front().ok_or("Missing key value"), - _ => Err("Unsupported inner kind encountered"), - }; + let processed_inners = process_inners(&composite.inners, &mut data, &mut keys)?; + let ret = format!("Event name: {}\n{}", e.type_path(), processed_inners); + return Ok(Some(ret)); + } + } + + Ok(None) +} - let value = match result { - Ok(val) => val, - Err(_) => continue 'outer, +fn process_inners( + inners: &[CompositeInner], + data: &mut VecDeque, + keys: &mut VecDeque, +) -> Result { + let mut ret = String::new(); + + for inner in inners { + let value = match inner.kind { + CompositeInnerKind::Data => data.pop_front().ok_or(anyhow!("Missing data value")), + CompositeInnerKind::Key => keys.pop_front().ok_or(anyhow!("Missing key value")), + _ => Err(anyhow!("Unsupported inner kind encountered")), + }?; + + let formatted_value = match &inner.token { + Token::CoreBasic(ref cb) => parse_core_basic(cb, &value, true)?, + Token::Array(ref array) => { + let length = value + .to_string() + .parse::() + .map_err(|_| anyhow!("Error parsing length to usize"))?; + + let cb = if let Token::CoreBasic(ref cb) = *array.inner { + cb + } else { + return Err(anyhow!("Inner token of array is not CoreBasic")); }; - let formatted_value = match &inner.token { - Token::CoreBasic(ref cb) => match parse_core_basic(cb, &value, true) { - Ok(parsed_value) => parsed_value, - Err(_) => continue 'outer, - }, - Token::Array(ref array) => { - let length = match value.to_string().parse::() { - Ok(len) => len, - Err(_) => continue 'outer, - }; - - let cb = if let Token::CoreBasic(ref cb) = *array.inner { - cb - } else { - continue 'outer; - }; - - let mut elements = Vec::new(); - for _ in 0..length { - if let Some(element_value) = data.pop_front() { - match parse_core_basic(cb, &element_value, false) { - Ok(element_str) => elements.push(element_str), - Err(_) => continue 'outer, - }; - } else { - continue 'outer; - } - } - - format!("[{}]", elements.join(", ")) + let mut elements = Vec::new(); + for _ in 0..length { + if let Some(element_value) = data.pop_front() { + let element_str = parse_core_basic(cb, &element_value, false)?; + elements.push(element_str); + } else { + return Err(anyhow!("Missing array element value")); } - _ => continue 'outer, - }; - ret.push_str(&format!("{}: {}\n", inner.name, formatted_value)); + } + + format!("[{}]", elements.join(", ")) } - return Some(ret); - } + _ => return Err(anyhow!("Unsupported token type encountered")), + }; + ret.push_str(&format!("{}: {}\n", inner.name, formatted_value)); } - None + Ok(ret) } #[cfg(test)] @@ -277,10 +287,12 @@ mod tests { let expected_output = "Event name: dojo::world::world::TestEvent\nfelt252: Test1 \"0x5465737431\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n".to_string(); - let actual_output = parse_event(event.clone(), &events_map) - .unwrap_or_else(|| "Couldn't parse event".to_string()); + let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); - assert_eq!(actual_output, expected_output); + match actual_output_option { + Some(actual_output) => assert_eq!(actual_output, expected_output), + None => panic!("Expected event was not found."), + } } #[test] @@ -337,10 +349,12 @@ mod tests { let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: Test \"0x54657374\"\nkeys: [\"0x5465737431\", \"0x5465737432\", \"0x5465737433\"]\n".to_string(); - let actual_output = parse_event(event.clone(), &events_map) - .unwrap_or_else(|| "Couldn't parse event".to_string()); + let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); - assert_eq!(actual_output, expected_output); + match actual_output_option { + Some(actual_output) => assert_eq!(actual_output, expected_output), + None => panic!("Expected event was not found."), + } } #[test] @@ -408,10 +422,12 @@ mod tests { let expected_output = "Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: Test1 \"0x5465737431\"\ndata_1: 1\ndata_2: 2\n".to_string(); - let actual_output = parse_event(event.clone(), &events_map) - .unwrap_or_else(|| "Couldn't parse event".to_string()); + let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); - assert_eq!(actual_output, expected_output); + match actual_output_option { + Some(actual_output) => assert_eq!(actual_output, expected_output), + None => panic!("Expected event was not found."), + } } #[test] @@ -467,9 +483,11 @@ mod tests { let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: \"0x0\"\nkeys: [\"0x0\", \"0x1\", \"0x2\"]\n".to_string(); - let actual_output = parse_event(event.clone(), &events_map) - .unwrap_or_else(|| "Couldn't parse event".to_string()); + let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); - assert_eq!(actual_output, expected_output); + match actual_output_option { + Some(actual_output) => assert_eq!(actual_output, expected_output), + None => panic!("Expected event was not found."), + } } } From 2a9c57dad12a7037b3a0f7b74f60fa667c4482c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Mar 2024 09:00:51 +0100 Subject: [PATCH 05/10] update how felt are displayed + remove unnecessary expect in tests --- bin/sozo/src/ops/events.rs | 52 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index c95a7444b4..5136fb821c 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -76,9 +76,9 @@ fn parse_core_basic( let hex = format!("{:#x}", value); match parse_cairo_short_string(value) { Ok(parsed) if !parsed.is_empty() && include_felt_string => { - Ok(format!("{} \"{}\"", parsed, hex)) + Ok(format!("{} \"{}\"", hex, parsed)) } - _ => Ok(format!("\"{}\"", hex)), + _ => Ok(format!("{}", hex)), } } "bool" => { @@ -267,7 +267,7 @@ mod tests { let event = EmittedEvent { keys: vec![starknet_keccak("TestEvent".as_bytes())], data: vec![ - FieldElement::from_hex_be("0x5465737431").expect("Invalid hex"), + FieldElement::from_hex_be("0x5465737431").unwrap(), FieldElement::from(1u8), // bool true FieldElement::from(1u8), FieldElement::from(2u16), @@ -275,13 +275,13 @@ mod tests { FieldElement::from(4u64), FieldElement::from(5u128), FieldElement::from(6usize), - FieldElement::from_hex_be("0x54657374").expect("Invalid hex"), - FieldElement::from_hex_be("0x54657374").expect("Invalid hex"), + FieldElement::from_hex_be("0x54657374").unwrap(), + FieldElement::from_hex_be("0x54657374").unwrap(), ], - from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), - block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + from_address: FieldElement::from_hex_be("0x123").unwrap(), + block_hash: FieldElement::from_hex_be("0x456").unwrap(), block_number: 1, - transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; let expected_output = @@ -334,16 +334,16 @@ mod tests { let event = EmittedEvent { keys: vec![starknet_keccak("StoreDelRecord".as_bytes())], data: vec![ - FieldElement::from_hex_be("0x54657374").expect("Invalid hex"), + FieldElement::from_hex_be("0x54657374").unwrap(), FieldElement::from(3u128), - FieldElement::from_hex_be("0x5465737431").expect("Invalid hex"), - FieldElement::from_hex_be("0x5465737432").expect("Invalid hex"), - FieldElement::from_hex_be("0x5465737433").expect("Invalid hex"), + FieldElement::from_hex_be("0x5465737431").unwrap(), + FieldElement::from_hex_be("0x5465737432").unwrap(), + FieldElement::from_hex_be("0x5465737433").unwrap(), ], - from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), - block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + from_address: FieldElement::from_hex_be("0x123").unwrap(), + block_hash: FieldElement::from_hex_be("0x456").unwrap(), block_number: 1, - transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; let expected_output = @@ -410,13 +410,13 @@ mod tests { keys: vec![ starknet_keccak("CustomEvent".as_bytes()), FieldElement::from(3u128), - FieldElement::from_hex_be("0x5465737431").expect("Invalid hex"), + FieldElement::from_hex_be("0x5465737431").unwrap(), ], data: vec![FieldElement::from(1u128), FieldElement::from(2u128)], - from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), - block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + from_address: FieldElement::from_hex_be("0x123").unwrap(), + block_hash: FieldElement::from_hex_be("0x456").unwrap(), block_number: 1, - transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; let expected_output = @@ -469,16 +469,16 @@ mod tests { let event = EmittedEvent { keys: vec![starknet_keccak("StoreDelRecord".as_bytes())], data: vec![ - FieldElement::from_hex_be("0x0").expect("Invalid hex"), + FieldElement::from_hex_be("0x0").unwrap(), FieldElement::from(3u128), - FieldElement::from_hex_be("0x0").expect("Invalid hex"), - FieldElement::from_hex_be("0x1").expect("Invalid hex"), - FieldElement::from_hex_be("0x2").expect("Invalid hex"), + FieldElement::from_hex_be("0x0").unwrap(), + FieldElement::from_hex_be("0x1").unwrap(), + FieldElement::from_hex_be("0x2").unwrap(), ], - from_address: FieldElement::from_hex_be("0x123").expect("Invalid hex"), - block_hash: FieldElement::from_hex_be("0x456").expect("Invalid hex"), + from_address: FieldElement::from_hex_be("0x123").unwrap(), + block_hash: FieldElement::from_hex_be("0x456").unwrap(), block_number: 1, - transaction_hash: FieldElement::from_hex_be("0x789").expect("Invalid hex"), + transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: \"0x0\"\nkeys: [\"0x0\", \"0x1\", \"0x2\"]\n".to_string(); From 03b0954bdd378735ae7a3ee69b9f0862281aba96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Mar 2024 07:41:34 +0100 Subject: [PATCH 06/10] fix tests --- bin/sozo/src/ops/events.rs | 68 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index 5bb007560b..efe72dd0d4 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -254,6 +254,33 @@ fn process_inners( #[cfg(test)] mod tests { + use camino::Utf8Path; + use clap::Parser; + use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; + use dojo_world::manifest::BaseManifest; + + #[test] + fn events_are_parsed_correctly() { + let arg = EventsArgs::parse_from(["event", "Event1,Event2", "--chunk-size", "1"]); + assert!(arg.events.unwrap().len() == 2); + assert!(arg.from_block.is_none()); + assert!(arg.to_block.is_none()); + assert!(arg.chunk_size == 1); + } + + #[test] + fn extract_events_work_as_expected() { + let manifest_dir = Utf8Path::new("../../examples/spawn-and-move").to_path_buf(); + let manifest = + BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR)) + .unwrap() + .into(); + let result = extract_events(&manifest, &manifest_dir).unwrap(); + + // we are just collection all events from manifest file so just verifying count should work + assert!(result.len() == 2); + } + use cainome::parser::tokens::{Array, Composite, CompositeInner, CompositeType}; use starknet::core::types::EmittedEvent; @@ -371,7 +398,7 @@ mod tests { }; let expected_output = - "Event name: dojo::world::world::TestEvent\nfelt252: Test1 \"0x5465737431\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n".to_string(); + "Event name: dojo::world::world::TestEvent\nfelt252: 0x5465737431 \"Test1\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n".to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -433,7 +460,7 @@ mod tests { }; let expected_output = - "Event name: dojo::world::world::StoreDelRecord\ntable: Test \"0x54657374\"\nkeys: [\"0x5465737431\", \"0x5465737432\", \"0x5465737433\"]\n".to_string(); + "Event name: dojo::world::world::StoreDelRecord\ntable: 0x54657374 \"Test\"\nkeys: [0x5465737431, 0x5465737432, 0x5465737433]\n".to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -506,7 +533,7 @@ mod tests { }; let expected_output = - "Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: Test1 \"0x5465737431\"\ndata_1: 1\ndata_2: 2\n".to_string(); + "Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: 0x5465737431 \"Test1\"\ndata_1: 1\ndata_2: 2\n".to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -567,7 +594,9 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; - let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: \"0x0\"\nkeys: [\"0x0\", \"0x1\", \"0x2\"]\n".to_string(); + let expected_output = + "Event name: dojo::world::world::StoreDelRecord\ntable: 0x0\nkeys: [0x0, 0x1, 0x2]\n" + .to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -577,34 +606,3 @@ mod tests { } } } - -#[cfg(test)] -mod test { - use camino::Utf8Path; - use clap::Parser; - use dojo_lang::compiler::{BASE_DIR, MANIFESTS_DIR}; - use dojo_world::manifest::BaseManifest; - - use super::*; - #[test] - fn events_are_parsed_correctly() { - let arg = EventsArgs::parse_from(["event", "Event1,Event2", "--chunk-size", "1"]); - assert!(arg.events.unwrap().len() == 2); - assert!(arg.from_block.is_none()); - assert!(arg.to_block.is_none()); - assert!(arg.chunk_size == 1); - } - - #[test] - fn extract_events_work_as_expected() { - let manifest_dir = Utf8Path::new("../../examples/spawn-and-move").to_path_buf(); - let manifest = - BaseManifest::load_from_path(&manifest_dir.join(MANIFESTS_DIR).join(BASE_DIR)) - .unwrap() - .into(); - let result = extract_events(&manifest, &manifest_dir).unwrap(); - - // we are just collection all events from manifest file so just verifying count should work - assert!(result.len() == 2); - } -} From 1c816caba69ce4696a96093367cdec20036e2caf Mon Sep 17 00:00:00 2001 From: glihm Date: Fri, 8 Mar 2024 13:35:17 -0600 Subject: [PATCH 07/10] fix: fmt and artifacts parsing for missing events --- Cargo.lock | 2 +- bin/sozo/src/ops/events.rs | 86 ++++++++++--------- .../manifests/deployments/KATANA.toml | 26 +++--- 3 files changed, 60 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31868d928e..1887e75fb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10995,7 +10995,7 @@ dependencies = [ "anyhow", "assert_fs", "async-trait", - "cainome 0.1.5 (git+https://github.com/cartridge-gg/cainome?tag=v0.2.2)", + "cainome 0.1.5", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-filesystem", diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index efe72dd0d4..fb6259d735 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -1,20 +1,19 @@ use std::collections::{HashMap, VecDeque}; use std::fs; -use crate::commands::events::EventsArgs; use anyhow::{anyhow, Context, Result}; use cainome::parser::tokens::{CompositeInner, CompositeInnerKind, CoreBasic, Token}; use cainome::parser::AbiParser; -use cairo_lang_starknet::abi; use camino::Utf8PathBuf; use dojo_lang::compiler::{DEPLOYMENTS_DIR, MANIFESTS_DIR}; use dojo_world::manifest::{DeployedManifest, ManifestMethods}; use dojo_world::metadata::Environment; -use starknet::core::types::FieldElement; -use starknet::core::types::{BlockId, EventFilter}; +use starknet::core::types::{BlockId, EventFilter, FieldElement}; use starknet::core::utils::{parse_cairo_short_string, starknet_keccak}; use starknet::providers::Provider; +use crate::commands::events::EventsArgs; + pub async fn execute( args: EventsArgs, env_metadata: Option, @@ -85,48 +84,50 @@ fn extract_events( ) -> Result>> { fn process_abi( events: &mut HashMap>, - abi_path: &Utf8PathBuf, - manifest_dir: &Utf8PathBuf, + full_abi_path: &Utf8PathBuf, ) -> Result<()> { - let full_abi_path = manifest_dir.join(abi_path); - let abi: abi::Contract = serde_json::from_str(&fs::read_to_string(full_abi_path)?)?; - - match serde_json::to_string(&abi) { - Ok(abi_str) => match AbiParser::tokens_from_abi_string(&abi_str, &HashMap::new()) { - Ok(tokens) => { - for token in tokens.structs { - if is_event(&token) { - let event_name = starknet_keccak(token.type_name().as_bytes()); - let vec = events.entry(event_name.to_string()).or_default(); - vec.push(token.clone()); - } + let abi_str = fs::read_to_string(full_abi_path)?; + + match AbiParser::tokens_from_abi_string(&abi_str, &HashMap::new()) { + Ok(tokens) => { + for token in tokens.structs { + if is_event(&token) { + let event_name = starknet_keccak(token.type_name().as_bytes()); + let vec = events.entry(event_name.to_string()).or_default(); + vec.push(token.clone()); } } - Err(e) => return Err(anyhow!("Error parsing ABI: {}", e)), - }, - Err(e) => return Err(anyhow!("Error serializing Contract to JSON: {}", e)), + } + Err(e) => return Err(anyhow!("Error parsing ABI: {}", e)), } + Ok(()) } let mut events_map = HashMap::new(); - if let Some(abi_path) = manifest.world.inner.abi() { - process_abi(&mut events_map, abi_path, manifest_dir)?; - } - for contract in &manifest.contracts { if let Some(abi_path) = contract.inner.abi() { - process_abi(&mut events_map, abi_path, manifest_dir)?; + let full_abi_path = manifest_dir.join(abi_path); + process_abi(&mut events_map, &full_abi_path)?; } } for model in &manifest.contracts { if let Some(abi_path) = model.inner.abi() { - process_abi(&mut events_map, abi_path, manifest_dir)?; + let full_abi_path = manifest_dir.join(abi_path); + process_abi(&mut events_map, &full_abi_path)?; } } + // Read the world and base ABI from scarb artifacts as the + // manifest does not include them. + let world_abi_path = manifest_dir.join("target/dev/dojo::world::world.json"); + process_abi(&mut events_map, &world_abi_path)?; + + let base_abi_path = manifest_dir.join("target/dev/dojo::base::base.json"); + process_abi(&mut events_map, &base_abi_path)?; + Ok(events_map) } @@ -161,10 +162,10 @@ fn parse_core_basic( "felt252" => { let hex = format!("{:#x}", value); match parse_cairo_short_string(value) { - Ok(parsed) if !parsed.is_empty() && include_felt_string => { + Ok(parsed) if !parsed.is_empty() && (include_felt_string && parsed.is_ascii()) => { Ok(format!("{} \"{}\"", hex, parsed)) } - _ => Ok(format!("{}", hex)), + _ => Ok(hex.to_string()), } } "bool" => { @@ -190,8 +191,9 @@ fn parse_event( let mut keys = VecDeque::from(event.keys.clone()); let event_hash = keys.pop_front().ok_or(anyhow!("Event hash missing"))?; - let events = - events_map.get(&event_hash.to_string()).ok_or(anyhow!("Events for hash not found"))?; + let events = events_map + .get(&event_hash.to_string()) + .ok_or(anyhow!("Events for hash not found: {:#x}", event_hash))?; for e in events { if let Token::Composite(composite) = e { @@ -397,8 +399,10 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; - let expected_output = - "Event name: dojo::world::world::TestEvent\nfelt252: 0x5465737431 \"Test1\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n".to_string(); + let expected_output = "Event name: dojo::world::world::TestEvent\nfelt252: 0x5465737431 \ + \"Test1\"\nbool: true\nu8: 1\nu16: 2\nu32: 3\nu64: 4\nu128: \ + 5\nusize: 6\nclass_hash: 0x54657374\ncontract_address: 0x54657374\n" + .to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -459,8 +463,9 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; - let expected_output = - "Event name: dojo::world::world::StoreDelRecord\ntable: 0x54657374 \"Test\"\nkeys: [0x5465737431, 0x5465737432, 0x5465737433]\n".to_string(); + let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: 0x54657374 \ + \"Test\"\nkeys: [0x5465737431, 0x5465737432, 0x5465737433]\n" + .to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -532,8 +537,9 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; - let expected_output = - "Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: 0x5465737431 \"Test1\"\ndata_1: 1\ndata_2: 2\n".to_string(); + let expected_output = "Event name: dojo::world::world::CustomEvent\nkey_1: 3\nkey_2: \ + 0x5465737431 \"Test1\"\ndata_1: 1\ndata_2: 2\n" + .to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); @@ -594,9 +600,9 @@ mod tests { transaction_hash: FieldElement::from_hex_be("0x789").unwrap(), }; - let expected_output = - "Event name: dojo::world::world::StoreDelRecord\ntable: 0x0\nkeys: [0x0, 0x1, 0x2]\n" - .to_string(); + let expected_output = "Event name: dojo::world::world::StoreDelRecord\ntable: 0x0\nkeys: \ + [0x0, 0x1, 0x2]\n" + .to_string(); let actual_output_option = parse_event(event, &events_map).expect("Failed to parse event"); diff --git a/examples/spawn-and-move/manifests/deployments/KATANA.toml b/examples/spawn-and-move/manifests/deployments/KATANA.toml index d925e16711..5fba7b8f9a 100644 --- a/examples/spawn-and-move/manifests/deployments/KATANA.toml +++ b/examples/spawn-and-move/manifests/deployments/KATANA.toml @@ -24,8 +24,8 @@ name = "dojo_examples::actions::actions" [[models]] kind = "DojoModel" -class_hash = "0x53672d63a83f40ab5f3aeec55d1541a98aa822f5b197a30fbbac28e6f98a7d8" -name = "dojo_examples::models::position" +class_hash = "0x764906a97ff3e532e82b154908b25711cdec1c692bf68e3aba2a3dd9964a15c" +name = "dojo_examples::models::moves" [[models.members]] name = "player" @@ -33,14 +33,19 @@ type = "ContractAddress" key = true [[models.members]] -name = "vec" -type = "Vec2" +name = "remaining" +type = "u8" +key = false + +[[models.members]] +name = "last_direction" +type = "Direction" key = false [[models]] kind = "DojoModel" -class_hash = "0x764906a97ff3e532e82b154908b25711cdec1c692bf68e3aba2a3dd9964a15c" -name = "dojo_examples::models::moves" +class_hash = "0x53672d63a83f40ab5f3aeec55d1541a98aa822f5b197a30fbbac28e6f98a7d8" +name = "dojo_examples::models::position" [[models.members]] name = "player" @@ -48,11 +53,6 @@ type = "ContractAddress" key = true [[models.members]] -name = "remaining" -type = "u8" -key = false - -[[models.members]] -name = "last_direction" -type = "Direction" +name = "vec" +type = "Vec2" key = false From c731c9ce07236b6bfb2b55ac0243209f92f31090 Mon Sep 17 00:00:00 2001 From: glihm Date: Fri, 8 Mar 2024 15:57:25 -0600 Subject: [PATCH 08/10] fix: adjust event count as now world and base are included --- bin/sozo/src/ops/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index fb6259d735..f5a1317622 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -279,8 +279,8 @@ mod tests { .into(); let result = extract_events(&manifest, &manifest_dir).unwrap(); - // we are just collection all events from manifest file so just verifying count should work - assert!(result.len() == 2); + // we are just collecting all events from manifest file so just verifying count should work + assert_eq!(result.len(), 12); } use cainome::parser::tokens::{Array, Composite, CompositeInner, CompositeType}; From 3b6a71c99ad18378f48a52a9e15ac297b17a54b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Mar 2024 12:45:31 +0100 Subject: [PATCH 09/10] return error from parse_and_print_events function --- bin/sozo/src/ops/events.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index efe72dd0d4..cff1f37d75 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -137,16 +137,12 @@ fn parse_and_print_events( println!("Continuation token: {:?}", res.continuation_token); println!("----------------------------------------------"); for event in res.events { - match parse_event(event.clone(), &events_map) { - Ok(Some(e)) => { - println!("{e}"); - } - Ok(None) => { - println!("No matching event found for {:?}", event); - } - Err(e) => { - println!("Error parsing event: {}", e); - } + let parsed_event = parse_event(event.clone(), &events_map) + .map_err(|e| anyhow!("Error parsing event: {}", e))?; + + match parsed_event { + Some(e) => println!("{e}"), + None => return Err(anyhow!("No matching event found for {:?}", event)), } } Ok(()) From 440cbb6c436a6ba2142d32d5834ef06ee03bb36b Mon Sep 17 00:00:00 2001 From: glihm Date: Tue, 12 Mar 2024 21:21:24 -0600 Subject: [PATCH 10/10] fix: ensure world address is always present to filter events --- bin/sozo/src/ops/events.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/sozo/src/ops/events.rs b/bin/sozo/src/ops/events.rs index 06568062bf..98c6fc3bb5 100644 --- a/bin/sozo/src/ops/events.rs +++ b/bin/sozo/src/ops/events.rs @@ -35,6 +35,7 @@ pub async fn execute( let chain_id = provider.chain_id().await?; let chain_id = parse_cairo_short_string(&chain_id).with_context(|| "Cannot parse chain_id as string")?; + let world_address = world.address(env_metadata.as_ref())?; let events_map = if !json { let deployed_manifest = manifest_dir @@ -60,7 +61,7 @@ pub async fn execute( events.map(|e| vec![e.iter().map(|event| starknet_keccak(event.as_bytes())).collect()]); let provider = starknet.provider(env_metadata.as_ref())?; - let event_filter = EventFilter { from_block, to_block, address: world.world_address, keys }; + let event_filter = EventFilter { from_block, to_block, address: Some(world_address), keys }; let res = provider.get_events(event_filter, continuation_token, chunk_size).await?;