diff --git a/Cargo.lock b/Cargo.lock index 9df06ba0e4..0c9f9b8a37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1028,6 +1028,7 @@ dependencies = [ "serde_json", "sha2", "sha3", + "sierra-emu", "starknet-types-core", "starknet_api", "strum 0.25.0", @@ -8850,6 +8851,35 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sierra-emu" +version = "0.1.0" +source = "git+https://github.com/lambdaclass/sierra-emu.git#2b612d56db15a1002bb78827aa3fb2f66db4cc81" +dependencies = [ + "cairo-lang-sierra", + "cairo-lang-sierra-ap-change", + "cairo-lang-sierra-gas", + "cairo-lang-utils", + "clap", + "k256", + "keccak", + "num-bigint 0.4.6", + "num-traits 0.2.19", + "p256", + "rand 0.8.5", + "sec1", + "serde", + "serde_json", + "sha2", + "smallvec", + "starknet-crypto 0.7.1", + "starknet-curve 0.5.0", + "starknet-types-core", + "thiserror", + "tracing", + "tracing-subscriber", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index e354c03b59..fbbf60b7fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,14 +74,15 @@ byteorder = "1.4.3" bytes = "1" cached = "0.44.0" cairo-felt = "0.9.1" -cairo-lang-casm = "2.8.0" -cairo-lang-runner = "2.8.0" -cairo-lang-sierra = "2.8.0" -cairo-lang-sierra-to-casm = "2.7.1" -cairo-lang-starknet-classes = "2.8.0" -cairo-lang-utils = "2.7.1" +cairo-lang-casm = "2.8.2" +cairo-lang-runner = "2.8.2" +cairo-lang-sierra = "2.8.2" +cairo-lang-sierra-to-casm = "2.8.2" +cairo-lang-starknet-classes = "2.8.2" +cairo-lang-utils = "2.8.2" # 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" } +sierra-emu = { git = "https://github.com/lambdaclass/sierra-emu.git" } cairo-vm = "1.0.1" camelpaste = "0.1.0" chrono = "0.4.26" diff --git a/crates/blockifier/Cargo.toml b/crates/blockifier/Cargo.toml index a3a8f514d5..2bcd38b52a 100644 --- a/crates/blockifier/Cargo.toml +++ b/crates/blockifier/Cargo.toml @@ -13,6 +13,7 @@ workspace = true concurrency = [] jemalloc = ["dep:tikv-jemallocator"] testing = ["rand", "rstest"] +use-sierra-emu = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -30,6 +31,7 @@ cairo-lang-sierra.workspace = true cairo-lang-starknet-classes.workspace = true cairo-lang-utils.workspace = true cairo-native.workspace = true +sierra-emu.workspace = true cairo-vm.workspace = true derive_more.workspace = true indexmap.workspace = true diff --git a/crates/blockifier/src/execution/contract_class.rs b/crates/blockifier/src/execution/contract_class.rs index ace7ba1aea..513927cbec 100644 --- a/crates/blockifier/src/execution/contract_class.rs +++ b/crates/blockifier/src/execution/contract_class.rs @@ -644,15 +644,21 @@ impl NativeContractClassV1 { } } -#[derive(Debug)] pub struct NativeContractClassV1Inner { pub executor: Arc, entry_points_by_type: NativeContractEntryPoints, // Storing the raw sierra program and entry points to be able to fallback to the vm sierra_program_raw: Vec, + program: cairo_lang_sierra::program::Program, // for sierra emu fallback_entry_points_by_type: SierraContractEntryPoints, } +impl std::fmt::Debug for NativeContractClassV1Inner { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "NativeContractClassV1Inner") + } +} + impl NativeContractClassV1Inner { /// See [NativeContractClassV1::new] fn new( @@ -682,6 +688,7 @@ impl NativeContractClassV1Inner { &sierra_contract_class.entry_points_by_type, )?, sierra_program_raw: sierra_contract_class.sierra_program, + program: sierra_program.clone(), fallback_entry_points_by_type: sierra_contract_class.entry_points_by_type, }) } diff --git a/crates/blockifier/src/execution/native/entry_point_execution.rs b/crates/blockifier/src/execution/native/entry_point_execution.rs index 87bf8efaf8..8e73ecd320 100644 --- a/crates/blockifier/src/execution/native/entry_point_execution.rs +++ b/crates/blockifier/src/execution/native/entry_point_execution.rs @@ -35,7 +35,12 @@ pub fn execute_entry_point_call( tracing::info!("native contract execution started"); let pre_execution_instant = Instant::now(); - let result = run_native_executor(&contract_class.executor, function_id, call, syscall_handler); + + let result = if cfg!(feature = "use-sierra-emu") { + todo!() + } else { + run_native_executor(&contract_class.executor, function_id, call, syscall_handler) + }; let execution_time = pre_execution_instant.elapsed().as_millis(); tracing::info!(time = execution_time, "native contract execution finished"); diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index 1ccf9b3a7c..477349269a 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -872,6 +872,762 @@ impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { } } +pub mod sierra_emu_impl { + use std::sync::Arc; + + use sierra_emu::starknet::{ + BlockInfo, ExecutionInfo, ExecutionInfoV2, ResourceBounds, Secp256k1Point, Secp256r1Point, SyscallResult, TxInfo, TxV2Info, U256 + }; + use starknet_api::{core::{calculate_contract_address, ClassHash, ContractAddress, EntryPointSelector, EthAddress, PatriciaKey}, data_availability::DataAvailabilityMode, deprecated_contract_class::EntryPointType, state::StorageKey, transaction::{Calldata, ContractAddressSalt, EventContent, EventData, EventKey, L2ToL1Payload}}; + use starknet_types_core::felt::Felt; + use num_traits::ToPrimitive; + + use crate::{ + abi::constants, + execution::{ + call_info::{MessageToL1, OrderedEvent, OrderedL2ToL1Message}, common_hints::ExecutionMode, contract_class::ContractClass, entry_point::{CallEntryPoint, CallType, ConstructorContext}, execution_utils::{execute_deployment, max_fee_for_execution_info}, native::utils::{ + calculate_resource_bounds, contract_address_to_native_felt, default_tx_v2_info_sierra_emu, encode_str_as_felts + }, syscalls::{ + exceeds_event_size_limit, hint_processor::{SyscallExecutionError, BLOCK_NUMBER_OUT_OF_RANGE_ERROR, INVALID_INPUT_LENGTH_ERROR, OUT_OF_GAS_ERROR}, SyscallSelector + } + }, + transaction::objects::TransactionInfo, + }; + + use super::{to_u256_native, NativeSyscallHandler, Secp256Point}; + + + + impl<'state> sierra_emu::starknet::StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { + fn get_block_hash( + &mut self, + block_number: u64, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::GetBlockHash, + self.execution_context.gas_costs().get_block_hash_gas_cost, + )?; + + if self.execution_context.execution_mode == ExecutionMode::Validate { + let err = SyscallExecutionError::InvalidSyscallInExecutionMode { + syscall_name: "get_block_hash".to_string(), + execution_mode: ExecutionMode::Validate, + }; + + return Err(encode_str_as_felts(&err.to_string())); + } + + let current_block_number = + self.execution_context.tx_context.block_context.block_info.block_number.0; + + if current_block_number < constants::STORED_BLOCK_HASH_BUFFER + || block_number > current_block_number - constants::STORED_BLOCK_HASH_BUFFER + { + // `panic` is unreachable in this case, also this is covered by tests so we can safely + // unwrap + let out_of_range_felt = Felt::from_hex(BLOCK_NUMBER_OUT_OF_RANGE_ERROR).unwrap(); + + // This error is wrapped into a `SyscallExecutionError::SyscallError` in the VM + // implementation, but here it would be more convenient to return it directly, since + // wrapping it like VM does will result in a double encoding to felts, which adds extra + // layer of complication + return Err(vec![out_of_range_felt]); + } + + let key = StorageKey::try_from(Felt::from(block_number)) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + let block_hash_address = + ContractAddress::try_from(Felt::from(constants::BLOCK_HASH_CONTRACT_ADDRESS)) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + + match self.state.get_storage_at(block_hash_address, key) { + Ok(value) => Ok(value), + Err(e) => Err(encode_str_as_felts(&e.to_string())), + } + } + + fn get_execution_info(&mut self, remaining_gas: &mut u128) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::GetExecutionInfo, + self.execution_context.gas_costs().get_execution_info_gas_cost, + )?; + + let block_info = &self.execution_context.tx_context.block_context.block_info; + let native_block_info: BlockInfo = + if self.execution_context.execution_mode == ExecutionMode::Validate { + // TODO: Literal copy from get execution info v2, could be refactored + let versioned_constants = self.execution_context.versioned_constants(); + let block_number = block_info.block_number.0; + let block_timestamp = block_info.block_timestamp.0; + // Round down to the nearest multiple of validate_block_number_rounding. + let validate_block_number_rounding = + versioned_constants.get_validate_block_number_rounding(); + let rounded_block_number = (block_number / validate_block_number_rounding) + * validate_block_number_rounding; + // Round down to the nearest multiple of validate_timestamp_rounding. + let validate_timestamp_rounding = + versioned_constants.get_validate_timestamp_rounding(); + let rounded_timestamp = (block_timestamp / validate_timestamp_rounding) + * validate_timestamp_rounding; + BlockInfo { + block_number: rounded_block_number, + block_timestamp: rounded_timestamp, + sequencer_address: Felt::ZERO, + } + } else { + BlockInfo { + block_number: block_info.block_number.0, + block_timestamp: block_info.block_timestamp.0, + sequencer_address: contract_address_to_native_felt( + block_info.sequencer_address, + ), + } + }; + + let tx_info = &self.execution_context.tx_context.tx_info; + let native_tx_info = TxInfo { + version: tx_info.version().0, + account_contract_address: contract_address_to_native_felt(tx_info.sender_address()), + max_fee: tx_info.max_fee().unwrap_or_default().0, + signature: tx_info.signature().0, + transaction_hash: tx_info.transaction_hash().0, + chain_id: Felt::from_hex( + &self.execution_context.tx_context.block_context.chain_info.chain_id.as_hex(), + ) + .unwrap(), + nonce: tx_info.nonce().0, + }; + + let caller_address = contract_address_to_native_felt(self.caller_address); + let contract_address = contract_address_to_native_felt(self.contract_address); + let entry_point_selector = self.entry_point_selector; + + Ok(ExecutionInfo { + block_info: native_block_info, + tx_info: native_tx_info, + caller_address, + contract_address, + entry_point_selector, + }) + } + + fn get_execution_info_v2( + &mut self, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::GetExecutionInfo, + self.execution_context.gas_costs().get_execution_info_gas_cost, + )?; + + // Get Block Info + let block_info = &self.execution_context.tx_context.block_context.block_info; + let native_block_info: BlockInfo = + if self.execution_context.execution_mode == ExecutionMode::Validate { + let versioned_constants = self.execution_context.versioned_constants(); + let block_number = block_info.block_number.0; + let block_timestamp = block_info.block_timestamp.0; + // Round down to the nearest multiple of validate_block_number_rounding. + let validate_block_number_rounding = + versioned_constants.get_validate_block_number_rounding(); + let rounded_block_number = (block_number / validate_block_number_rounding) + * validate_block_number_rounding; + // Round down to the nearest multiple of validate_timestamp_rounding. + let validate_timestamp_rounding = + versioned_constants.get_validate_timestamp_rounding(); + let rounded_timestamp = (block_timestamp / validate_timestamp_rounding) + * validate_timestamp_rounding; + BlockInfo { + block_number: rounded_block_number, + block_timestamp: rounded_timestamp, + sequencer_address: Felt::ZERO, + } + } else { + BlockInfo { + block_number: block_info.block_number.0, + block_timestamp: block_info.block_timestamp.0, + sequencer_address: contract_address_to_native_felt( + block_info.sequencer_address, + ), + } + }; + + // Get Transaction Info + let tx_info = &self.execution_context.tx_context.tx_info; + let mut native_tx_info = TxV2Info { + version: tx_info.signed_version().0, + account_contract_address: contract_address_to_native_felt(tx_info.sender_address()), + max_fee: max_fee_for_execution_info(tx_info).to_u128().unwrap(), + signature: tx_info.signature().0, + transaction_hash: tx_info.transaction_hash().0, + chain_id: Felt::from_hex( + &self.execution_context.tx_context.block_context.chain_info.chain_id.as_hex(), + ) + .unwrap(), + nonce: tx_info.nonce().0, + ..default_tx_v2_info_sierra_emu() + }; + // If handling V3 transaction fill the "default" fields + if let TransactionInfo::Current(context) = tx_info { + let to_u32 = |x| match x { + DataAvailabilityMode::L1 => 0, + DataAvailabilityMode::L2 => 1, + }; + native_tx_info = TxV2Info { + resource_bounds: calculate_resource_bounds(context)? + .iter() + .map(|x| ResourceBounds { + resource: x.resource, + max_amount: x.max_amount, + max_price_per_unit: x.max_price_per_unit, + }) + .collect(), + tip: context.tip.0.into(), + paymaster_data: context.paymaster_data.0.clone(), + nonce_data_availability_mode: to_u32(context.nonce_data_availability_mode), + fee_data_availability_mode: to_u32(context.fee_data_availability_mode), + account_deployment_data: context.account_deployment_data.0.clone(), + ..native_tx_info + }; + } + + let caller_address = contract_address_to_native_felt(self.caller_address); + let contract_address = contract_address_to_native_felt(self.contract_address); + let entry_point_selector = self.entry_point_selector; + + Ok(ExecutionInfoV2 { + block_info: native_block_info, + tx_info: native_tx_info, + caller_address, + contract_address, + entry_point_selector, + }) + } + + fn deploy( + &mut self, + class_hash: Felt, + contract_address_salt: Felt, + calldata: Vec, + deploy_from_zero: bool, + remaining_gas: &mut u128, + ) -> SyscallResult<(Felt, Vec)> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Deploy, + self.execution_context.gas_costs().deploy_gas_cost, + )?; + + let deployer_address = + if deploy_from_zero { ContractAddress::default() } else { self.contract_address }; + + let class_hash = ClassHash(class_hash); + let wrapper_calldata = Calldata(Arc::new(calldata.to_vec())); + let calculated_contract_address = calculate_contract_address( + ContractAddressSalt(contract_address_salt), + class_hash, + &wrapper_calldata, + deployer_address, + ) + .map_err(|err| encode_str_as_felts(&err.to_string()))?; + + let ctor_context = ConstructorContext { + class_hash, + code_address: Some(calculated_contract_address), + storage_address: calculated_contract_address, + caller_address: deployer_address, + }; + + let call_info = execute_deployment( + self.state, + self.execution_resources, + self.execution_context, + ctor_context, + wrapper_calldata, + // Warning: converting of reference would create a new reference to different data, + // example: + // let mut a: u128 = 1; + // let a_ref: &mut u128 = &mut a; + // + // let mut b: u64 = u64::try_from(*a_ref).unwrap(); + // + // assert_eq!(b, 1); + // + // b += 1; + // + // assert_eq!(b, 2); + // assert_eq!(a, 1); + // in this case we don't pass a reference, so everything is OK, but still can cause + // conversion issues + u64::try_from(*remaining_gas).unwrap(), + ) + .map_err(|error| encode_str_as_felts(&error.to_string()))?; + + self.update_remaining_gas(remaining_gas, &call_info); + + let return_data = call_info.execution.retdata.0[..].to_vec(); + let contract_address_felt = Felt::from(calculated_contract_address); + + self.inner_calls.push(call_info); + + Ok((contract_address_felt, return_data)) + } + + fn replace_class( + &mut self, + class_hash: Felt, + remaining_gas: &mut u128, + ) -> SyscallResult<()> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::ReplaceClass, + self.execution_context.gas_costs().replace_class_gas_cost, + )?; + + let class_hash = ClassHash(class_hash); + let contract_class = self + .state + .get_compiled_contract_class(class_hash) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + + match contract_class { + ContractClass::V0(_) => Err(encode_str_as_felts( + &SyscallExecutionError::ForbiddenClassReplacement { class_hash }.to_string(), + )), + ContractClass::V1(_) | ContractClass::V1Native(_) => { + self.state + .set_class_hash_at(self.contract_address, class_hash) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + + Ok(()) + } + } + } + + fn library_call( + &mut self, + class_hash: Felt, + function_selector: Felt, + calldata: Vec, + remaining_gas: &mut u128, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::LibraryCall, + self.execution_context.gas_costs().library_call_gas_cost, + )?; + + let class_hash = ClassHash(class_hash); + + let wrapper_calldata = Calldata(Arc::new(calldata.to_vec())); + + let entry_point = CallEntryPoint { + class_hash: Some(class_hash), + code_address: None, + entry_point_type: EntryPointType::External, + entry_point_selector: EntryPointSelector(function_selector), + calldata: wrapper_calldata, + // The call context remains the same in a library call. + storage_address: self.contract_address, + caller_address: self.caller_address, + call_type: CallType::Delegate, + initial_gas: u64::try_from(*remaining_gas).unwrap(), + }; + + let retdata = self + .execute_inner_call(entry_point, remaining_gas) + .map(|call_info| call_info.execution.retdata.0.clone())?; + + Ok(retdata) + } + + fn call_contract( + &mut self, + address: Felt, + entry_point_selector: Felt, + calldata: Vec, + remaining_gas: &mut u128, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::CallContract, + self.execution_context.gas_costs().call_contract_gas_cost, + )?; + + let contract_address = ContractAddress::try_from(address) + .map_err(|error| encode_str_as_felts(&error.to_string()))?; + + if self.execution_context.execution_mode == ExecutionMode::Validate + && self.contract_address != contract_address + { + let err = SyscallExecutionError::InvalidSyscallInExecutionMode { + syscall_name: "call_contract".to_string(), + execution_mode: ExecutionMode::Validate, + }; + + return Err(encode_str_as_felts(&err.to_string())); + } + + let wrapper_calldata = Calldata(Arc::new(calldata.to_vec())); + + let entry_point = CallEntryPoint { + class_hash: None, + code_address: Some(contract_address), + entry_point_type: EntryPointType::External, + entry_point_selector: EntryPointSelector(entry_point_selector), + calldata: wrapper_calldata, + storage_address: contract_address, + caller_address: self.contract_address, + call_type: CallType::Call, + initial_gas: u64::try_from(*remaining_gas).unwrap(), + }; + + let retdata = self + .execute_inner_call(entry_point, remaining_gas) + .map(|call_info| call_info.execution.retdata.0.clone())?; + + Ok(retdata) + } + + fn storage_read( + &mut self, + _address_domain: u32, + address: Felt, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::StorageRead, + self.execution_context.gas_costs().storage_read_gas_cost, + )?; + + let key = StorageKey( + PatriciaKey::try_from(address).map_err(|e| encode_str_as_felts(&e.to_string()))?, + ); + + let read_result = self.state.get_storage_at(self.contract_address, key); + let value = read_result.map_err(|e| encode_str_as_felts(&e.to_string()))?; + + self.accessed_storage_keys.insert(key); + self.storage_read_values.push(value); + + Ok(value) + } + + fn storage_write( + &mut self, + _address_domain: u32, + address: Felt, + value: Felt, + remaining_gas: &mut u128, + ) -> SyscallResult<()> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::StorageWrite, + self.execution_context.gas_costs().storage_write_gas_cost, + )?; + + let key = StorageKey( + PatriciaKey::try_from(address).map_err(|e| encode_str_as_felts(&e.to_string()))?, + ); + self.accessed_storage_keys.insert(key); + + let write_result = self.state.set_storage_at(self.contract_address, key, value); + write_result.map_err(|e| encode_str_as_felts(&e.to_string()))?; + + Ok(()) + } + + fn emit_event( + &mut self, + keys: Vec, + data: Vec, + remaining_gas: &mut u128, + ) -> SyscallResult<()> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::EmitEvent, + self.execution_context.gas_costs().emit_event_gas_cost, + )?; + + let order = self.execution_context.n_emitted_events; + let event = EventContent { + keys: keys.iter().copied().map(EventKey).collect(), + data: EventData(data.to_vec()), + }; + + exceeds_event_size_limit( + self.execution_context.versioned_constants(), + self.execution_context.n_emitted_events + 1, + &event, + ) + .map_err(|e| encode_str_as_felts(&e.to_string()))?; + + self.events.push(OrderedEvent { order, event }); + self.execution_context.n_emitted_events += 1; + + Ok(()) + } + + fn send_message_to_l1( + &mut self, + to_address: Felt, + payload: Vec, + remaining_gas: &mut u128, + ) -> SyscallResult<()> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::SendMessageToL1, + self.execution_context.gas_costs().send_message_to_l1_gas_cost, + )?; + + let order = self.execution_context.n_sent_messages_to_l1; + + self.l2_to_l1_messages.push(OrderedL2ToL1Message { + order, + message: MessageToL1 { + to_address: EthAddress::try_from(to_address) + .map_err(|e| encode_str_as_felts(&e.to_string()))?, + payload: L2ToL1Payload(payload.to_vec()), + }, + }); + + self.execution_context.n_sent_messages_to_l1 += 1; + + Ok(()) + } + + fn keccak(&mut self, input: Vec, remaining_gas: &mut u128) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Keccak, + self.execution_context.gas_costs().keccak_gas_cost, + )?; + + const KECCAK_FULL_RATE_IN_WORDS: usize = 17; + + let length = input.len(); + let (n_rounds, remainder) = num_integer::div_rem(length, KECCAK_FULL_RATE_IN_WORDS); + + if remainder != 0 { + // In VM this error is wrapped into `SyscallExecutionError::SyscallError` + return Err(vec![Felt::from_hex(INVALID_INPUT_LENGTH_ERROR).unwrap()]); + } + + // TODO(Ori, 1/2/2024): Write an indicative expect message explaining why the conversion + // works. + let n_rounds_as_u64 = u64::try_from(n_rounds).expect("Failed to convert usize to u64."); + let gas_cost = u128::from( + n_rounds_as_u64 * self.execution_context.gas_costs().keccak_round_cost_gas_cost, + ); + + if gas_cost > *remaining_gas { + // In VM this error is wrapped into `SyscallExecutionError::SyscallError` + return Err(vec![Felt::from_hex(OUT_OF_GAS_ERROR).unwrap()]); + } + *remaining_gas -= gas_cost; + + self.increment_syscall_count_by(&SyscallSelector::Keccak, n_rounds); + + let mut state = [0u64; 25]; + for chunk in input.chunks(KECCAK_FULL_RATE_IN_WORDS) { + for (i, val) in chunk.iter().enumerate() { + state[i] ^= val; + } + keccak::f1600(&mut state) + } + + Ok(U256 { + hi: u128::from(state[2]) | (u128::from(state[3]) << 64), + lo: u128::from(state[0]) | (u128::from(state[1]) << 64), + }) + } + + // The secp256 syscalls are implement in impl SecpHintProcessor + // The trait methods are responsible for routing to the correct hint processor (r1 or k1). + + fn secp256k1_new( + &mut self, + x: U256, + y: U256, + remaining_gas: &mut u128, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256k1New, + self.execution_context.gas_costs().secp256k1_new_gas_cost, + )?; + + Secp256Point::new(to_u256_native(x), to_u256_native(y)).map(|op| op.map(|p| p.into())) + } + + fn secp256k1_add( + &mut self, + p0: Secp256k1Point, + p1: Secp256k1Point, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256k1Add, + self.execution_context.gas_costs().secp256k1_add_gas_cost, + )?; + + Ok(Secp256Point::add(p0.into(), p1.into()).into()) + } + + fn secp256k1_mul( + &mut self, + p: Secp256k1Point, + m: U256, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256k1Mul, + self.execution_context.gas_costs().secp256k1_mul_gas_cost, + )?; + + Ok(Secp256Point::mul(p.into(), to_u256_native(m)).into()) + } + + fn secp256k1_get_point_from_x( + &mut self, + x: U256, + y_parity: bool, + remaining_gas: &mut u128, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256k1GetPointFromX, + self.execution_context.gas_costs().secp256k1_get_point_from_x_gas_cost, + )?; + + Secp256Point::get_point_from_x(to_u256_native(x), y_parity) + .map(|op| op.map(|p| p.into())) + } + + fn secp256k1_get_xy( + &mut self, + p: Secp256k1Point, + remaining_gas: &mut u128, + ) -> SyscallResult<(U256, U256)> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256k1GetXy, + self.execution_context.gas_costs().secp256k1_get_xy_gas_cost, + )?; + + Ok((p.x, p.y)) + } + + fn secp256r1_new( + &mut self, + x: U256, + y: U256, + remaining_gas: &mut u128, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256r1New, + self.execution_context.gas_costs().secp256r1_new_gas_cost, + )?; + + Secp256Point::new(to_u256_native(x), to_u256_native(y)).map(|op| op.map(|p| p.into())) + } + + fn secp256r1_add( + &mut self, + p0: Secp256r1Point, + p1: Secp256r1Point, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256r1Add, + self.execution_context.gas_costs().secp256r1_add_gas_cost, + )?; + + Ok(Secp256Point::add(p0.into(), p1.into()).into()) + } + + fn secp256r1_mul( + &mut self, + p: Secp256r1Point, + m: U256, + remaining_gas: &mut u128, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256r1Mul, + self.execution_context.gas_costs().secp256r1_mul_gas_cost, + )?; + + Ok(Secp256Point::mul(p.into(), to_u256_native(m)).into()) + } + + fn secp256r1_get_point_from_x( + &mut self, + x: U256, + y_parity: bool, + remaining_gas: &mut u128, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256r1GetPointFromX, + self.execution_context.gas_costs().secp256r1_get_point_from_x_gas_cost, + )?; + + Secp256Point::get_point_from_x(to_u256_native(x), y_parity) + .map(|op| op.map(|p| p.into())) + } + + fn secp256r1_get_xy( + &mut self, + p: Secp256r1Point, + remaining_gas: &mut u128, + ) -> SyscallResult<(U256, U256)> { + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Secp256r1GetXy, + self.execution_context.gas_costs().secp256r1_get_xy_gas_cost, + )?; + + Ok((p.x, p.y)) + } + + fn sha256_process_block( + &mut self, + prev_state: [u32; 8], + current_block: [u32; 16], + remaining_gas: &mut u128, + ) -> SyscallResult<[u32; 8]> { + const SHA256_STATE_SIZE: usize = 8; + + self.pre_execute_syscall( + remaining_gas, + SyscallSelector::Sha256ProcessBlock, + self.execution_context.gas_costs().sha256_process_block_gas_cost, + )?; + + let data_as_bytes = sha2::digest::generic_array::GenericArray::from_exact_iter( + current_block.iter().flat_map(|x| x.to_be_bytes()), + ) + .expect( + "u32.to_be_bytes() returns 4 bytes, and data.len() == 16. So data contains 64 \ + bytes.", + ); + let mut state: [u32; SHA256_STATE_SIZE] = prev_state; + sha2::compress256(&mut state, &[data_as_bytes]); + Ok(state) + } + } +} + use ark_ff::PrimeField; impl Secp256Point @@ -1004,6 +1760,38 @@ impl From for Secp256Point { } } +fn to_u256_emu(value: cairo_native::starknet::U256) -> sierra_emu::starknet::U256 { + sierra_emu::starknet::U256 { lo: value.lo, hi: value.hi } +} + +fn to_u256_native(value: sierra_emu::starknet::U256) -> cairo_native::starknet::U256 { + cairo_native::starknet::U256 { lo: value.lo, hi: value.hi } +} + +impl From> for sierra_emu::starknet::Secp256k1Point { + fn from(p: Secp256Point) -> Self { + sierra_emu::starknet::Secp256k1Point { x: to_u256_emu(p.x), y: to_u256_emu(p.y) } + } +} + +impl From> for sierra_emu::starknet::Secp256r1Point { + fn from(p: Secp256Point) -> Self { + sierra_emu::starknet::Secp256r1Point { x: to_u256_emu(p.x), y: to_u256_emu(p.y) } + } +} + +impl From for Secp256Point { + fn from(p: sierra_emu::starknet::Secp256k1Point) -> Self { + Self { x: to_u256_native(p.x), y: to_u256_native(p.y), _phantom: Default::default() } + } +} + +impl From for Secp256Point { + fn from(p: sierra_emu::starknet::Secp256r1Point) -> Self { + Self { x: to_u256_native(p.x), y: to_u256_native(p.y), _phantom: Default::default() } + } +} + impl From> for Affine where Curve::BaseField: From, diff --git a/crates/blockifier/src/execution/native/utils.rs b/crates/blockifier/src/execution/native/utils.rs index e801ec01f2..f1917a2d84 100644 --- a/crates/blockifier/src/execution/native/utils.rs +++ b/crates/blockifier/src/execution/native/utils.rs @@ -1,4 +1,5 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::hash::RandomState; use ark_ff::BigInt; use cairo_lang_sierra::ids::FunctionId; @@ -11,10 +12,11 @@ use itertools::Itertools; use num_bigint::BigUint; use num_traits::ToBytes; use starknet_api::core::{ContractAddress, EntryPointSelector}; +use starknet_api::state::StorageKey; use starknet_api::transaction::Resource; use starknet_types_core::felt::Felt; -use crate::execution::call_info::{CallExecution, CallInfo, Retdata}; +use crate::execution::call_info::{CallExecution, CallInfo, OrderedEvent, OrderedL2ToL1Message, Retdata}; use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionResult}; use crate::execution::errors::EntryPointExecutionError; use crate::execution::native::syscall_handler::NativeSyscallHandler; @@ -66,6 +68,36 @@ pub fn run_native_executor( create_callinfo(call, run_result, syscall_handler) } +pub fn run_sierra_emu_executor( + native_executor: &AotNativeExecutor, + function_id: &FunctionId, + call: CallEntryPoint, + mut syscall_handler: NativeSyscallHandler<'_>, +) -> EntryPointExecutionResult { + let execution_result = native_executor.invoke_contract_dynamic( + function_id, + &call.calldata.0, + Some(call.initial_gas.into()), + &mut syscall_handler, + ); + + let run_result = match execution_result { + Ok(res) if res.failure_flag => Err(EntryPointExecutionError::NativeExecutionError { + info: if !res.return_values.is_empty() { + decode_felts_as_str(&res.return_values) + } else { + String::from("Unknown error") + }, + }), + Err(runner_err) => { + Err(EntryPointExecutionError::NativeUnexpectedError { source: runner_err }) + } + Ok(res) => Ok(res), + }?; + + create_callinfo(call, run_result, syscall_handler) +} + fn create_callinfo( call: CallEntryPoint, run_result: ContractExecutionResult, @@ -102,6 +134,46 @@ fn create_callinfo( }) } +pub fn create_callinfo_emu( + call: CallEntryPoint, + run_result: sierra_emu::ContractExecutionResult, + events: Vec, + l2_to_l1_messages: Vec, + inner_calls: Vec, + storage_read_values: Vec, + accessed_storage_keys: HashSet, +) -> Result { + let gas_consumed = { + let low = run_result.remaining_gas as u64; + let high = (run_result.remaining_gas >> 64) as u64; + if high != 0 { + return Err(EntryPointExecutionError::NativeExecutionError { + info: "Overflow: gas consumed bigger than 64 bit".into(), + }); + } + call.initial_gas - low + }; + + Ok(CallInfo { + call, + execution: CallExecution { + retdata: Retdata(run_result.return_values), + events, + l2_to_l1_messages, + failed: run_result.failure_flag, + gas_consumed, + }, + resources: ExecutionResources { + n_steps: 0, + n_memory_holes: 0, + builtin_instance_counter: HashMap::default(), + }, + inner_calls, + storage_read_values, + accessed_storage_keys, + }) +} + pub fn u256_to_biguint(u256: U256) -> BigUint { let lo = BigUint::from(u256.lo); let hi = BigUint::from(u256.hi); @@ -168,6 +240,24 @@ pub fn default_tx_v2_info() -> TxV2Info { } } +pub fn default_tx_v2_info_sierra_emu() -> sierra_emu::starknet::TxV2Info { + sierra_emu::starknet::TxV2Info { + version: Default::default(), + account_contract_address: Default::default(), + max_fee: 0, + signature: vec![], + transaction_hash: Default::default(), + chain_id: Default::default(), + nonce: Default::default(), + resource_bounds: vec![], + tip: 0, + paymaster_data: vec![], + nonce_data_availability_mode: 0, + fee_data_availability_mode: 0, + account_deployment_data: vec![], + } +} + pub fn calculate_resource_bounds( tx_info: &CurrentTransactionInfo, ) -> SyscallResult> {