From 3c862cce022d5e526182f8a4ef9b6666441a99fc Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii Date: Wed, 21 Aug 2024 13:01:49 +0300 Subject: [PATCH 1/4] feat: add native syscall handler --- crates/blockifier/src/execution/native.rs | 1 + .../src/execution/native/syscall_handler.rs | 318 ++++++++++++++++++ .../blockifier/src/execution/native/utils.rs | 37 ++ .../src/execution/native/utils_test.rs | 42 +++ 4 files changed, 398 insertions(+) create mode 100644 crates/blockifier/src/execution/native/syscall_handler.rs create mode 100644 crates/blockifier/src/execution/native/utils_test.rs diff --git a/crates/blockifier/src/execution/native.rs b/crates/blockifier/src/execution/native.rs index b5614dd823..2cedb78c88 100644 --- a/crates/blockifier/src/execution/native.rs +++ b/crates/blockifier/src/execution/native.rs @@ -1 +1,2 @@ +pub mod syscall_handler; pub mod utils; 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..d5568e8d94 --- /dev/null +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -0,0 +1,318 @@ +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}; +use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext}; +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; +use crate::transaction::transaction_utils::update_remaining_gas; + +pub struct NativeSyscallHandler<'state> { + // Input for execution. + pub state: &'state mut dyn State, + pub execution_resources: &'state mut ExecutionResources, + pub execution_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 execution result info. + pub storage_read_values: Vec, + pub accessed_storage_keys: HashSet, +} + +impl<'state> NativeSyscallHandler<'state> { + pub fn new( + state: &'state mut dyn State, + caller_address: ContractAddress, + contract_address: ContractAddress, + entry_point_selector: EntryPointSelector, + execution_resources: &'state mut ExecutionResources, + execution_context: &'state mut EntryPointExecutionContext, + ) -> NativeSyscallHandler<'state> { + NativeSyscallHandler { + state, + caller_address, + contract_address, + entry_point_selector: entry_point_selector.0, + execution_resources, + execution_context, + events: Vec::new(), + l2_to_l1_messages: Vec::new(), + inner_calls: Vec::new(), + storage_read_values: Vec::new(), + accessed_storage_keys: HashSet::new(), + } + } + + pub fn execute_inner_call( + &mut self, + entry_point: CallEntryPoint, + remaining_gas: &mut u128, + ) -> SyscallResult { + let call_info = entry_point + .execute(self.state, self.execution_resources, self.execution_context) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + let retdata = call_info.execution.retdata.0.clone(); + + if call_info.execution.failed { + // In VM it's wrapped into `SyscallExecutionError::SyscallError`. + return Err(retdata); + } + + self.update_remaining_gas(remaining_gas, &call_info); + + self.inner_calls.push(call_info.clone()); + + Ok(call_info) + } + + pub fn update_remaining_gas(&mut self, remaining_gas: &mut u128, call_info: &CallInfo) { + // Create a new variable with converted type. + let mut remaining_gas_u64 = u64::try_from(*remaining_gas).unwrap(); + + // 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); + } + + // 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. + pub 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.execution_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).unwrap()]); + } + + *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."); + } +} diff --git a/crates/blockifier/src/execution/native/utils.rs b/crates/blockifier/src/execution/native/utils.rs index 263da4b90f..b78be606e5 100644 --- a/crates/blockifier/src/execution/native/utils.rs +++ b/crates/blockifier/src/execution/native/utils.rs @@ -1,11 +1,48 @@ use cairo_lang_starknet_classes::contract_class::ContractEntryPoint; +use itertools::Itertools; use num_traits::ToBytes; use starknet_api::core::EntryPointSelector; use starknet_types_core::felt::Felt; +#[cfg(test)] +#[path = "utils_test.rs"] +pub mod test; + pub fn contract_entrypoint_to_entrypoint_selector( entrypoint: &ContractEntryPoint, ) -> EntryPointSelector { let selector_felt = Felt::from_bytes_be_slice(&entrypoint.selector.to_be_bytes()); EntryPointSelector(selector_felt) } + +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 +} + +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) + } + } +} 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..1f295fe74f --- /dev/null +++ b/crates/blockifier/src/execution/native/utils_test.rs @@ -0,0 +1,42 @@ +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 super::{contract_entrypoint_to_entrypoint_selector, decode_felts_as_str, encode_str_as_felts}; + +#[test] +fn test_encode_decode_str() { + const STR: &str = "Hello StarkNet!"; + + 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]; + + let res = decode_felts_as_str(&felts); + dbg!(res.as_bytes()); + assert_eq!(res, format!("[{}, {} ({}), {}]", v1, v2_msg, v2, v3)) +} + +#[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); +} From 18ec25ea2d6a3881200039e04c89723027a4bb41 Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii Date: Wed, 21 Aug 2024 13:08:30 +0300 Subject: [PATCH 2/4] fix: comment in Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7e60e4264e..154f9e42f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,13 +73,13 @@ byteorder = "1.4.3" bytes = "1" cached = "0.44.0" cairo-felt = "0.9.1" -# This is a temporary dependency, will be removed once the new version of cairo-native is released to main. cairo-lang-casm = "2.7.0" cairo-lang-runner = "2.7.0" cairo-lang-sierra = "=2.7.0" cairo-lang-sierra-to-casm = "2.7.0" cairo-lang-starknet-classes = "2.7.0" cairo-lang-utils = "2.7.0" +# This is a temporary dependency, will be removed once the new version of cairo-native is released to main. cairo-native = { git = "https://github.com/lambdaclass/cairo_native", branch = "cairo-lang2.7.0-rc.3" } cairo-vm = "=1.0.0-rc6" camelpaste = "0.1.0" From 15342e5d702f10880c596c42aaf6fd61541de445 Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii Date: Tue, 27 Aug 2024 12:26:22 +0300 Subject: [PATCH 3/4] fix: remove unnecessary debug message --- crates/blockifier/src/execution/native/utils_test.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/blockifier/src/execution/native/utils_test.rs b/crates/blockifier/src/execution/native/utils_test.rs index 1f295fe74f..a9e5fe17ee 100644 --- a/crates/blockifier/src/execution/native/utils_test.rs +++ b/crates/blockifier/src/execution/native/utils_test.rs @@ -25,9 +25,7 @@ fn test_decode_non_utf8_str() { let v3 = Felt::from_dec_str("13299428").unwrap(); let felts = [v1, v2, v3]; - let res = decode_felts_as_str(&felts); - dbg!(res.as_bytes()); - assert_eq!(res, format!("[{}, {} ({}), {}]", v1, v2_msg, v2, v3)) + assert_eq!(decode_felts_as_str(&felts), format!("[{}, {} ({}), {}]", v1, v2_msg, v2, v3)) } #[test] From 5102a32fd690aadd8d93bfa856b58399cef397a1 Mon Sep 17 00:00:00 2001 From: Bohdan Ohorodnii Date: Fri, 20 Sep 2024 17:59:37 +0300 Subject: [PATCH 4/4] fix: address review comments --- .../src/execution/native/syscall_handler.rs | 41 +++++++++++-------- .../blockifier/src/execution/native/utils.rs | 2 +- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index d5568e8d94..15c8441988 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -15,7 +15,7 @@ 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}; +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::OUT_OF_GAS_ERROR; @@ -25,8 +25,8 @@ use crate::transaction::transaction_utils::update_remaining_gas; pub struct NativeSyscallHandler<'state> { // Input for execution. pub state: &'state mut dyn State, - pub execution_resources: &'state mut ExecutionResources, - pub execution_context: &'state mut EntryPointExecutionContext, + pub resources: &'state mut ExecutionResources, + pub context: &'state mut EntryPointExecutionContext, // Call information. pub caller_address: ContractAddress, @@ -38,9 +38,9 @@ pub struct NativeSyscallHandler<'state> { pub l2_to_l1_messages: Vec, pub inner_calls: Vec, - // Additional execution result info. - pub storage_read_values: Vec, - pub accessed_storage_keys: HashSet, + // Additional information gathered during execution. + pub read_values: Vec, + pub accessed_keys: HashSet, } impl<'state> NativeSyscallHandler<'state> { @@ -49,21 +49,21 @@ impl<'state> NativeSyscallHandler<'state> { caller_address: ContractAddress, contract_address: ContractAddress, entry_point_selector: EntryPointSelector, - execution_resources: &'state mut ExecutionResources, - execution_context: &'state mut EntryPointExecutionContext, + resources: &'state mut ExecutionResources, + context: &'state mut EntryPointExecutionContext, ) -> NativeSyscallHandler<'state> { NativeSyscallHandler { state, caller_address, contract_address, entry_point_selector: entry_point_selector.0, - execution_resources, - execution_context, + resources, + context, events: Vec::new(), l2_to_l1_messages: Vec::new(), inner_calls: Vec::new(), - storage_read_values: Vec::new(), - accessed_storage_keys: HashSet::new(), + read_values: Vec::new(), + accessed_keys: HashSet::new(), } } @@ -71,9 +71,9 @@ impl<'state> NativeSyscallHandler<'state> { &mut self, entry_point: CallEntryPoint, remaining_gas: &mut u128, - ) -> SyscallResult { + ) -> SyscallResult { let call_info = entry_point - .execute(self.state, self.execution_resources, self.execution_context) + .execute(self.state, self.resources, self.context) .map_err(|e| encode_str_as_felts(&e.to_string()))?; let retdata = call_info.execution.retdata.0.clone(); @@ -84,9 +84,11 @@ impl<'state> NativeSyscallHandler<'state> { self.update_remaining_gas(remaining_gas, &call_info); - self.inner_calls.push(call_info.clone()); + let retdata = call_info.execution.retdata.clone(); - Ok(call_info) + self.inner_calls.push(call_info); + + Ok(retdata) } pub fn update_remaining_gas(&mut self, remaining_gas: &mut u128, call_info: &CallInfo) { @@ -110,11 +112,14 @@ impl<'state> NativeSyscallHandler<'state> { ) -> SyscallResult<()> { // Refund `SYSCALL_BASE_GAS_COST` as it was pre-charged. let required_gas = - u128::from(syscall_gas_cost - self.execution_context.gas_costs().syscall_base_gas_cost); + 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).unwrap()]); + return Err(vec![ + Felt::from_hex(OUT_OF_GAS_ERROR) + .expect("Failed to parse OUT_OF_GAS_ERROR hex string"), + ]); } *remaining_gas -= required_gas; diff --git a/crates/blockifier/src/execution/native/utils.rs b/crates/blockifier/src/execution/native/utils.rs index b57ead7d32..83f1543f9d 100644 --- a/crates/blockifier/src/execution/native/utils.rs +++ b/crates/blockifier/src/execution/native/utils.rs @@ -1,5 +1,5 @@ use cairo_lang_starknet_classes::contract_class::ContractEntryPoint; -use num_traits::ToBytes; +use itertools::Itertools; use starknet_api::core::EntryPointSelector; use starknet_types_core::felt::Felt;