Skip to content

Commit

Permalink
feat(blockifier): add library_call cairo native syscall
Browse files Browse the repository at this point in the history
  • Loading branch information
PearsonWhite authored and varex83 committed Nov 13, 2024
1 parent 2b4f533 commit 0b7374c
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 43 deletions.
37 changes: 30 additions & 7 deletions crates/blockifier/src/execution/native/syscall_handler.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::hash::RandomState;
use std::sync::Arc;

use cairo_native::starknet::{
ExecutionInfo,
Expand All @@ -12,11 +13,14 @@ use cairo_native::starknet::{
};
use cairo_native::starknet_stub::encode_str_as_felts;
use cairo_vm::vm::runners::cairo_runner::ExecutionResources;
use starknet_api::contract_class::EntryPointType;
use starknet_api::core::{ClassHash, EntryPointSelector};
use starknet_api::state::StorageKey;
use starknet_api::transaction::fields::Calldata;
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::entry_point::{CallEntryPoint, CallType, EntryPointExecutionContext};
use crate::execution::syscalls::hint_processor::{SyscallExecutionError, OUT_OF_GAS_ERROR};
use crate::state::state_api::State;

Expand Down Expand Up @@ -57,7 +61,6 @@ impl<'state> NativeSyscallHandler<'state> {
}
}

#[allow(dead_code)]
fn execute_inner_call(
&mut self,
entry_point: CallEntryPoint,
Expand Down Expand Up @@ -158,12 +161,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<Vec<Felt>> {
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(
Expand Down
131 changes: 95 additions & 36 deletions crates/blockifier/src/execution/syscalls/syscall_tests/library_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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<T>(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)]);

Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -132,20 +172,26 @@ 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 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)]),
}),
);

// The default VersionedConstants is used in the execute_directly call bellow.
let tracked_resource = test_contract.get_runnable_class().tracked_resource(
Expand All @@ -157,33 +203,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: storage_entry_point_resources.clone(),
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()
Expand All @@ -193,32 +245,39 @@ 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 {
retdata: retdata![felt!(value)],
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()
Expand Down

0 comments on commit 0b7374c

Please sign in to comment.