diff --git a/crates/blockifier/src/execution/native.rs b/crates/blockifier/src/execution/native.rs index b5614dd823..7c3dd35b21 100644 --- a/crates/blockifier/src/execution/native.rs +++ b/crates/blockifier/src/execution/native.rs @@ -1 +1,5 @@ +pub mod syscall_handler; pub mod utils; + +#[cfg(test)] +pub mod utils_test; diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs new file mode 100644 index 0000000000..806abbc7b1 --- /dev/null +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -0,0 +1,328 @@ +use std::collections::HashSet; +use std::hash::RandomState; + +use cairo_native::starknet::{ + ExecutionInfo, + ExecutionInfoV2, + Secp256k1Point, + Secp256r1Point, + StarknetSyscallHandler, + SyscallResult, + U256, +}; +use cairo_vm::vm::runners::cairo_runner::ExecutionResources; +use starknet_api::core::{ContractAddress, EntryPointSelector}; +use starknet_api::state::StorageKey; +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::execution_utils::update_remaining_gas; +use crate::execution::native::utils::encode_str_as_felts; +use crate::execution::syscalls::hint_processor::OUT_OF_GAS_ERROR; +use crate::state::state_api::State; + +pub struct NativeSyscallHandler<'state> { + // Input for execution. + pub state: &'state mut dyn State, + pub resources: &'state mut ExecutionResources, + pub context: &'state mut EntryPointExecutionContext, + + // Call information. + pub caller_address: ContractAddress, + pub contract_address: ContractAddress, + pub entry_point_selector: Felt, + + // Execution results. + pub events: Vec, + pub l2_to_l1_messages: Vec, + pub inner_calls: Vec, + + // Additional information gathered during execution. + pub read_values: Vec, + pub accessed_keys: HashSet, +} + +impl<'state> NativeSyscallHandler<'state> { + pub fn new( + state: &'state mut dyn State, + caller_address: ContractAddress, + contract_address: ContractAddress, + entry_point_selector: EntryPointSelector, + resources: &'state mut ExecutionResources, + context: &'state mut EntryPointExecutionContext, + ) -> NativeSyscallHandler<'state> { + NativeSyscallHandler { + state, + caller_address, + contract_address, + entry_point_selector: entry_point_selector.0, + resources, + context, + events: Vec::new(), + l2_to_l1_messages: Vec::new(), + inner_calls: Vec::new(), + read_values: Vec::new(), + accessed_keys: HashSet::new(), + } + } + + #[allow(dead_code)] + fn execute_inner_call( + &mut self, + entry_point: CallEntryPoint, + remaining_gas: &mut u128, + ) -> SyscallResult { + let call_info = entry_point + .execute(self.state, self.resources, self.context) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + let retdata = call_info.execution.retdata.clone(); + + if call_info.execution.failed { + // In VM it's wrapped into `SyscallExecutionError::SyscallError`. + return Err(retdata.0.clone()); + } + + native_update_remaining_gas(remaining_gas, &call_info); + + self.inner_calls.push(call_info); + + Ok(retdata) + } + + // Handles gas related logic when executing a syscall. Required because Native calls the + // syscalls directly unlike the VM where the `execute_syscall` method perform this operation + // first. + #[allow(dead_code)] + fn substract_syscall_gas_cost( + &mut self, + remaining_gas: &mut u128, + syscall_gas_cost: u64, + ) -> SyscallResult<()> { + // Refund `SYSCALL_BASE_GAS_COST` as it was pre-charged. + let required_gas = + u128::from(syscall_gas_cost - self.context.gas_costs().syscall_base_gas_cost); + + if *remaining_gas < required_gas { + // Out of gas failure. + return Err(vec![ + Felt::from_hex(OUT_OF_GAS_ERROR) + .expect("Failed to parse OUT_OF_GAS_ERROR hex string"), + ]); + } + + *remaining_gas -= required_gas; + + Ok(()) + } +} + +impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { + fn get_block_hash( + &mut self, + _block_number: u64, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement get_block_hash syscall."); + } + + fn get_execution_info(&mut self, _remaining_gas: &mut u128) -> SyscallResult { + todo!("Implement get_execution_info syscall."); + } + + fn get_execution_info_v2( + &mut self, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement get_execution_info_v2 syscall."); + } + + fn deploy( + &mut self, + _class_hash: Felt, + _contract_address_salt: Felt, + _calldata: &[Felt], + _deploy_from_zero: bool, + _remaining_gas: &mut u128, + ) -> SyscallResult<(Felt, Vec)> { + todo!("Implement deploy syscall."); + } + + fn replace_class(&mut self, _class_hash: Felt, _remaining_gas: &mut u128) -> SyscallResult<()> { + todo!("Implement replace_class syscall."); + } + + fn library_call( + &mut self, + _class_hash: Felt, + _function_selector: Felt, + _calldata: &[Felt], + _remaining_gas: &mut u128, + ) -> SyscallResult> { + todo!("Implement library_call syscall."); + } + + fn call_contract( + &mut self, + _address: Felt, + _entry_point_selector: Felt, + _calldata: &[Felt], + _remaining_gas: &mut u128, + ) -> SyscallResult> { + todo!("Implement call_contract syscall."); + } + + fn storage_read( + &mut self, + _address_domain: u32, + _address: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement storage_read syscall."); + } + + fn storage_write( + &mut self, + _address_domain: u32, + _address: Felt, + _value: Felt, + _remaining_gas: &mut u128, + ) -> SyscallResult<()> { + todo!("Implement storage_write syscall."); + } + + fn emit_event( + &mut self, + _keys: &[Felt], + _data: &[Felt], + _remaining_gas: &mut u128, + ) -> SyscallResult<()> { + todo!("Implement emit_event syscall."); + } + + fn send_message_to_l1( + &mut self, + _to_address: Felt, + _payload: &[Felt], + _remaining_gas: &mut u128, + ) -> SyscallResult<()> { + todo!("Implement send_message_to_l1 syscall."); + } + + fn keccak(&mut self, _input: &[u64], _remaining_gas: &mut u128) -> SyscallResult { + todo!("Implement keccak syscall."); + } + + fn secp256k1_new( + &mut self, + _x: U256, + _y: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + todo!("Implement secp256k1_new syscall."); + } + + fn secp256k1_add( + &mut self, + _p0: Secp256k1Point, + _p1: Secp256k1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement secp256k1_add syscall."); + } + + fn secp256k1_mul( + &mut self, + _p: Secp256k1Point, + _m: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement secp256k1_mul syscall."); + } + + fn secp256k1_get_point_from_x( + &mut self, + _x: U256, + _y_parity: bool, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + todo!("Implement secp256k1_get_point_from_x syscall."); + } + + fn secp256k1_get_xy( + &mut self, + _p: Secp256k1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult<(U256, U256)> { + todo!("Implement secp256k1_get_xy syscall."); + } + + fn secp256r1_new( + &mut self, + _x: U256, + _y: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + todo!("Implement secp256r1_new syscall."); + } + + fn secp256r1_add( + &mut self, + _p0: Secp256r1Point, + _p1: Secp256r1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement secp256r1_add syscall."); + } + + fn secp256r1_mul( + &mut self, + _p: Secp256r1Point, + _m: U256, + _remaining_gas: &mut u128, + ) -> SyscallResult { + todo!("Implement secp256r1_mul syscall."); + } + + fn secp256r1_get_point_from_x( + &mut self, + _x: U256, + _y_parity: bool, + _remaining_gas: &mut u128, + ) -> SyscallResult> { + todo!("Implement secp256r1_get_point_from_x syscall."); + } + + fn secp256r1_get_xy( + &mut self, + _p: Secp256r1Point, + _remaining_gas: &mut u128, + ) -> SyscallResult<(U256, U256)> { + todo!("Implement secp256r1_get_xy syscall."); + } + + fn sha256_process_block( + &mut self, + _prev_state: &[u32; 8], + _current_block: &[u32; 16], + _remaining_gas: &mut u128, + ) -> SyscallResult<[u32; 8]> { + todo!("Implement sha256_process_block syscall."); + } +} + +/// Wrapper function around [update_remaining_gas] which takes a u128 as an input, converts it to +/// u64 and uses [update_remaining_gas] to withdraw the right amount. Finally, updates the value +/// to which `remaining_gas` points to. +#[allow(dead_code)] +fn native_update_remaining_gas(remaining_gas: &mut u128, call_info: &CallInfo) { + // Create a new variable with converted type. + let mut remaining_gas_u64 = + u64::try_from(*remaining_gas).expect("Failed to convert gas to u64."); + + // Pass the reference to the function. + update_remaining_gas(&mut remaining_gas_u64, call_info); + + // Change the remaining gas value. + *remaining_gas = u128::from(remaining_gas_u64); +} diff --git a/crates/blockifier/src/execution/native/utils.rs b/crates/blockifier/src/execution/native/utils.rs index addcbcc3ad..2aeb969e9d 100644 --- a/crates/blockifier/src/execution/native/utils.rs +++ b/crates/blockifier/src/execution/native/utils.rs @@ -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; @@ -7,3 +8,41 @@ pub fn contract_entrypoint_to_entrypoint_selector( ) -> EntryPointSelector { EntryPointSelector(Felt::from(&entrypoint.selector)) } + +pub fn encode_str_as_felts(msg: &str) -> Vec { + const CHUNK_SIZE: usize = 32; + + let data = msg.as_bytes().chunks(CHUNK_SIZE - 1); + let mut encoding = vec![Felt::default(); data.len()]; + for (i, data_chunk) in data.enumerate() { + let mut chunk = [0_u8; CHUNK_SIZE]; + chunk[1..data_chunk.len() + 1].copy_from_slice(data_chunk); + encoding[i] = Felt::from_bytes_be(&chunk); + } + encoding +} + +// Todo(rodrigo): This is an opinionated way of interpretting error messages. It's ok for now but I +// think it can be improved; (for example) trying to make the output similar to a Cairo VM panic +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) { + // If the string is utf8 make sure it is not prefixed by no null chars. Null chars in + // between can still happen + Ok(s) => s.trim_matches('\0').to_owned(), + // If the string is non-utf8 overall, try to decode them as utf8 chunks of it and keep the + // original bytes for the non-utf8 chunks + 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) + } + } +} diff --git a/crates/blockifier/src/execution/native/utils_test.rs b/crates/blockifier/src/execution/native/utils_test.rs new file mode 100644 index 0000000000..bffd39b6ca --- /dev/null +++ b/crates/blockifier/src/execution/native/utils_test.rs @@ -0,0 +1,44 @@ +use cairo_lang_starknet_classes::contract_class::ContractEntryPoint; +use num_bigint::BigUint; +use pretty_assertions::assert_eq; +use starknet_api::core::EntryPointSelector; +use starknet_types_core::felt::Felt; + +use crate::execution::native::utils::{ + contract_entrypoint_to_entrypoint_selector, + decode_felts_as_str, + encode_str_as_felts, +}; + +#[test] +fn test_contract_entrypoint_to_entrypoint_selector() { + const NUM: u128 = 123; + + let entrypoint = ContractEntryPoint { selector: BigUint::from(NUM), function_idx: 0 }; + let expected_entrypoint_selector = EntryPointSelector(Felt::from(NUM)); + let actual_entrypoint_selector = contract_entrypoint_to_entrypoint_selector(&entrypoint); + + assert_eq!(actual_entrypoint_selector, expected_entrypoint_selector); +} + +#[test] +fn test_encode_decode_str() { + const STR: &str = "normal utf8 string:"; + + let encoded_felt_array = encode_str_as_felts(STR); + + let decoded_felt_array = decode_felts_as_str(encoded_felt_array.as_slice()); + + assert_eq!(&decoded_felt_array, STR); +} + +#[test] +fn test_decode_non_utf8_str() { + let v1 = Felt::from_dec_str("1234").unwrap(); + let v2_msg = "i am utf8"; + let v2 = Felt::from_bytes_be_slice(v2_msg.as_bytes()); + let v3 = Felt::from_dec_str("13299428").unwrap(); + let felts = [v1, v2, v3]; + + assert_eq!(decode_felts_as_str(&felts), format!("[{}, {} ({}), {}]", v1, v2_msg, v2, v3)) +}