diff --git a/corelib/src/starknet/testing.cairo b/corelib/src/starknet/testing.cairo index b04dfc16394..b4c5bbcd6c8 100644 --- a/corelib/src/starknet/testing.cairo +++ b/corelib/src/starknet/testing.cairo @@ -12,3 +12,6 @@ extern fn set_transaction_hash(hash: felt252) implicits() nopanic; extern fn set_chain_id(chain_id: felt252) implicits() nopanic; extern fn set_nonce(nonce: felt252) implicits() nopanic; extern fn set_signature(signature: Span) implicits() nopanic; +extern fn pop_log( + address: ContractAddress +) -> Option<(Span, Span)> implicits() nopanic; diff --git a/crates/cairo-lang-casm/src/hints/mod.rs b/crates/cairo-lang-casm/src/hints/mod.rs index 4c8e4a2394b..3ffbff18b00 100644 --- a/crates/cairo-lang-casm/src/hints/mod.rs +++ b/crates/cairo-lang-casm/src/hints/mod.rs @@ -71,6 +71,15 @@ pub enum StarknetHint { SetNonce { value: ResOperand }, #[codec(index = 12)] SetSignature { start: ResOperand, end: ResOperand }, + #[codec(index = 13)] + PopLog { + value: ResOperand, + opt_variant: CellRef, + keys_start: CellRef, + keys_end: CellRef, + data_start: CellRef, + data_end: CellRef, + }, } // Represents a cairo core hint. @@ -760,6 +769,16 @@ impl Display for StarknetHint { ResOperandFormatter(end) ) } + StarknetHint::PopLog { + value: _, + opt_variant: _, + keys_start: _, + keys_end: _, + data_start: _, + data_end: _, + } => { + write!(f, "raise NotImplemented") + } } } } diff --git a/crates/cairo-lang-runner/src/casm_run/mod.rs b/crates/cairo-lang-runner/src/casm_run/mod.rs index 7d04b09310c..b9c9077c510 100644 --- a/crates/cairo-lang-runner/src/casm_run/mod.rs +++ b/crates/cairo-lang-runner/src/casm_run/mod.rs @@ -1,6 +1,6 @@ use std::any::Any; use std::borrow::Cow; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use std::ops::{Deref, Shl}; use ark_ff::fields::{Fp256, MontBackend, MontConfig}; @@ -139,6 +139,9 @@ macro_rules! insert_value_to_cellref { }; } +// Log type signature +type Log = (Vec, Vec); + /// Execution scope for starknet related data. /// All values will be 0 and by default if not setup by the test. #[derive(Clone, Default)] @@ -148,6 +151,8 @@ pub struct StarknetState { /// A mapping from contract address to class hash. #[allow(dead_code)] deployed_contracts: HashMap, + /// A mapping from contract address to logs. + logs: HashMap>, /// The simulated execution info. exec_info: ExecutionInfo, next_id: Felt252, @@ -382,6 +387,38 @@ impl HintProcessor for CairoHintProcessor<'_> { let end = get_ptr(vm, cell, &offset)?; self.starknet_state.exec_info.tx_info.signature = vm_get_range(vm, start, end)?; } + StarknetHint::PopLog { + value, + opt_variant, + keys_start, + keys_end, + data_start, + data_end, + } => { + let contract_address = get_val(vm, value)?; + let mut res_segment = MemBuffer::new_segment(vm); + let logs = self.starknet_state.logs.entry(contract_address).or_default(); + + if let Some((keys, data)) = logs.pop_front() { + let keys_start_ptr = res_segment.ptr; + res_segment.write_data(keys.iter())?; + let keys_end_ptr = res_segment.ptr; + + let data_start_ptr = res_segment.ptr; + res_segment.write_data(data.iter())?; + let data_end_ptr = res_segment.ptr; + + // Option::Some variant + insert_value_to_cellref!(vm, opt_variant, 0)?; + insert_value_to_cellref!(vm, keys_start, keys_start_ptr)?; + insert_value_to_cellref!(vm, keys_end, keys_end_ptr)?; + insert_value_to_cellref!(vm, data_start, data_start_ptr)?; + insert_value_to_cellref!(vm, data_end, data_end_ptr)?; + } else { + // Option::None variant + insert_value_to_cellref!(vm, opt_variant, 1)?; + } + } }; Ok(()) } @@ -592,10 +629,7 @@ impl<'a> CairoHintProcessor<'a> { self.get_execution_info(gas_counter, system_buffer) }), "EmitEvent" => execute_handle_helper(&mut |system_buffer, gas_counter| { - let _keys = system_buffer.next_arr()?; - let _values = system_buffer.next_arr()?; - deduct_gas!(gas_counter, 50); - Ok(SyscallResult::Success(vec![])) + self.emit_event(gas_counter, system_buffer.next_arr()?, system_buffer.next_arr()?) }), "SendMessageToL1" => execute_handle_helper(&mut |system_buffer, gas_counter| { let _to_address = system_buffer.next_felt252()?; @@ -796,6 +830,19 @@ impl<'a> CairoHintProcessor<'a> { Ok(SyscallResult::Success(vec![exec_info_ptr.into()])) } + /// Executes the `emit_event_syscall` syscall. + fn emit_event( + &mut self, + gas_counter: &mut usize, + keys: Vec, + data: Vec, + ) -> Result { + deduct_gas!(gas_counter, 50); + let contract = self.starknet_state.exec_info.contract_address.clone(); + self.starknet_state.logs.entry(contract).or_default().push_front((keys, data)); + Ok(SyscallResult::Success(vec![])) + } + /// Executes the `deploy_syscall` syscall. fn deploy( &mut self, diff --git a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs index a5ffdcca61a..3e89600e41c 100644 --- a/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs +++ b/crates/cairo-lang-sierra-ap-change/src/core_libfunc_ap_change.rs @@ -25,6 +25,7 @@ use cairo_lang_sierra::extensions::mem::MemConcreteLibfunc; use cairo_lang_sierra::extensions::nullable::NullableConcreteLibfunc; use cairo_lang_sierra::extensions::pedersen::PedersenConcreteLibfunc; use cairo_lang_sierra::extensions::poseidon::PoseidonConcreteLibfunc; +use cairo_lang_sierra::extensions::starknet::testing::TestingConcreteLibfunc; use cairo_lang_sierra::extensions::starknet::StarkNetConcreteLibfunc; use cairo_lang_sierra::extensions::structure::StructConcreteLibfunc; use cairo_lang_sierra::ids::ConcreteTypeId; @@ -242,7 +243,10 @@ pub fn core_libfunc_ap_change( | StarkNetConcreteLibfunc::Secp256(_) => { vec![ApChange::Known(2), ApChange::Known(2)] } - StarkNetConcreteLibfunc::Testing(_) => vec![ApChange::Known(0)], + StarkNetConcreteLibfunc::Testing(libfunc) => match libfunc { + TestingConcreteLibfunc::PopLog(_) => vec![ApChange::Known(5), ApChange::Known(5)], + _ => vec![ApChange::Known(0)], + }, }, CoreConcreteLibfunc::Nullable(libfunc) => match libfunc { NullableConcreteLibfunc::Null(_) => vec![ApChange::Known(0)], diff --git a/crates/cairo-lang-sierra-gas/src/starknet_libfunc_cost_base.rs b/crates/cairo-lang-sierra-gas/src/starknet_libfunc_cost_base.rs index 75cc0f60ca8..c16b7468fed 100644 --- a/crates/cairo-lang-sierra-gas/src/starknet_libfunc_cost_base.rs +++ b/crates/cairo-lang-sierra-gas/src/starknet_libfunc_cost_base.rs @@ -3,6 +3,7 @@ use std::vec; use cairo_lang_sierra::extensions::starknet::secp256::{ Secp256ConcreteLibfunc, Secp256OpConcreteLibfunc, }; +use cairo_lang_sierra::extensions::starknet::testing::TestingConcreteLibfunc; use cairo_lang_sierra::extensions::starknet::StarkNetConcreteLibfunc; use crate::objects::ConstCost; @@ -46,7 +47,10 @@ pub fn starknet_libfunc_cost_base(libfunc: &StarkNetConcreteLibfunc) -> Vec syscall_cost(4), StarkNetConcreteLibfunc::ReplaceClass(_) => syscall_cost(1), StarkNetConcreteLibfunc::SendMessageToL1(_) => syscall_cost(3), - StarkNetConcreteLibfunc::Testing(_) => vec![steps(1)], + StarkNetConcreteLibfunc::Testing(libfunc) => match libfunc { + TestingConcreteLibfunc::PopLog(_) => vec![steps(2), steps(2)], + _ => vec![steps(1)], + }, StarkNetConcreteLibfunc::Secp256(libfunc) => { match libfunc { Secp256ConcreteLibfunc::K1(libfunc) => match libfunc { diff --git a/crates/cairo-lang-sierra-to-casm/src/invocations/starknet/testing.rs b/crates/cairo-lang-sierra-to-casm/src/invocations/starknet/testing.rs index b994dded346..71384bdb7fb 100644 --- a/crates/cairo-lang-sierra-to-casm/src/invocations/starknet/testing.rs +++ b/crates/cairo-lang-sierra-to-casm/src/invocations/starknet/testing.rs @@ -4,8 +4,8 @@ use cairo_lang_casm::hints::StarknetHint; use cairo_lang_sierra::extensions::starknet::testing::TestingConcreteLibfunc; use crate::invocations::{ - add_input_variables, CompiledInvocation, CompiledInvocationBuilder, CostValidationInfo, - InvocationError, + add_input_variables, get_non_fallthrough_statement_id, CompiledInvocation, + CompiledInvocationBuilder, CostValidationInfo, InvocationError, }; /// Builds instructions for starknet test setup operations. @@ -72,8 +72,39 @@ pub fn build( hint StarknetHint::SetSignature { start: start, end: end }; }; } + TestingConcreteLibfunc::PopLog(_) => { + let address = declare_single_value()?; + + casm_build_extend! {casm_builder, + tempvar variant; + tempvar keys_start; + tempvar keys_end; + tempvar data_start; + tempvar data_end; + hint StarknetHint::PopLog { + value: address + } into { + opt_variant: variant, keys_start: keys_start, + keys_end: keys_end, data_start: data_start, + data_end: data_end + }; + ap += 5; + jump None if variant != 0; + }; + + let none_variant_id = get_non_fallthrough_statement_id(&builder); + return Ok(builder.build_from_casm_builder( + casm_builder, + [ + ("Fallthrough", &[&[keys_start, keys_end], &[data_start, data_end]], None), + ("None", &[], Some(none_variant_id)), + ], + CostValidationInfo::default(), + )); + } } casm_build_extend! {casm_builder, ap += 0; }; + Ok(builder.build_from_casm_builder( casm_builder, [("Fallthrough", &[], None)], diff --git a/crates/cairo-lang-sierra/src/extensions/modules/starknet/testing.rs b/crates/cairo-lang-sierra/src/extensions/modules/starknet/testing.rs index 0a55aa63847..9d5f91f100c 100644 --- a/crates/cairo-lang-sierra/src/extensions/modules/starknet/testing.rs +++ b/crates/cairo-lang-sierra/src/extensions/modules/starknet/testing.rs @@ -7,10 +7,12 @@ use crate::extensions::felt252::Felt252Type; use crate::extensions::int::unsigned::Uint64Type; use crate::extensions::int::unsigned128::Uint128Type; use crate::extensions::lib_func::{ - LibfuncSignature, SierraApChange, SignatureSpecializationContext, + BranchSignature, DeferredOutputKind, LibfuncSignature, OutputVarInfo, ParamSignature, + SierraApChange, SignatureSpecializationContext, }; use crate::extensions::{ - NamedType, NoGenericArgsGenericLibfunc, NoGenericArgsGenericType, SpecializationError, + NamedType, NoGenericArgsGenericLibfunc, NoGenericArgsGenericType, OutputVarReferenceInfo, + SpecializationError, }; use crate::ids::ConcreteTypeId; /// Trait for implementing test setters. @@ -151,6 +153,49 @@ impl TestSetterTraits for SetSignatureTrait { } } +#[derive(Default)] +pub struct PopLogLibfunc {} + +impl NoGenericArgsGenericLibfunc for PopLogLibfunc { + const STR_ID: &'static str = "pop_log"; + + fn specialize_signature( + &self, + context: &dyn SignatureSpecializationContext, + ) -> Result { + let contract_address_ty = context.get_concrete_type(ContractAddressType::id(), &[])?; + let span_ty = felt252_span_ty(context)?; + + Ok(LibfuncSignature { + param_signatures: vec![ParamSignature::new(contract_address_ty)], + branch_signatures: vec![ + // Some variant branch. + BranchSignature { + vars: vec![ + // keys + OutputVarInfo { + ty: span_ty.clone(), + ref_info: OutputVarReferenceInfo::Deferred(DeferredOutputKind::Generic), + }, + // data + OutputVarInfo { + ty: span_ty, + ref_info: OutputVarReferenceInfo::Deferred(DeferredOutputKind::Generic), + }, + ], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + // None variant branch. + BranchSignature { + vars: vec![], + ap_change: SierraApChange::Known { new_vars_only: false }, + }, + ], + fallthrough: Some(0), + }) + } +} + define_libfunc_hierarchy! { pub enum TestingLibfunc { SetBlockNumber(TestSetterLibfunc), @@ -165,5 +210,6 @@ define_libfunc_hierarchy! { SetChainId(TestSetterLibfunc), SetNonce(TestSetterLibfunc), SetSignature(TestSetterLibfunc), + PopLog(PopLogLibfunc), }, TestingConcreteLibfunc } diff --git a/crates/cairo-lang-starknet/cairo_level_tests/contract_tests.cairo b/crates/cairo-lang-starknet/cairo_level_tests/contract_tests.cairo index d6b77cc869a..ee60f9b1a90 100644 --- a/crates/cairo-lang-starknet/cairo_level_tests/contract_tests.cairo +++ b/crates/cairo-lang-starknet/cairo_level_tests/contract_tests.cairo @@ -305,6 +305,32 @@ fn test_get_signature() { assert_eq(read_signature.at(1), @'signature', 'unexpected element 1'); } +#[test] +#[available_gas(300000)] +fn test_pop_log() { + let contract_address = starknet::contract_address_const::<0x1234>(); + starknet::testing::set_contract_address(contract_address); + let mut keys = Default::default(); + let mut data = Default::default(); + keys.append(1234); + data.append(2345); + starknet::emit_event_syscall(keys.span(), data.span()); + let (keys, data) = starknet::testing::pop_log(contract_address).unwrap(); + + assert_eq(@keys.len(), @1, 'unexpected keys size'); + assert_eq(@data.len(), @1, 'unexpected data size'); + assert_eq(keys.at(0), @1234, 'unexpected key'); + assert_eq(data.at(0), @2345, 'unexpected data'); +} + +#[test] +#[available_gas(300000)] +#[should_panic] +fn test_pop_log_empty_logs() { + let contract_address = starknet::contract_address_const::<0x1234>(); + starknet::testing::pop_log(contract_address).unwrap(); +} + #[test] #[should_panic] fn test_out_of_range_storage_address_from_felt252() -> starknet::StorageAddress { diff --git a/crates/cairo-lang-starknet/src/allowed_libfuncs_test.rs b/crates/cairo-lang-starknet/src/allowed_libfuncs_test.rs index 7e4b131f55a..c364ae9ed6c 100644 --- a/crates/cairo-lang-starknet/src/allowed_libfuncs_test.rs +++ b/crates/cairo-lang-starknet/src/allowed_libfuncs_test.rs @@ -24,6 +24,7 @@ fn experimental_list_includes_all() { "set_chain_id", "set_nonce", "set_signature", + "pop_log", "get_available_gas", ]; pretty_assertions::assert_eq!(