diff --git a/crates/blockifier/src/execution/native/entry_point_execution.rs b/crates/blockifier/src/execution/native/entry_point_execution.rs index ee713aa263..863da23582 100644 --- a/crates/blockifier/src/execution/native/entry_point_execution.rs +++ b/crates/blockifier/src/execution/native/entry_point_execution.rs @@ -1,6 +1,7 @@ use cairo_native::execution_result::ContractExecutionResult; use cairo_native::utils::BuiltinCosts; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; +use num_rational::Ratio; use stacker; use crate::execution::call_info::{CallExecution, CallInfo, ChargedResources, Retdata}; @@ -15,6 +16,7 @@ use crate::execution::errors::{EntryPointExecutionError, PostExecutionError}; use crate::execution::native::contract_class::NativeCompiledClassV1; use crate::execution::native::syscall_handler::NativeSyscallHandler; use crate::state::state_api::State; +use crate::versioned_constants::CairoNativeStackConfig; // todo(rodrigo): add an `entry point not found` test for Native pub fn execute_entry_point_call( @@ -53,9 +55,20 @@ pub fn execute_entry_point_call( // The gas upper bound is MAX_POSSIBLE_SIERRA_GAS, and sequencers must not raise it without // adjusting the stack size. // This also limits multi-threading, since each thread has its own stack. - // TODO(Aviv/Yonatan): add these numbers to overridable VC. - let stack_size_red_zone = 160 * 1024 * 1024; - let target_stack_size = stack_size_red_zone + 10 * 1024 * 1024; + // If the the free stack size is in the red zone, We will grow the stack to the + // target size, relative to reaming gas. + let stack_config = CairoNativeStackConfig { + // TODO(Aviv): Take it from VC. + gas_to_stack_ratio: Ratio::new(1, 20), + max_stack_size: 170 * 1024 * 1024, + min_stack_red_zone: 2 * 1024 * 1024, + buffer_size: 1024 * 1024, + }; + let stack_size_red_zone = stack_config.get_stack_size_red_zone( + usize::try_from(syscall_handler.base.call.initial_gas) + .expect("initial gas to usize should always succeed"), + ); + let target_stack_size = stack_config.get_target_stack_size(stack_size_red_zone); // Use `maybe_grow` and not `grow` for performance, since in happy flows, only the main call // should trigger the growth. let execution_result = stacker::maybe_grow(stack_size_red_zone, target_stack_size, || { diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs index e93e26c6be..05e3a1e80c 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/out_of_gas.rs @@ -50,14 +50,24 @@ fn test_total_tx_limits_less_than_max_sierra_gas() { } #[cfg(feature = "cairo_native")] -#[test] +#[rstest::rstest] +#[case(MAX_POSSIBLE_SIERRA_GAS, MAX_POSSIBLE_SIERRA_GAS - 6590)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 10, 349991430)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 100, 34992990)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 1000, 3498420)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 10000, 342810)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 100000, 26370)] +#[case(MAX_POSSIBLE_SIERRA_GAS / 1000000, 0)] +#[case(350, 0)] +#[case(35, 0)] +#[case(0, 0)] /// Tests that Native can handle deep recursion calls without overflowing the stack. /// Note that the recursive function must be complicated, since the compiler might transform /// simple recursions into loops. The tested function was manually tested with higher gas and /// reached stack overflow. /// /// Also, there is no need to test the VM here since it doesn't use the stack. -fn test_stack_overflow() { +fn test_stack_overflow(#[case] initial_gas: u64, #[case] gas_consumed: u64) { let test_contract = FeatureContract::TestContract(CairoVersion::Cairo1(RunnableCairo1::Native)); let mut state = test_state(&ChainInfo::create_for_testing(), BALANCE, &[(test_contract, 1)]); @@ -65,7 +75,7 @@ fn test_stack_overflow() { let entry_point_call = CallEntryPoint { calldata: calldata![depth], entry_point_selector: selector_from_name("test_stack_overflow"), - initial_gas: MAX_POSSIBLE_SIERRA_GAS, + initial_gas, ..trivial_external_entry_point_new(test_contract) }; let call_info = entry_point_call.execute_directly(&mut state).unwrap(); @@ -74,7 +84,7 @@ fn test_stack_overflow() { CallExecution { // 'Out of gas' retdata: retdata![felt!["0x4f7574206f6620676173"]], - gas_consumed: MAX_POSSIBLE_SIERRA_GAS - 6590, + gas_consumed, failed: true, ..Default::default() } diff --git a/crates/blockifier/src/versioned_constants.rs b/crates/blockifier/src/versioned_constants.rs index 928c9b6940..cfe1b7d331 100644 --- a/crates/blockifier/src/versioned_constants.rs +++ b/crates/blockifier/src/versioned_constants.rs @@ -483,6 +483,26 @@ pub struct ArchivalDataGasCosts { pub gas_per_code_byte: ResourceCost, } +pub struct CairoNativeStackConfig { + pub gas_to_stack_ratio: Ratio, + pub max_stack_size: usize, + pub min_stack_red_zone: usize, + pub buffer_size: usize, +} + +impl CairoNativeStackConfig { + pub fn get_stack_size_red_zone(&self, remaining_gas: usize) -> usize { + std::cmp::max( + (self.gas_to_stack_ratio * Ratio::new(remaining_gas, 1)).to_integer(), + self.min_stack_red_zone, + ) + } + + pub fn get_target_stack_size(&self, red_zone: usize) -> usize { + std::cmp::min(red_zone + self.buffer_size, self.max_stack_size) + } +} + #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)] pub struct EventLimits { pub max_data_length: usize,