From 7a90ae1c76ef4da409b6845a242e7d2bde19de5b Mon Sep 17 00:00:00 2001 From: Pearson White Date: Wed, 23 Oct 2024 12:36:38 -0400 Subject: [PATCH] feat(blockifier): add library_call cairo native syscall --- .../src/execution/native/syscall_handler.rs | 38 ++++- .../syscalls/syscall_tests/library_call.rs | 138 +++++++++++++----- 2 files changed, 132 insertions(+), 44 deletions(-) diff --git a/crates/blockifier/src/execution/native/syscall_handler.rs b/crates/blockifier/src/execution/native/syscall_handler.rs index 48cf8e76d0..7ea1cbcba3 100644 --- a/crates/blockifier/src/execution/native/syscall_handler.rs +++ b/crates/blockifier/src/execution/native/syscall_handler.rs @@ -1,5 +1,6 @@ use std::collections::HashSet; use std::hash::RandomState; +use std::sync::Arc; use cairo_native::starknet::{ BlockInfo, @@ -13,8 +14,10 @@ use cairo_native::starknet::{ U256, }; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; -use starknet_api::core::EthAddress; +use starknet_api::contract_class::EntryPointType; +use starknet_api::core::{ClassHash, EntryPointSelector, EthAddress}; use starknet_api::state::StorageKey; +use starknet_api::transaction::fields::Calldata; use starknet_api::transaction::L2ToL1Payload; use starknet_types_core::felt::Felt; @@ -26,7 +29,7 @@ use crate::execution::call_info::{ Retdata, }; use crate::execution::common_hints::ExecutionMode; -use crate::execution::entry_point::{CallEntryPoint, EntryPointExecutionContext}; +use crate::execution::entry_point::{CallEntryPoint, CallType, EntryPointExecutionContext}; use crate::execution::errors::EntryPointExecutionError; use crate::execution::syscalls::hint_processor::{ SyscallExecutionError, @@ -76,7 +79,6 @@ impl<'state> NativeSyscallHandler<'state> { } } - #[allow(dead_code)] fn execute_inner_call( &mut self, entry_point: CallEntryPoint, @@ -259,12 +261,32 @@ impl<'state> StarknetSyscallHandler for &mut NativeSyscallHandler<'state> { fn library_call( &mut self, - _class_hash: Felt, - _function_selector: Felt, - _calldata: &[Felt], - _remaining_gas: &mut u128, + class_hash: Felt, + function_selector: Felt, + calldata: &[Felt], + remaining_gas: &mut u128, ) -> SyscallResult> { - todo!("Implement library_call syscall."); + self.pre_execute_syscall(remaining_gas, self.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.call.storage_address, + caller_address: self.call.caller_address, + call_type: CallType::Delegate, + initial_gas: u64::try_from(*remaining_gas) + .expect("Failed to convert gas (u128 -> u64)"), + }; + + Ok(self.execute_inner_call(entry_point, remaining_gas)?.0) } fn call_contract( diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs index 0d71757d2f..4c42be13a5 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use cairo_vm::types::builtin_name::BuiltinName; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use pretty_assertions::assert_eq; +use starknet_api::execution_resources::GasAmount; use starknet_api::execution_utils::format_panic_data; use starknet_api::transaction::fields::GasVectorComputationMode; use starknet_api::{calldata, felt, storage_key}; @@ -28,6 +29,10 @@ use crate::test_utils::{ }; use crate::versioned_constants::VersionedConstants; +#[cfg_attr( + feature = "cairo_native", + test_case(FeatureContract::TestContract(CairoVersion::Native), 189470; "Native") +)] #[test_case(FeatureContract::TestContract(CairoVersion::Cairo1), REQUIRED_GAS_LIBRARY_CALL_TEST; "VM")] fn test_library_call(test_contract: FeatureContract, expected_gas: u64) { let chain_info = &ChainInfo::create_for_testing(); @@ -59,6 +64,10 @@ fn test_library_call(test_contract: FeatureContract, expected_gas: u64) { ); } +#[cfg_attr( + feature = "cairo_native", + test_case(FeatureContract::TestContract(CairoVersion::Native); "Native") +)] #[test_case(FeatureContract::TestContract(CairoVersion::Cairo1); "VM")] fn test_library_call_assert_fails(test_contract: FeatureContract) { let chain_info = &ChainInfo::create_for_testing(); @@ -80,14 +89,45 @@ fn test_library_call_assert_fails(test_contract: FeatureContract) { let call_info = entry_point_call.execute_directly(&mut state).unwrap(); assert!(call_info.execution.failed); - assert_eq!( - format_panic_data(&call_info.execution.retdata.0), - "(0x7820213d2079 ('x != y'), 0x454e545259504f494e545f4641494c4544 ('ENTRYPOINT_FAILED'))" - ); + + let expected_err = match test_contract.cairo_version() { + CairoVersion::Cairo0 | CairoVersion::Cairo1 => { + "(0x7820213d2079 ('x != y'), 0x454e545259504f494e545f4641494c4544 \ + ('ENTRYPOINT_FAILED'))" + } + #[cfg(feature = "cairo_native")] + CairoVersion::Native => "0x7820213d2079 ('x != y')", + }; + assert_eq!(format_panic_data(&call_info.execution.retdata.0), expected_err); } +#[cfg_attr( + feature = "cairo_native", + test_case(FeatureContract::TestContract(CairoVersion::Native), 518110; "Native") +)] #[test_case(FeatureContract::TestContract(CairoVersion::Cairo1), 475110; "VM")] fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { + // Todo(pwhite) 2024/10/28: Execution resources from the VM & Native are mesaured differently + // helper function to change the expected resource values from both of executions + // When gas is changed to be the same between VM and Native this should be removed. + #[cfg_attr(not(feature = "cairo_native"), allow(unused_variables))] + fn if_native(test_contract: &FeatureContract) -> impl Fn(T, T) -> T + '_ { + move |native: T, non_native: T| { + #[cfg(feature = "cairo_native")] + { + if matches!(test_contract, FeatureContract::TestContract(CairoVersion::Native)) { + native + } else { + non_native + } + } + #[cfg(not(feature = "cairo_native"))] + { + non_native + } + } + } + let chain_info = &ChainInfo::create_for_testing(); let mut state = test_state(chain_info, BALANCE, &[(test_contract, 1)]); @@ -108,7 +148,7 @@ fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { entry_point_selector: selector_from_name("test_nested_library_call"), calldata: main_entry_point_calldata, class_hash: Some(test_class_hash), - initial_gas: 9999906600, + initial_gas: if_native(&test_contract)(9999584180, 9999292440), ..trivial_external_entry_point_new(test_contract) }; let nested_storage_entry_point = CallEntryPoint { @@ -117,7 +157,7 @@ fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { class_hash: Some(test_class_hash), code_address: None, call_type: CallType::Delegate, - initial_gas: 9999600120, + initial_gas: if_native(&test_contract)(9999255300, 9998985960), ..trivial_external_entry_point_new(test_contract) }; let library_entry_point = CallEntryPoint { @@ -132,20 +172,33 @@ fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { class_hash: Some(test_class_hash), code_address: None, call_type: CallType::Delegate, - initial_gas: 9999751100, + initial_gas: if_native(&test_contract)(9999417480, 9999136940), ..trivial_external_entry_point_new(test_contract) }; let storage_entry_point = CallEntryPoint { calldata: calldata![felt!(key), felt!(value)], - initial_gas: 9999448480, + initial_gas: if_native(&test_contract)(9999093360, 9998834320), ..nested_storage_entry_point }; - let storage_entry_point_resources = ExecutionResources { - n_steps: 244, - n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 7)]), - }; + let first_storage_entry_point_resources = if_native(&test_contract)( + ChargedResources { + vm_resources: ExecutionResources::default(), + gas_for_fee: GasAmount(27290), + }, + ChargedResources::from_execution_resources(ExecutionResources { + n_steps: 244, + n_memory_holes: 0, + builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 7)]), + }), + ); + let storage_entry_point_resources = if_native(&test_contract)( + ChargedResources { + vm_resources: ExecutionResources::default(), + gas_for_fee: GasAmount(27290), + }, + first_storage_entry_point_resources.clone(), + ); // The default VersionedConstants is used in the execute_directly call bellow. let tracked_resource = test_contract.get_runnable_class().tracked_resource( @@ -157,33 +210,39 @@ fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { call: nested_storage_entry_point, execution: CallExecution { retdata: retdata![felt!(value + 1)], - gas_consumed: REQUIRED_GAS_STORAGE_READ_WRITE_TEST, + gas_consumed: if_native(&test_contract)(27290, REQUIRED_GAS_STORAGE_READ_WRITE_TEST), ..CallExecution::default() }, - charged_resources: ChargedResources::from_execution_resources( - storage_entry_point_resources.clone(), - ), + charged_resources: first_storage_entry_point_resources, tracked_resource, storage_read_values: vec![felt!(value + 1)], accessed_storage_keys: HashSet::from([storage_key!(key + 1)]), ..Default::default() }; - let library_call_resources = &get_syscall_resources(SyscallSelector::LibraryCall) - + &ExecutionResources { - n_steps: 377, - n_memory_holes: 0, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 15)]), - }; + let library_call_resources = if_native(&test_contract)( + ChargedResources { + vm_resources: ExecutionResources::default(), + gas_for_fee: GasAmount(189470), + }, + ChargedResources::from_execution_resources( + &get_syscall_resources(SyscallSelector::LibraryCall) + + &ExecutionResources { + n_steps: 377, + n_memory_holes: 0, + builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 15)]), + }, + ), + ); let library_call_info = CallInfo { call: library_entry_point, execution: CallExecution { retdata: retdata![felt!(value + 1)], - gas_consumed: REQUIRED_GAS_LIBRARY_CALL_TEST, + gas_consumed: if_native(&test_contract)(189470, REQUIRED_GAS_LIBRARY_CALL_TEST), ..CallExecution::default() }, - charged_resources: ChargedResources::from_execution_resources(library_call_resources), + charged_resources: library_call_resources, inner_calls: vec![nested_storage_call_info], tracked_resource, ..Default::default() @@ -193,24 +252,31 @@ fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { call: storage_entry_point, execution: CallExecution { retdata: retdata![felt!(value)], - gas_consumed: REQUIRED_GAS_STORAGE_READ_WRITE_TEST, + gas_consumed: if_native(&test_contract)(27290, REQUIRED_GAS_STORAGE_READ_WRITE_TEST), ..CallExecution::default() }, - charged_resources: ChargedResources::from_execution_resources( - storage_entry_point_resources, - ), + charged_resources: storage_entry_point_resources, storage_read_values: vec![felt!(value)], accessed_storage_keys: HashSet::from([storage_key!(key)]), tracked_resource, ..Default::default() }; - let main_call_resources = &(&get_syscall_resources(SyscallSelector::LibraryCall) * 3) - + &ExecutionResources { - n_steps: 727, - n_memory_holes: 2, - builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 27)]), - }; + let main_call_resources = if_native(&test_contract)( + ChargedResources { + vm_resources: ExecutionResources::default(), + gas_for_fee: GasAmount(518110), + }, + ChargedResources::from_execution_resources( + &(&get_syscall_resources(SyscallSelector::LibraryCall) * 3) + + &ExecutionResources { + n_steps: 727, + n_memory_holes: 2, + builtin_instance_counter: HashMap::from([(BuiltinName::range_check, 27)]), + }, + ), + ); + let expected_call_info = CallInfo { call: main_entry_point.clone(), execution: CallExecution { @@ -218,7 +284,7 @@ fn test_nested_library_call(test_contract: FeatureContract, expected_gas: u64) { gas_consumed: expected_gas, ..CallExecution::default() }, - charged_resources: ChargedResources::from_execution_resources(main_call_resources), + charged_resources: main_call_resources, inner_calls: vec![library_call_info, storage_call_info], tracked_resource, ..Default::default()