Skip to content

Commit

Permalink
feat(blockifier): add emit_event cairo native syscall
Browse files Browse the repository at this point in the history
  • Loading branch information
PearsonWhite committed Oct 30, 2024
1 parent ded98cf commit 7e3b4f6
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 14 deletions.
33 changes: 28 additions & 5 deletions crates/blockifier/src/execution/native/syscall_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ use cairo_native::starknet::{
};
use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
use starknet_api::state::StorageKey;
use starknet_api::transaction::{EventContent, EventData, EventKey};
use starknet_types_core::felt::Felt;

use crate::execution::call_info::{CallInfo, OrderedEvent, OrderedL2ToL1Message, Retdata};
use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext};
use crate::execution::native::utils::encode_str_as_felts;
use crate::execution::syscalls::hint_processor::{SyscallCounter, OUT_OF_GAS_ERROR};
use crate::execution::syscalls::SyscallSelector;
use crate::execution::syscalls::{exceeds_event_size_limit, SyscallSelector};
use crate::state::state_api::State;

pub struct NativeSyscallHandler<'state> {
Expand Down Expand Up @@ -203,11 +204,33 @@ impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> {

fn emit_event(
&mut self,
_keys: &[Felt],
_data: &[Felt],
_remaining_gas: &mut u128,
keys: &[Felt],
data: &[Felt],
remaining_gas: &mut u128,
) -> SyscallResult<()> {
todo!("Implement emit_event syscall.");
self.substract_syscall_gas_cost(
remaining_gas,
SyscallSelector::EmitEvent,
self.context.gas_costs().emit_event_gas_cost,
)?;

let order = self.context.n_emitted_events;
let event = EventContent {
keys: keys.iter().copied().map(EventKey).collect(),
data: EventData(data.to_vec()),
};

exceeds_event_size_limit(
self.context.versioned_constants(),
self.context.n_emitted_events + 1,
&event,
)
.map_err(|e| encode_str_as_felts(&e.to_string()))?;

self.events.push(OrderedEvent { order, event });
self.context.n_emitted_events += 1;

Ok(())
}

fn send_message_to_l1(
Expand Down
20 changes: 20 additions & 0 deletions crates/blockifier/src/execution/native/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use cairo_lang_starknet_classes::contract_class::ContractEntryPoint;
use itertools::Itertools;
use starknet_api::core::EntryPointSelector;
use starknet_types_core::felt::Felt;

Expand All @@ -20,3 +21,22 @@ pub fn encode_str_as_felts(msg: &str) -> Vec<Felt> {
}
encoding
}

pub fn decode_felts_as_str(encoding: &[Felt]) -> String {
let bytes_err: Vec<_> =
encoding.iter().flat_map(|felt| felt.to_bytes_be()[1..32].to_vec()).collect();

match String::from_utf8(bytes_err) {
Ok(s) => s.trim_matches('\0').to_owned(),
Err(_) => {
let err_msgs = encoding
.iter()
.map(|felt| match String::from_utf8(felt.to_bytes_be()[1..32].to_vec()) {
Ok(s) => format!("{} ({})", s.trim_matches('\0'), felt),
Err(_) => felt.to_string(),
})
.join(", ");
format!("[{}]", err_msgs)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::context::ChainInfo;
use crate::execution::call_info::{CallExecution, CallInfo, OrderedEvent};
use crate::execution::entry_point::CallEntryPoint;
use crate::execution::errors::EntryPointExecutionError;
use crate::execution::native::utils::decode_felts_as_str;
use crate::execution::syscalls::hint_processor::EmitEventError;
use crate::test_utils::contracts::FeatureContract;
use crate::test_utils::initial_test_state::test_state;
Expand All @@ -24,11 +25,11 @@ const DATA: [Felt; 3] = [
];
const N_EMITTED_EVENTS: [Felt; 1] = [Felt::from_hex_unchecked("0x1")];

#[test_case(FeatureContract::TestContract(CairoVersion::Native), 58930; "Native")]
#[test_case(FeatureContract::TestContract(CairoVersion::Cairo1), 48930; "VM")]
fn positive_flow(test_contract: FeatureContract, expected_gas: u64) {
// TODO(Ori, 1/2/2024): Write an indicative expect message explaining why the conversion
// works.
let call_info = emit_events(test_contract, &N_EMITTED_EVENTS, &KEYS, &DATA).unwrap();
let call_info = emit_events(test_contract, &N_EMITTED_EVENTS, &KEYS, &DATA)
.expect("emit_events failed with valued parameters");
let event = EventContent {
keys: KEYS.into_iter().map(EventKey).collect(),
data: EventData(DATA.to_vec()),
Expand All @@ -44,35 +45,58 @@ fn positive_flow(test_contract: FeatureContract, expected_gas: u64) {
);
}

#[test_case(FeatureContract::TestContract(CairoVersion::Native); "Native")]
#[test_case(FeatureContract::TestContract(CairoVersion::Cairo1); "VM")]
fn data_length_exceeds_limit(test_contract: FeatureContract) {
let versioned_constants = VersionedConstants::create_for_testing();

let max_event_data_length = versioned_constants.tx_event_limits.max_data_length;
let data_too_long = vec![felt!(2_u16); max_event_data_length + 1];
let error = emit_events(test_contract, &N_EMITTED_EVENTS, &KEYS, &data_too_long).unwrap_err();

let call_result = emit_events(test_contract, &N_EMITTED_EVENTS, &KEYS, &data_too_long);
let error_message =
if matches!(test_contract, FeatureContract::TestContract(CairoVersion::Native)) {
let call_info = call_result
.expect("Expected CallResult with failed execution error message in the retdata.");
decode_felts_as_str(&call_info.execution.retdata.0)
} else {
let error = call_result.unwrap_err();
error.to_string()
};
let expected_error = EmitEventError::ExceedsMaxDataLength {
data_length: max_event_data_length + 1,
max_data_length: max_event_data_length,
};
assert!(error.to_string().contains(&expected_error.to_string()));
assert!(error_message.contains(&expected_error.to_string()));
}

#[test_case(FeatureContract::TestContract(CairoVersion::Native); "Native")]
#[test_case(FeatureContract::TestContract(CairoVersion::Cairo1); "VM")]
fn keys_length_exceeds_limit(test_contract: FeatureContract) {
let versioned_constants = VersionedConstants::create_for_testing();

let max_event_keys_length = versioned_constants.tx_event_limits.max_keys_length;
let keys_too_long = vec![felt!(1_u16); max_event_keys_length + 1];
let error = emit_events(test_contract, &N_EMITTED_EVENTS, &keys_too_long, &DATA).unwrap_err();

let call_result = emit_events(test_contract, &N_EMITTED_EVENTS, &keys_too_long, &DATA);
let error_message =
if matches!(test_contract, FeatureContract::TestContract(CairoVersion::Native)) {
let call_info = call_result
.expect("Expected CallResult with failed execution error message in the retdata.");
decode_felts_as_str(&call_info.execution.retdata.0)
} else {
let error = call_result.unwrap_err();
error.to_string()
};
let expected_error = EmitEventError::ExceedsMaxKeysLength {
keys_length: max_event_keys_length + 1,
max_keys_length: max_event_keys_length,
};

assert!(error.to_string().contains(&expected_error.to_string()));
assert!(error_message.contains(&expected_error.to_string()));
}

#[test_case(FeatureContract::TestContract(CairoVersion::Native); "Native")]
#[test_case(FeatureContract::TestContract(CairoVersion::Cairo1); "VM")]
fn event_number_exceeds_limit(test_contract: FeatureContract) {
let versioned_constants = VersionedConstants::create_for_testing();
Expand All @@ -81,12 +105,22 @@ fn event_number_exceeds_limit(test_contract: FeatureContract) {
let n_emitted_events_too_big = vec![felt!(
u16::try_from(max_n_emitted_events + 1).expect("Failed to convert usize to u16.")
)];
let error = emit_events(test_contract, &n_emitted_events_too_big, &KEYS, &DATA).unwrap_err();
let call_result = emit_events(test_contract, &n_emitted_events_too_big, &KEYS, &DATA);

let error_message =
if matches!(test_contract, FeatureContract::TestContract(CairoVersion::Native)) {
let call_info = call_result
.expect("Expected CallResult with failed execution error message in the retdata.");
decode_felts_as_str(&call_info.execution.retdata.0)
} else {
let error = call_result.unwrap_err();
error.to_string()
};
let expected_error = EmitEventError::ExceedsMaxNumberOfEmittedEvents {
n_emitted_events: max_n_emitted_events + 1,
max_n_emitted_events,
};
assert!(error.to_string().contains(&expected_error.to_string()));
assert!(error_message.contains(&expected_error.to_string()));
}

fn emit_events(
Expand Down

0 comments on commit 7e3b4f6

Please sign in to comment.