From 174fe63c97d9734cbc98575f553b4076d65fe90f Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 3 Oct 2024 09:52:59 -0400 Subject: [PATCH 1/7] feat: add native syscall handler --- crates/blockifier/src/execution/native.rs | 4 + .../src/execution/native/syscall_handler.rs | 323 ++++++++++++++++++ .../blockifier/src/execution/native/utils.rs | 39 +++ .../src/execution/native/utils_test.rs | 43 +++ 4 files changed, 409 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..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..6ed2758251 --- /dev/null +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -0,0 +1,323 @@ +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(), + } + } + + pub 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.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); + + let retdata = call_info.execution.retdata.clone(); + + self.inner_calls.push(call_info); + + Ok(retdata) + } + + 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.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."); + } +} 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..f2fa3125c3 --- /dev/null +++ b/crates/blockifier/src/execution/native/utils_test.rs @@ -0,0 +1,43 @@ +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, + 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)) +} From c0ecba464312e57864f85def29e6d82499f359fe Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 3 Oct 2024 18:02:20 -0400 Subject: [PATCH 2/7] fix: compilation error --- crates/blockifier/src/execution/native/utils_test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/blockifier/src/execution/native/utils_test.rs b/crates/blockifier/src/execution/native/utils_test.rs index f2fa3125c3..bffd39b6ca 100644 --- a/crates/blockifier/src/execution/native/utils_test.rs +++ b/crates/blockifier/src/execution/native/utils_test.rs @@ -6,6 +6,7 @@ use starknet_types_core::felt::Felt; use crate::execution::native::utils::{ contract_entrypoint_to_entrypoint_selector, + decode_felts_as_str, encode_str_as_felts, }; From 8bf593da429c5f55319b1cc55fb99a9a31ef3212 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sun, 6 Oct 2024 11:53:57 -0400 Subject: [PATCH 3/7] refactor: native syscall handler --- .../src/execution/native/syscall_handler.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index 6ed2758251..0da19907f0 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -67,7 +67,8 @@ impl<'state> NativeSyscallHandler<'state> { } } - pub fn execute_inner_call( + #[allow(dead_code)] + fn execute_inner_call( &mut self, entry_point: CallEntryPoint, remaining_gas: &mut u128, @@ -75,23 +76,22 @@ impl<'state> NativeSyscallHandler<'state> { 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.0.clone(); + let retdata = call_info.execution.retdata.clone(); if call_info.execution.failed { // In VM it's wrapped into `SyscallExecutionError::SyscallError`. - return Err(retdata); + return Err(retdata.0.clone()); } self.update_remaining_gas(remaining_gas, &call_info); - let retdata = call_info.execution.retdata.clone(); - self.inner_calls.push(call_info); Ok(retdata) } - pub fn update_remaining_gas(&mut self, remaining_gas: &mut u128, call_info: &CallInfo) { + #[allow(dead_code)] + 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(); @@ -105,7 +105,8 @@ impl<'state> NativeSyscallHandler<'state> { // 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( + #[allow(dead_code)] + fn substract_syscall_gas_cost( &mut self, remaining_gas: &mut u128, syscall_gas_cost: u64, From 7797ac545f4eb58735cb093c738766089e631d09 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sun, 6 Oct 2024 14:07:46 -0400 Subject: [PATCH 4/7] refactor: move update_remainng_gas out of SyscallHandler impl --- .../src/execution/native/syscall_handler.rs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index 0da19907f0..7f9ca94be6 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -83,25 +83,13 @@ impl<'state> NativeSyscallHandler<'state> { return Err(retdata.0.clone()); } - self.update_remaining_gas(remaining_gas, &call_info); + native_update_remaining_gas(remaining_gas, &call_info); self.inner_calls.push(call_info); Ok(retdata) } - #[allow(dead_code)] - 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. @@ -322,3 +310,18 @@ impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { 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).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); +} From 530d3fe122917d851f4ce08456b38acedca1dda7 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Sun, 6 Oct 2024 14:15:25 -0400 Subject: [PATCH 5/7] fix: restore concurrency in main workflow --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a87db9c686..3ffc539917 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,11 @@ on: # On PR events, cancel existing CI runs on this same PR for this workflow. concurrency: +<<<<<<< HEAD group: ${{ github.workflow }}-${{ github.ref }}-${{ github.job }} +======= + group: ${{ github.workflow }}-${{ github.ref }} +>>>>>>> df3f7106c (fix: restore concurrency in main workflow) cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: From ee6efede3e8ac64387d2496df91bc4b76fd08f42 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 7 Oct 2024 07:18:41 -0400 Subject: [PATCH 6/7] refactor: unwrap -> expect in native gas withdraw --- crates/blockifier/src/execution/native/syscall_handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index 7f9ca94be6..806abbc7b1 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -317,7 +317,8 @@ impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { #[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).unwrap(); + 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); From e9d7c2aeff5eb656dd0dd39c54420f554ed0da9f Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 7 Oct 2024 12:23:24 -0400 Subject: [PATCH 7/7] chore: restore rebase unintended changes --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ffc539917..a87db9c686 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,11 +18,7 @@ on: # On PR events, cancel existing CI runs on this same PR for this workflow. concurrency: -<<<<<<< HEAD group: ${{ github.workflow }}-${{ github.ref }}-${{ github.job }} -======= - group: ${{ github.workflow }}-${{ github.ref }} ->>>>>>> df3f7106c (fix: restore concurrency in main workflow) cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: