From 9c892392d3a127af243010584928ede304935724 Mon Sep 17 00:00:00 2001 From: Piotr Figiela <77412592+Draggu@users.noreply.github.com> Date: Fri, 8 Mar 2024 13:15:25 +0100 Subject: [PATCH] Fix collecting events in cairo0 (#1836) Closes #1633 ## Introduced changes - Fixes events collecting in cairo0 ## Checklist - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md` --- CHANGELOG.md | 6 ++ .../execution/syscall_hooks.rs | 53 +++++++--- .../mod.rs | 12 +++ .../runtime.rs | 33 ++++--- .../cheatnet/tests/cheatcodes/spy_events.rs | 83 ++++++++++++++-- .../cheatnet/tests/contracts/src/events.cairo | 1 + .../src/events/spy_events_cairo0.cairo | 34 +++++++ .../data/contracts/spy_events_checker.cairo | 20 +++- crates/forge/tests/integration/spy_events.rs | 97 +++++++++++++++++++ 9 files changed, 304 insertions(+), 35 deletions(-) create mode 100644 crates/cheatnet/tests/contracts/src/events/spy_events_cairo0.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index 34849d4afc..861b6ac6ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Forge + +#### Fixed + +- Events emitted in cairo 0 contracts are now properly collected + ## [0.19.0] - 2024-03-06 ### Forge diff --git a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/syscall_hooks.rs b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/syscall_hooks.rs index a4784dede1..7fafaa22a4 100644 --- a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/syscall_hooks.rs +++ b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/syscall_hooks.rs @@ -1,25 +1,50 @@ -use blockifier::execution::syscalls::hint_processor::SyscallHintProcessor; - use crate::{ runtime_extensions::forge_runtime_extension::cheatcodes::spy_events::Event, state::CheatnetState, }; +use blockifier::execution::{ + call_info::OrderedEvent, deprecated_syscalls::hint_processor::DeprecatedSyscallHintProcessor, + syscalls::hint_processor::SyscallHintProcessor, +}; +use starknet_api::core::ContractAddress; + +pub trait SyscallHintProcessorExt { + fn contract_address(&self) -> ContractAddress; + fn last_event(&self) -> &OrderedEvent; +} + +impl SyscallHintProcessorExt for SyscallHintProcessor<'_> { + fn contract_address(&self) -> ContractAddress { + self.call.code_address.unwrap_or(self.call.storage_address) + } + fn last_event(&self) -> &OrderedEvent { + self.events.last().unwrap() + } +} + +impl SyscallHintProcessorExt for DeprecatedSyscallHintProcessor<'_> { + fn contract_address(&self) -> ContractAddress { + self.storage_address + } + fn last_event(&self) -> &OrderedEvent { + self.events.last().unwrap() + } +} pub fn emit_event_hook( - syscall_handler: &SyscallHintProcessor<'_>, + syscall_handler: &impl SyscallHintProcessorExt, cheatnet_state: &mut CheatnetState, ) { - let contract_address = syscall_handler - .call - .code_address - .unwrap_or(syscall_handler.call.storage_address); + let contract_address = syscall_handler.contract_address(); + let last_event = syscall_handler.last_event(); + let is_spied_on = cheatnet_state + .spies + .iter() + .any(|spy_on| spy_on.does_spy(contract_address)); - for spy_on in &cheatnet_state.spies { - if spy_on.does_spy(contract_address) { - let event = - Event::from_ordered_event(syscall_handler.events.last().unwrap(), contract_address); - cheatnet_state.detected_events.push(event); - break; - } + if is_spied_on { + cheatnet_state + .detected_events + .push(Event::from_ordered_event(last_event, contract_address)); } } diff --git a/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/mod.rs b/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/mod.rs index 6f3512274e..5412ded5f9 100644 --- a/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/mod.rs +++ b/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/mod.rs @@ -34,6 +34,7 @@ use self::runtime::{ }; use super::call_to_blockifier_runtime_extension::execution::entry_point::execute_call_entry_point; +use super::call_to_blockifier_runtime_extension::execution::syscall_hooks; use super::call_to_blockifier_runtime_extension::RuntimeState; pub mod runtime; @@ -159,6 +160,17 @@ impl<'a> DeprecatedExtensionLogic for DeprecatedCheatableStarknetRuntimeExtensio _ => Ok(SyscallHandlingResult::Forwarded), } } + + fn post_syscall_hook( + &mut self, + selector: &DeprecatedSyscallSelector, + extended_runtime: &mut Self::Runtime, + ) { + let syscall_handler = &extended_runtime.hint_handler; + if let DeprecatedSyscallSelector::EmitEvent = selector { + syscall_hooks::emit_event_hook(syscall_handler, self.cheatnet_state); + } + } } impl<'a> DeprecatedCheatableStarknetRuntimeExtension<'a> { diff --git a/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/runtime.rs b/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/runtime.rs index 50dd3ff02e..f0150bcd0b 100644 --- a/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/runtime.rs +++ b/crates/cheatnet/src/runtime_extensions/deprecated_cheatable_starknet_extension/runtime.rs @@ -1,18 +1,21 @@ use std::{any::Any, collections::HashMap}; -use blockifier::execution::hint_code; +use crate::runtime_extensions::cheatable_starknet_runtime_extension::stark_felt_from_ptr_immutable; +use anyhow::Result; use blockifier::execution::{ deprecated_syscalls::{ hint_processor::DeprecatedSyscallHintProcessor, DeprecatedSyscallSelector, }, + hint_code, syscalls::{hint_processor::SyscallExecutionError, SyscallResult}, }; use cairo_felt::Felt252; -use cairo_vm::hint_processor::builtin_hint_processor::hint_utils::get_ptr_from_var_name; use cairo_vm::{ hint_processor::{ - builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData, - hint_processor_definition::{HintProcessorLogic, HintReference}, + builtin_hint_processor::{ + builtin_hint_processor_definition::HintProcessorData, hint_utils::get_ptr_from_var_name, + }, + hint_processor_definition::{HintProcessor, HintProcessorLogic, HintReference}, }, serde::deserialize_program::ApTracking, types::{exec_scope::ExecutionScopes, relocatable::Relocatable}, @@ -24,12 +27,6 @@ use cairo_vm::{ }; use runtime::{SyscallHandlingResult, SyscallPtrAccess}; -use anyhow::Result; - -use cairo_vm::hint_processor::hint_processor_definition::HintProcessor; - -use crate::runtime_extensions::cheatable_starknet_runtime_extension::stark_felt_from_ptr_immutable; - pub struct DeprecatedStarknetRuntime<'a> { pub hint_handler: DeprecatedSyscallHintProcessor<'a>, } @@ -161,8 +158,14 @@ impl DeprecatedExtendedRuntime { { Ok(()) } else { - self.extended_runtime - .execute_hint(vm, exec_scopes, hint_data, constants) + let res = self + .extended_runtime + .execute_hint(vm, exec_scopes, hint_data, constants); + + self.extension + .post_syscall_hook(&selector, &mut self.extended_runtime); + + res } } } @@ -208,4 +211,10 @@ pub trait DeprecatedExtensionLogic { ) -> Result { Ok(SyscallHandlingResult::Forwarded) } + + fn post_syscall_hook( + &mut self, + _selector: &DeprecatedSyscallSelector, + _extended_runtime: &mut Self::Runtime, + ); } diff --git a/crates/cheatnet/tests/cheatcodes/spy_events.rs b/crates/cheatnet/tests/cheatcodes/spy_events.rs index b85423592f..371303e010 100644 --- a/crates/cheatnet/tests/cheatcodes/spy_events.rs +++ b/crates/cheatnet/tests/cheatcodes/spy_events.rs @@ -1,16 +1,24 @@ -use crate::common::state::{build_runtime_state, create_cached_state}; -use crate::common::{call_contract, deploy_wrapper}; -use crate::common::{deploy_contract, felt_selector_from_name, get_contracts}; -use cairo_felt::Felt252; +use crate::common::{ + call_contract, deploy_contract, deploy_wrapper, felt_selector_from_name, get_contracts, + state::{build_runtime_state, create_cached_state}, +}; +use blockifier::state::cached_state::{CachedState, GlobalContractCache}; +use cairo_felt::{felt_str, Felt252}; use cairo_lang_starknet::contract::starknet_keccak; use cairo_vm::hint_processor::hint_processor_utils::felt_to_usize; -use cheatnet::runtime_extensions::forge_runtime_extension::cheatcodes::declare::declare; -use cheatnet::runtime_extensions::forge_runtime_extension::cheatcodes::spy_events::{ - Event, SpyTarget, +use cheatnet::{ + constants::build_testing_state, + forking::state::ForkStateReader, + runtime_extensions::forge_runtime_extension::cheatcodes::{ + declare::declare, + spy_events::{Event, SpyTarget}, + }, + state::{CheatnetState, ExtendedStateReader}, }; -use cheatnet::state::CheatnetState; use conversions::IntoConv; +use starknet_api::block::BlockNumber; use std::vec; +use tempfile::TempDir; pub fn felt_vec_to_event_vec(felts: &[Felt252]) -> Vec { let mut events = vec![]; @@ -625,3 +633,62 @@ fn test_emitted_by_emit_events_syscall() { "Wrong spy_events_checker event" ); } +#[test] +fn capture_cairo0_event() { + let temp_dir = TempDir::new().unwrap(); + let mut cached_state = CachedState::new( + ExtendedStateReader { + dict_state_reader: build_testing_state(), + fork_state_reader: Some(ForkStateReader::new( + "http://188.34.188.184:6060/rpc/v0_6".parse().unwrap(), + BlockNumber(960_107), + temp_dir.path().to_str().unwrap(), + )), + }, + GlobalContractCache::default(), + ); + let mut cheatnet_state = CheatnetState::default(); + let mut runtime_state = build_runtime_state(&mut cheatnet_state); + + let contract_address = deploy_contract( + &mut cached_state, + &mut runtime_state, + "SpyEventsCairo0", + &[], + ); + + let id = runtime_state.cheatnet_state.spy_events(SpyTarget::All); + + let selector = felt_selector_from_name("test_cairo0_event_collection"); + + let cairo0_contract_address = felt_str!( + "1960625ba5c435bac113ecd15af3c60e327d550fc5dbb43f07cd0875ad2f54c", + 16 + ); + + call_contract( + &mut cached_state, + &mut runtime_state, + &contract_address, + &selector, + &[cairo0_contract_address.clone()], + ); + + let (length, serialized_events) = runtime_state + .cheatnet_state + .fetch_events(&Felt252::from(id)); + + let events = felt_vec_to_event_vec(&serialized_events); + + assert_eq!(length, 1, "There should be one event"); + + assert_eq!( + events[0], + Event { + from: cairo0_contract_address.into_(), + keys: vec![starknet_keccak("my_event".as_ref()).into()], + data: vec![Felt252::from(123_456_789)] + }, + "Wrong spy_events_checker event" + ); +} diff --git a/crates/cheatnet/tests/contracts/src/events.cairo b/crates/cheatnet/tests/contracts/src/events.cairo index f97dc3b192..ca55785b27 100644 --- a/crates/cheatnet/tests/contracts/src/events.cairo +++ b/crates/cheatnet/tests/contracts/src/events.cairo @@ -3,3 +3,4 @@ mod spy_events_order_checker; mod spy_events_lib_call; mod constructor_spy_events_checker; mod spy_events_checker_proxy; +mod spy_events_cairo0; diff --git a/crates/cheatnet/tests/contracts/src/events/spy_events_cairo0.cairo b/crates/cheatnet/tests/contracts/src/events/spy_events_cairo0.cairo new file mode 100644 index 0000000000..46090ce7f9 --- /dev/null +++ b/crates/cheatnet/tests/contracts/src/events/spy_events_cairo0.cairo @@ -0,0 +1,34 @@ +use starknet::ContractAddress; + +// https://testnet.starkscan.co/contract/0x1960625ba5c435bac113ecd15af3c60e327d550fc5dbb43f07cd0875ad2f54c +#[starknet::interface] +trait ICairo0Contract { + // this function only job is to emit `my_event` with single felt252 value + fn emit_one_cairo0_event(ref self: TContractState, contract_address: felt252); +} + +#[starknet::interface] +trait ISpyEventsCairo0 { + fn test_cairo0_event_collection(ref self: TContractState, cairo0_address: ContractAddress); +} + +#[starknet::contract] +mod SpyEventsCairo0 { + use core::traits::Into; + use starknet::{get_contract_address, get_caller_address, ContractAddress}; + use super::ICairo0ContractDispatcherTrait; + + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl ISpyEventsCairo0 of super::ISpyEventsCairo0 { + fn test_cairo0_event_collection(ref self: ContractState, cairo0_address: ContractAddress) { + let cairo0_contract = super::ICairo0ContractDispatcher { + contract_address: cairo0_address + }; + + cairo0_contract.emit_one_cairo0_event(123456789); + } + } +} diff --git a/crates/forge/tests/data/contracts/spy_events_checker.cairo b/crates/forge/tests/data/contracts/spy_events_checker.cairo index 5d7ae6640c..4fe508f56c 100644 --- a/crates/forge/tests/data/contracts/spy_events_checker.cairo +++ b/crates/forge/tests/data/contracts/spy_events_checker.cairo @@ -1,5 +1,12 @@ use starknet::ContractAddress; +// https://testnet.starkscan.co/contract/0x1960625ba5c435bac113ecd15af3c60e327d550fc5dbb43f07cd0875ad2f54c +#[starknet::interface] +trait ICairo0Contract { + // this function only job is to emit `my_event` with single felt252 value + fn emit_one_cairo0_event(ref self: TContractState, contract_address: felt252); +} + #[starknet::interface] trait ISpyEventsChecker { fn do_not_emit(ref self: TContractState); @@ -14,12 +21,14 @@ trait ISpyEventsChecker { even_more_data: u256 ); fn emit_event_syscall(ref self: TContractState, some_key: felt252, some_data: felt252); + fn test_cairo0_event_collection(ref self: TContractState, cairo0_address: ContractAddress); } #[starknet::contract] mod SpyEventsChecker { use starknet::ContractAddress; use starknet::SyscallResultTrait; + use super::ICairo0ContractDispatcherTrait; #[storage] struct Storage {} @@ -78,7 +87,16 @@ mod SpyEventsChecker { } fn emit_event_syscall(ref self: ContractState, some_key: felt252, some_data: felt252) { - starknet::emit_event_syscall(array![some_key].span(), array![some_data].span()).unwrap_syscall(); + starknet::emit_event_syscall(array![some_key].span(), array![some_data].span()) + .unwrap_syscall(); + } + + fn test_cairo0_event_collection(ref self: ContractState, cairo0_address: ContractAddress) { + let cairo0_contract = super::ICairo0ContractDispatcher { + contract_address: cairo0_address + }; + + cairo0_contract.emit_one_cairo0_event(123456789); } } } diff --git a/crates/forge/tests/integration/spy_events.rs b/crates/forge/tests/integration/spy_events.rs index 1dfede6dea..f450150194 100644 --- a/crates/forge/tests/integration/spy_events.rs +++ b/crates/forge/tests/integration/spy_events.rs @@ -617,3 +617,100 @@ fn assert_not_emitted_fails() { ); assert_case_output_contains(&result, "assert_not_emitted_fails", "keys was emitted"); } + +#[test] +fn capture_cairo0_event() { + let test = test_case!( + indoc!( + r#" + use array::ArrayTrait; + use result::ResultTrait; + use starknet::{ContractAddress, contract_address_const}; + use snforge_std::{ declare, ContractClassTrait, spy_events, EventSpy, EventFetcher, + event_name_hash, EventAssertions, SpyOn }; + + #[starknet::interface] + trait ISpyEventsChecker { + fn emit_one_event(ref self: TContractState, some_data: felt252); + fn test_cairo0_event_collection(ref self: TContractState, cairo0_address: felt252); + } + + #[starknet::contract] + mod SpyEventsChecker { + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + FirstEvent: FirstEvent, + my_event: Cairo0Event, + } + + #[derive(Drop, starknet::Event)] + struct FirstEvent { + some_data: felt252 + } + + #[derive(Drop, starknet::Event)] + struct Cairo0Event { + some_data: felt252 + } + } + + #[test] + #[fork(url: "http://188.34.188.184:6060/rpc/v0_6", block_id: BlockId::Tag(BlockTag::Latest))] + fn capture_cairo0_event() { + let cairo0_contract_address = contract_address_const::<0x1960625ba5c435bac113ecd15af3c60e327d550fc5dbb43f07cd0875ad2f54c>(); + let contract = declare("SpyEventsChecker"); + let contract_address = contract.deploy(@ArrayTrait::new()).unwrap(); + let dispatcher = ISpyEventsCheckerDispatcher { contract_address }; + + let mut spy = spy_events(SpyOn::All); + + dispatcher.test_cairo0_event_collection(cairo0_contract_address.into()); + dispatcher.emit_one_event(420); + dispatcher.test_cairo0_event_collection(cairo0_contract_address.into()); + + spy.assert_emitted(@array![ + ( + cairo0_contract_address, + SpyEventsChecker::Event::my_event( + SpyEventsChecker::Cairo0Event { + some_data: 123456789 + } + ) + ), + ( + contract_address, + SpyEventsChecker::Event::FirstEvent( + SpyEventsChecker::FirstEvent { + some_data: 420 + } + ) + ), + ( + cairo0_contract_address, + SpyEventsChecker::Event::my_event( + SpyEventsChecker::Cairo0Event { + some_data: 123456789 + } + ) + ) + ]); + } + "# + ), + Contract::from_code_path( + "SpyEventsChecker".to_string(), + Path::new("tests/data/contracts/spy_events_checker.cairo"), + ) + .unwrap() + ); + + let result = run_test_case(&test); + + assert_passed(&result); +}