diff --git a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs index e5849611ff..2e91e1b93a 100644 --- a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs +++ b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs @@ -5,17 +5,15 @@ use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution:: use blockifier::context::TransactionContext; use blockifier::execution::common_hints::ExecutionMode; use blockifier::execution::execution_utils::ReadOnlySegment; -use blockifier::execution::syscalls::hint_processor::create_retdata_segment; use blockifier::execution::syscalls::hint_processor::{ INVALID_ARGUMENT, SyscallExecutionError, SyscallHintProcessor, }; use blockifier::execution::syscalls::syscall_base::SyscallResult; use blockifier::execution::syscalls::vm_syscall_utils::{ - CallContractRequest, CallContractResponse, DeployRequest, DeployResponse, EmptyRequest, - GetBlockHashRequest, GetBlockHashResponse, GetExecutionInfoResponse, LibraryCallRequest, - LibraryCallResponse, MetaTxV0Request, MetaTxV0Response, StorageReadRequest, - StorageReadResponse, StorageWriteRequest, StorageWriteResponse, SyscallSelector, - TryExtractRevert, + CallContractRequest, CallContractResponse, EmptyRequest, GetBlockHashRequest, + GetBlockHashResponse, GetExecutionInfoResponse, LibraryCallRequest, LibraryCallResponse, + MetaTxV0Request, MetaTxV0Response, StorageReadRequest, StorageReadResponse, + StorageWriteRequest, StorageWriteResponse, SyscallSelector, TryExtractRevert, }; use blockifier::execution::{call_info::CallInfo, entry_point::ConstructorContext}; use blockifier::state::errors::StateError; @@ -39,7 +37,7 @@ use starknet_api::transaction::fields::TransactionSignature; use starknet_api::transaction::{TransactionHasher, TransactionOptions, signed_tx_version}; use starknet_api::{ contract_class::EntryPointType, - core::{ClassHash, ContractAddress, Nonce, calculate_contract_address}, + core::{ClassHash, ContractAddress, Nonce}, transaction::{ InvokeTransactionV0, TransactionVersion, fields::{Calldata, Fee}, @@ -65,68 +63,6 @@ pub fn get_execution_info_syscall( }) } -// blockifier/src/execution/syscalls/mod.rs:222 (deploy_syscall) -pub fn deploy_syscall( - request: DeployRequest, - vm: &mut VirtualMachine, - syscall_handler: &mut SyscallHintProcessor<'_>, - cheatnet_state: &mut CheatnetState, - remaining_gas: &mut u64, -) -> SyscallResult { - // Increment the Deploy syscall's linear cost counter by the number of elements in the - // constructor calldata - syscall_handler.base.increment_syscall_linear_factor_by( - &SyscallSelector::Deploy, - request.constructor_calldata.0.len(), - ); - - let deployer_address = syscall_handler.base.call.storage_address; - let deployer_address_for_calculation = if request.deploy_from_zero { - ContractAddress::default() - } else { - deployer_address - }; - - // region: Modified blockifier code - let deployed_contract_address = - if let Some(contract_address) = cheatnet_state.next_address_for_deployment() { - contract_address - } else { - calculate_contract_address( - request.contract_address_salt, - request.class_hash, - &request.constructor_calldata, - deployer_address_for_calculation, - )? - }; - // endregion - - let ctor_context = ConstructorContext { - class_hash: request.class_hash, - code_address: Some(deployed_contract_address), - storage_address: deployed_contract_address, - caller_address: deployer_address, - }; - let call_info = execute_deployment( - syscall_handler.base.state, - cheatnet_state, - syscall_handler.base.context, - &ctor_context, - request.constructor_calldata, - remaining_gas, - )?; - - let constructor_retdata = - create_retdata_segment(vm, syscall_handler, &call_info.execution.retdata.0)?; - - syscall_handler.base.inner_calls.push(call_info); - - Ok(DeployResponse { - contract_address: deployed_contract_address, - constructor_retdata, - }) -} - // blockifier/src/execution/execution_utils.rs:217 (execute_deployment) pub fn execute_deployment( state: &mut dyn State, diff --git a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/mod.rs b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/mod.rs index ae56e0172f..a36c9ce2f5 100644 --- a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/mod.rs +++ b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/mod.rs @@ -1,12 +1,12 @@ use std::marker::PhantomData; use crate::state::CheatnetState; -use blockifier::execution::entry_point::{CallEntryPoint, CallType}; +use blockifier::execution::entry_point::{CallEntryPoint, CallType, ConstructorContext}; use blockifier::execution::syscalls::hint_processor::{OUT_OF_GAS_ERROR, SyscallHintProcessor}; use blockifier::execution::syscalls::syscall_executor::SyscallExecutor; use blockifier::execution::syscalls::vm_syscall_utils::{ - CallContractRequest, LibraryCallRequest, RevertData, SingleSegmentResponse, - SyscallExecutorBaseError, SyscallRequestWrapper, SyscallSelector, + CallContractRequest, DeployRequest, DeployResponse, LibraryCallRequest, RevertData, + SingleSegmentResponse, SyscallExecutorBaseError, SyscallRequestWrapper, SyscallSelector, }; use blockifier::execution::{ execution_utils::ReadOnlySegment, @@ -17,7 +17,7 @@ use cairo_vm::types::relocatable::MaybeRelocatable; use cairo_vm::vm::{errors::hint_errors::HintError, vm_core::VirtualMachine}; use runtime::{ExtendedRuntime, ExtensionLogic, SyscallHandlingResult}; use starknet_api::contract_class::EntryPointType; -use starknet_api::core::ContractAddress; +use starknet_api::core::{ContractAddress, calculate_contract_address}; use starknet_api::execution_resources::GasAmount; use starknet_types_core::felt::Felt; @@ -29,6 +29,7 @@ use crate::runtime_extensions::call_to_blockifier_runtime_extension::rpc::{ }; use super::cheatable_starknet_runtime_extension::CheatableStarknetRuntime; +use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::cheated_syscalls::execute_deployment; use conversions::string::TryFromHexStr; use runtime::starknet::constants::TEST_ADDRESS; @@ -68,9 +69,13 @@ impl<'a> ExtensionLogic for CallToBlockifierExtension<'a> { Ok(SyscallHandlingResult::Handled) } + SyscallSelector::Deploy => { + execute_syscall::(selector, vm, extended_runtime)?; + + Ok(SyscallHandlingResult::Handled) + } SyscallSelector::DelegateCall | SyscallSelector::DelegateL1Handler - | SyscallSelector::Deploy | SyscallSelector::EmitEvent | SyscallSelector::GetBlockHash | SyscallSelector::GetBlockNumber @@ -179,6 +184,68 @@ impl ExecuteCall for LibraryCallRequest { } } +impl ExecuteCall for DeployRequest { + fn execute_call( + self: DeployRequest, + syscall_handler: &mut SyscallHintProcessor, + cheatnet_state: &mut CheatnetState, + remaining_gas: &mut u64, + ) -> CallResult { + // Increment the Deploy syscall's linear cost counter by the number of elements in the + // constructor calldata + syscall_handler.base.increment_syscall_linear_factor_by( + &SyscallSelector::Deploy, + self.constructor_calldata.0.len(), + ); + + let deployer_address = syscall_handler.base.call.storage_address; + let deployer_address_for_calculation = if self.deploy_from_zero { + ContractAddress::default() + } else { + deployer_address + }; + + // region: Modified blockifier code + let deployed_contract_address = + if let Some(contract_address) = cheatnet_state.next_address_for_deployment() { + contract_address + } else { + calculate_contract_address( + self.contract_address_salt, + self.class_hash, + &self.constructor_calldata, + deployer_address_for_calculation, + ) + .expect("Failed to calculate contract address") + }; + // endregion + + let ctor_context = ConstructorContext { + class_hash: self.class_hash, + code_address: Some(deployed_contract_address), + storage_address: deployed_contract_address, + caller_address: deployer_address, + }; + let exec_result = execute_deployment( + syscall_handler.base.state, + cheatnet_state, + syscall_handler.base.context, + &ctor_context, + self.constructor_calldata, + remaining_gas, + ); + + let result = + CallResult::from_execution_result_deploy(&exec_result, &deployed_contract_address); + + if let Ok(call_info) = exec_result { + syscall_handler.base.inner_calls.push(call_info); + } + + result + } +} + // crates/blockifier/src/execution/syscalls/vm_syscall_utils.rs:677 (execute_syscall) fn execute_syscall( selector: SyscallSelector, @@ -249,7 +316,7 @@ fn write_call_response( gas_counter: u64, call_result: CallResult, ) -> Result<(), HintError> { - let response_wrapper: SyscallResponseWrapper = match call_result { + match call_result { CallResult::Success { ret_data } => { let memory_segment_start_ptr = syscall_handler.read_only_segments.allocate( vm, @@ -260,7 +327,7 @@ fn write_call_response( .collect::>(), )?; - SyscallResponseWrapper::Success { + SyscallResponseWrapper::::Success { gas_counter, response: SingleSegmentResponse { segment: ReadOnlySegment { @@ -269,19 +336,46 @@ fn write_call_response( }, }, } + .write(vm, &mut syscall_handler.syscall_ptr)?; } CallResult::Failure(failure_type) => match failure_type { - CallFailure::Panic { panic_data } => SyscallResponseWrapper::Failure { - gas_counter, - revert_data: RevertData::new_normal(panic_data), - }, + CallFailure::Panic { panic_data } => { + SyscallResponseWrapper::::Failure { + gas_counter, + revert_data: RevertData::new_normal(panic_data), + } + .write(vm, &mut syscall_handler.syscall_ptr)?; + } CallFailure::Error { msg } => { return Err(HintError::CustomHint(Box::from(msg.to_string()))); } }, - }; + CallResult::DeploySuccess { + ret_data, + contract_address, + } => { + let memory_segment_start_ptr = syscall_handler.read_only_segments.allocate( + vm, + &ret_data + .clone() + .into_iter() + .map(MaybeRelocatable::Int) + .collect::>(), + )?; - response_wrapper.write(vm, &mut syscall_handler.syscall_ptr)?; + SyscallResponseWrapper::Success { + gas_counter, + response: DeployResponse { + contract_address, + constructor_retdata: ReadOnlySegment { + start_ptr: memory_segment_start_ptr, + length: ret_data.len(), + }, + }, + } + .write(vm, &mut syscall_handler.syscall_ptr)?; + } + } Ok(()) } diff --git a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/rpc.rs b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/rpc.rs index eaea3453dc..dad327571b 100644 --- a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/rpc.rs +++ b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/rpc.rs @@ -34,7 +34,13 @@ pub struct UsedResources { /// Enum representing possible call execution result, along with the data #[derive(Debug, Clone, CairoSerialize)] pub enum CallResult { - Success { ret_data: Vec }, + Success { + ret_data: Vec, + }, + DeploySuccess { + ret_data: Vec, + contract_address: ContractAddress, + }, Failure(CallFailure), } @@ -115,6 +121,9 @@ impl CallFailure { msg: ByteArray::from(msg.as_str()), } } + EntryPointExecutionError::CairoRunError(err) => CallFailure::Error { + msg: ByteArray::from(err.to_string().as_str()), + }, error => { let error_string = error.to_string(); if let Some(panic_data) = try_extract_panic_data(&error_string) { @@ -141,6 +150,35 @@ impl CallResult { } } + #[must_use] + pub fn from_execution_result_deploy( + result: &EntryPointExecutionResult, + contract_address: &ContractAddress, + ) -> Self { + match result { + Ok(call_info) => Self::from_non_error_deploy(call_info, contract_address), + Err(err) => { + Self::from_err(err, &AddressOrClassHash::ContractAddress(*contract_address)) + } + } + } + + #[must_use] + pub fn from_non_error_deploy(call_info: &CallInfo, contract_address: &ContractAddress) -> Self { + let return_data = &call_info.execution.retdata.0; + + if call_info.execution.failed { + return CallResult::Failure(CallFailure::Panic { + panic_data: return_data.clone(), + }); + } + + CallResult::DeploySuccess { + ret_data: return_data.clone(), + contract_address: *contract_address, + } + } + #[must_use] pub fn from_non_error(call_info: &CallInfo) -> Self { let return_data = &call_info.execution.retdata.0; diff --git a/crates/cheatnet/src/runtime_extensions/cheatable_starknet_runtime_extension.rs b/crates/cheatnet/src/runtime_extensions/cheatable_starknet_runtime_extension.rs index 252b66bc66..620c108814 100644 --- a/crates/cheatnet/src/runtime_extensions/cheatable_starknet_runtime_extension.rs +++ b/crates/cheatnet/src/runtime_extensions/cheatable_starknet_runtime_extension.rs @@ -71,14 +71,6 @@ impl<'a> ExtensionLogic for CheatableStarknetRuntimeExtension<'a> { SyscallSelector::LibraryCall, ) .map(|()| SyscallHandlingResult::Handled), - SyscallSelector::Deploy => self - .execute_syscall( - syscall_handler, - vm, - cheated_syscalls::deploy_syscall, - SyscallSelector::Deploy, - ) - .map(|()| SyscallHandlingResult::Handled), SyscallSelector::GetBlockHash => self .execute_syscall( syscall_handler, @@ -113,6 +105,7 @@ impl<'a> ExtensionLogic for CheatableStarknetRuntimeExtension<'a> { .map(|()| SyscallHandlingResult::Handled), SyscallSelector::DelegateCall | SyscallSelector::DelegateL1Handler + | SyscallSelector::Deploy | SyscallSelector::EmitEvent | SyscallSelector::GetBlockNumber | SyscallSelector::GetBlockTimestamp diff --git a/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/mod.rs b/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/mod.rs index e8af4b1750..ee38fb91fe 100644 --- a/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/mod.rs +++ b/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/mod.rs @@ -276,6 +276,7 @@ impl<'a> ExtensionLogic for ForgeExtension<'a> { CallResult::Failure(CallFailure::Error { msg }) => Err( EnhancedHintError::from(HintError::CustomHint(Box::from(msg.to_string()))), ), + _ => unreachable!(), } } "read_txt" => { diff --git a/crates/cheatnet/tests/common/mod.rs b/crates/cheatnet/tests/common/mod.rs index 41f24340e5..636f7c1ce2 100644 --- a/crates/cheatnet/tests/common/mod.rs +++ b/crates/cheatnet/tests/common/mod.rs @@ -71,6 +71,7 @@ pub fn recover_data(output: CallResult) -> Vec { CallFailure::Panic { panic_data, .. } => panic_data, CallFailure::Error { msg, .. } => panic!("Call failed with message: {msg}"), }, + CallResult::DeploySuccess { .. } => unreachable!(), } } diff --git a/crates/debugging/src/trace/collect.rs b/crates/debugging/src/trace/collect.rs index d5da749afe..ad58810613 100644 --- a/crates/debugging/src/trace/collect.rs +++ b/crates/debugging/src/trace/collect.rs @@ -117,6 +117,18 @@ impl<'a> Collector<'a> { .expect("call result should be successfully transformed"); format_result_message("success", &ret_data) } + CheatnetCallResult::DeploySuccess { + ret_data, + contract_address: _contract_address, + } => { + let ret_data = reverse_transform_output( + ret_data, + abi, + &self.call_trace.entry_point.entry_point_selector.0, + ) + .expect("ret data should be successfully transformed"); + format_result_message("success", &ret_data) + } CheatnetCallResult::Failure(failure) => match failure { CallFailure::Panic { panic_data } => { format_result_message("panic", &format_panic_data(panic_data)) diff --git a/crates/forge/tests/data/contracts/deploy_checker.cairo b/crates/forge/tests/data/contracts/deploy_checker.cairo index 19a0f9a83b..d81cd9181c 100644 --- a/crates/forge/tests/data/contracts/deploy_checker.cairo +++ b/crates/forge/tests/data/contracts/deploy_checker.cairo @@ -18,6 +18,7 @@ mod DeployChecker { #[constructor] fn constructor(ref self: ContractState, balance: felt252) -> (ContractAddress, felt252) { + assert(balance != 0, 'Initial balance cannot be 0'); self.balance.write(balance); self.caller.write(starknet::get_caller_address()); (self.caller.read(), balance) diff --git a/crates/forge/tests/integration/deploy.rs b/crates/forge/tests/integration/deploy.rs index 1d740794e5..71c04dce4f 100644 --- a/crates/forge/tests/integration/deploy.rs +++ b/crates/forge/tests/integration/deploy.rs @@ -1,4 +1,4 @@ -use crate::utils::runner::{Contract, assert_passed}; +use crate::utils::runner::{Contract, assert_case_output_contains, assert_failed, assert_passed}; use crate::utils::running_tests::run_test_case; use crate::utils::test_case; use forge_runner::forge_config::ForgeTrackedResource; @@ -257,3 +257,94 @@ fn verify_precalculate_address() { assert_passed(&result); } + +#[test] +fn panic_in_constructor() { + let test = test_case!( + indoc!( + r#" + use snforge_std::{ContractClassTrait, DeclareResultTrait, declare}; + use starknet::SyscallResultTrait; + + #[test] + #[should_panic(expected: 'Initial balance cannot be 0')] + fn constructor_panic() { + let contract = declare("DeployChecker").unwrap().contract_class(); + let constructor_calldata = array![0]; + + contract.deploy(@constructor_calldata).unwrap_syscall(); + } + "# + ), + Contract::from_code_path( + "DeployChecker".to_string(), + Path::new("tests/data/contracts/deploy_checker.cairo"), + ) + .unwrap() + ); + + let result = run_test_case(&test, ForgeTrackedResource::SierraGas); + + assert_passed(&result); +} + +#[test] +fn hard_error_inside_contract() { + let test = test_case!( + indoc!( + r#" + use snforge_std::{ContractClassTrait, DeclareResultTrait, declare}; + use starknet::SyscallResultTrait; + + #[test] + #[should_panic] + fn error_inside_contract() { + let contract = declare("DeployChecker").unwrap().contract_class(); + let deployer_contract = declare("Deployer").unwrap().contract_class(); + let (contract_address, _) = deployer_contract.deploy(@array![]).unwrap_syscall(); + + starknet::syscalls::call_contract_syscall( + contract_address, + selector!("deploy_contract"), + array![(*contract.class_hash).into(), 0].span(), + ).unwrap_syscall(); + } + "# + ), + Contract::new( + "Deployer", + indoc!( + r" + #[starknet::contract] + mod Deployer { + use starknet::SyscallResultTrait; + #[storage] + struct Storage { + } + + #[external(v0)] + fn deploy_contract(self: @ContractState, contract_class_hash: starknet::ClassHash, initial_balance: felt252) { + starknet::syscalls::deploy_syscall( + contract_class_hash, 0x1, array![initial_balance].span(), false, + ).unwrap_syscall(); + } + } + " + ) + ), + Contract::from_code_path( + "DeployChecker".to_string(), + Path::new("tests/data/contracts/deploy_checker.cairo"), + ) + .unwrap() + ); + + let result = run_test_case(&test, ForgeTrackedResource::SierraGas); + + assert_failed(&result); + assert_case_output_contains( + &result, + "error_inside_contract", + "Initial balance cannot be 0", + ); +}