diff --git a/crates/blockifier/resources/versioned_constants_0_13_0.json b/crates/blockifier/resources/versioned_constants_0_13_0.json index 3fd5450844..3e1b7703e0 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_0.json +++ b/crates/blockifier/resources/versioned_constants_0_13_0.json @@ -122,6 +122,10 @@ "syscall_base_gas_cost": 1, "step_gas_cost": 50 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 0, + "syscall_base_gas_cost": 0 + }, "emit_event_gas_cost": { "syscall_base_gas_cost": 1, "step_gas_cost": 10 @@ -397,6 +401,11 @@ "builtin_instance_counter": {}, "n_memory_holes": 0, "n_steps": 46 + }, + "GetClassHashAt": { + "n_steps": 0, + "builtin_instance_counter": {}, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/resources/versioned_constants_0_13_1.json b/crates/blockifier/resources/versioned_constants_0_13_1.json index 21113841a1..3a12982a71 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_1.json +++ b/crates/blockifier/resources/versioned_constants_0_13_1.json @@ -122,6 +122,10 @@ "syscall_base_gas_cost": 1, "step_gas_cost": 50 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 0, + "syscall_base_gas_cost": 0 + }, "emit_event_gas_cost": { "syscall_base_gas_cost": 1, "step_gas_cost": 10 @@ -421,6 +425,11 @@ "range_check_builtin": 1 }, "n_memory_holes": 0 + }, + "GetClassHashAt": { + "n_steps": 0, + "builtin_instance_counter": {}, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/resources/versioned_constants_0_13_1_1.json b/crates/blockifier/resources/versioned_constants_0_13_1_1.json index d557eeeb84..533d2c5cb8 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_1_1.json +++ b/crates/blockifier/resources/versioned_constants_0_13_1_1.json @@ -122,6 +122,10 @@ "syscall_base_gas_cost": 1, "step_gas_cost": 50 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 0, + "syscall_base_gas_cost": 0 + }, "emit_event_gas_cost": { "syscall_base_gas_cost": 1, "step_gas_cost": 10 @@ -421,6 +425,11 @@ "range_check_builtin": 1 }, "n_memory_holes": 0 + }, + "GetClassHashAt": { + "n_steps": 0, + "builtin_instance_counter": {}, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/resources/versioned_constants_0_13_2.json b/crates/blockifier/resources/versioned_constants_0_13_2.json index 9e89acd229..cb35171d91 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_2.json +++ b/crates/blockifier/resources/versioned_constants_0_13_2.json @@ -185,6 +185,10 @@ "step_gas_cost": 50, "syscall_base_gas_cost": 1 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 0, + "syscall_base_gas_cost": 0 + }, "stored_block_hash_buffer": 10, "syscall_base_gas_cost": { "step_gas_cost": 100 @@ -425,6 +429,11 @@ "range_check_builtin": 1 }, "n_memory_holes": 0 + }, + "GetClassHashAt": { + "n_steps": 0, + "builtin_instance_counter": {}, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/resources/versioned_constants_0_13_2_1.json b/crates/blockifier/resources/versioned_constants_0_13_2_1.json index 2934a76e22..fdda72b43b 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_2_1.json +++ b/crates/blockifier/resources/versioned_constants_0_13_2_1.json @@ -185,6 +185,10 @@ "step_gas_cost": 50, "syscall_base_gas_cost": 1 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 0, + "syscall_base_gas_cost": 0 + }, "stored_block_hash_buffer": 10, "syscall_base_gas_cost": { "step_gas_cost": 100 @@ -425,6 +429,11 @@ "range_check_builtin": 1 }, "n_memory_holes": 0 + }, + "GetClassHashAt": { + "n_steps": 0, + "builtin_instance_counter": {}, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/resources/versioned_constants_0_13_3.json b/crates/blockifier/resources/versioned_constants_0_13_3.json index 2934a76e22..fdda72b43b 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_3.json +++ b/crates/blockifier/resources/versioned_constants_0_13_3.json @@ -185,6 +185,10 @@ "step_gas_cost": 50, "syscall_base_gas_cost": 1 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 0, + "syscall_base_gas_cost": 0 + }, "stored_block_hash_buffer": 10, "syscall_base_gas_cost": { "step_gas_cost": 100 @@ -425,6 +429,11 @@ "range_check_builtin": 1 }, "n_memory_holes": 0 + }, + "GetClassHashAt": { + "n_steps": 0, + "builtin_instance_counter": {}, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/resources/versioned_constants_0_13_4.json b/crates/blockifier/resources/versioned_constants_0_13_4.json index 934326e145..eac76d6a7a 100644 --- a/crates/blockifier/resources/versioned_constants_0_13_4.json +++ b/crates/blockifier/resources/versioned_constants_0_13_4.json @@ -186,6 +186,10 @@ "step_gas_cost": 100, "range_check_gas_cost": 1 }, + "get_class_hash_at_gas_cost": { + "step_gas_cost": 100, + "range_check_gas_cost": 1 + }, "stored_block_hash_buffer": 10, "syscall_base_gas_cost": { "step_gas_cost": 100 @@ -427,6 +431,13 @@ "range_check_builtin": 1 }, "n_memory_holes": 0 + }, + "GetClassHashAt": { + "n_steps": 100, + "builtin_instance_counter": { + "range_check_builtin": 1 + }, + "n_memory_holes": 0 } }, "execute_txs_inner": { diff --git a/crates/blockifier/src/execution/call_info.rs b/crates/blockifier/src/execution/call_info.rs index 42398e82fe..ee47c07b27 100644 --- a/crates/blockifier/src/execution/call_info.rs +++ b/crates/blockifier/src/execution/call_info.rs @@ -4,7 +4,7 @@ use std::ops::Add; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use serde::Serialize; -use starknet_api::core::{ClassHash, EthAddress}; +use starknet_api::core::{ClassHash, ContractAddress, EthAddress}; use starknet_api::execution_resources::GasAmount; use starknet_api::state::StorageKey; use starknet_api::transaction::{EventContent, L2ToL1Payload}; @@ -126,6 +126,8 @@ pub struct CallInfo { // Additional information gathered during execution. pub storage_read_values: Vec, pub accessed_storage_keys: HashSet, + pub read_class_hash_values: Vec, + pub accessed_contract_addresses: HashSet, } impl CallInfo { diff --git a/crates/blockifier/src/execution/deprecated_entry_point_execution.rs b/crates/blockifier/src/execution/deprecated_entry_point_execution.rs index becee618c7..ab7d06a79e 100644 --- a/crates/blockifier/src/execution/deprecated_entry_point_execution.rs +++ b/crates/blockifier/src/execution/deprecated_entry_point_execution.rs @@ -285,6 +285,7 @@ pub fn finalize_execution( ), storage_read_values: syscall_handler.read_values, accessed_storage_keys: syscall_handler.accessed_keys, + ..Default::default() }) } diff --git a/crates/blockifier/src/execution/deprecated_syscalls/mod.rs b/crates/blockifier/src/execution/deprecated_syscalls/mod.rs index acdc54b386..bded7975de 100644 --- a/crates/blockifier/src/execution/deprecated_syscalls/mod.rs +++ b/crates/blockifier/src/execution/deprecated_syscalls/mod.rs @@ -57,6 +57,7 @@ pub enum DeprecatedSyscallSelector { GetBlockNumber, GetBlockTimestamp, GetCallerAddress, + GetClassHashAt, GetContractAddress, GetExecutionInfo, GetSequencerAddress, @@ -99,6 +100,7 @@ impl TryFrom for DeprecatedSyscallSelector { b"GetBlockNumber" => Ok(Self::GetBlockNumber), b"GetBlockTimestamp" => Ok(Self::GetBlockTimestamp), b"GetCallerAddress" => Ok(Self::GetCallerAddress), + b"GetClassHashAt" => Ok(Self::GetClassHashAt), b"GetContractAddress" => Ok(Self::GetContractAddress), b"GetExecutionInfo" => Ok(Self::GetExecutionInfo), b"GetSequencerAddress" => Ok(Self::GetSequencerAddress), diff --git a/crates/blockifier/src/execution/entry_point_execution.rs b/crates/blockifier/src/execution/entry_point_execution.rs index d747271c08..1b40aa7b57 100644 --- a/crates/blockifier/src/execution/entry_point_execution.rs +++ b/crates/blockifier/src/execution/entry_point_execution.rs @@ -458,6 +458,8 @@ pub fn finalize_execution( charged_resources, storage_read_values: syscall_handler.read_values, accessed_storage_keys: syscall_handler.accessed_keys, + read_class_hash_values: syscall_handler.read_class_hash_values, + accessed_contract_addresses: syscall_handler.accessed_contract_addresses, }) } diff --git a/crates/blockifier/src/execution/native/entry_point_execution.rs b/crates/blockifier/src/execution/native/entry_point_execution.rs index 2b1d22b19d..b6a5297ac1 100644 --- a/crates/blockifier/src/execution/native/entry_point_execution.rs +++ b/crates/blockifier/src/execution/native/entry_point_execution.rs @@ -38,6 +38,7 @@ pub fn execute_entry_point_call( create_callinfo(call_result, syscall_handler) } +#[allow(unreachable_code)] fn create_callinfo( call_result: ContractExecutionResult, syscall_handler: NativeSyscallHandler<'_>, @@ -78,6 +79,11 @@ fn create_callinfo( inner_calls: syscall_handler.inner_calls, storage_read_values: syscall_handler.read_values, accessed_storage_keys: syscall_handler.accessed_keys, + // TODO(Aviv): The syscall is not supported here yet. + // Currently, `accessed_contract_addresses` and `read_class_hash_values` are initialized + // as empty. Support for handling accessed storage keys via syscalls should be implemented. + accessed_contract_addresses: Default::default(), + read_class_hash_values: Default::default(), tracked_resource: TrackedResource::SierraGas, }) } diff --git a/crates/blockifier/src/execution/syscalls/hint_processor.rs b/crates/blockifier/src/execution/syscalls/hint_processor.rs index e6e3768b7e..a7fb67f61c 100644 --- a/crates/blockifier/src/execution/syscalls/hint_processor.rs +++ b/crates/blockifier/src/execution/syscalls/hint_processor.rs @@ -57,6 +57,7 @@ use crate::execution::syscalls::{ deploy, emit_event, get_block_hash, + get_class_hash_at, get_execution_info, keccak, library_call, @@ -233,6 +234,9 @@ pub struct SyscallHintProcessor<'a> { // Additional information gathered during execution. pub read_values: Vec, pub accessed_keys: HashSet, + pub read_class_hash_values: Vec, + // Accessed addresses by the `get_class_hash_at` syscall. + pub accessed_contract_addresses: HashSet, // The original storage value of the executed contract. // Should be moved back `context.revert_info` before executing an inner call. @@ -282,6 +286,8 @@ impl<'a> SyscallHintProcessor<'a> { syscall_ptr: initial_syscall_ptr, read_values: vec![], accessed_keys: HashSet::new(), + read_class_hash_values: vec![], + accessed_contract_addresses: HashSet::new(), original_values, hints, execution_info_ptr: None, @@ -349,6 +355,11 @@ impl<'a> SyscallHintProcessor<'a> { get_block_hash, self.context.gas_costs().get_block_hash_gas_cost, ), + SyscallSelector::GetClassHashAt => self.execute_syscall( + vm, + get_class_hash_at, + self.context.gas_costs().get_class_hash_at_gas_cost, + ), SyscallSelector::GetExecutionInfo => self.execute_syscall( vm, get_execution_info, diff --git a/crates/blockifier/src/execution/syscalls/mod.rs b/crates/blockifier/src/execution/syscalls/mod.rs index b10b463ddd..1f84bfa4f7 100644 --- a/crates/blockifier/src/execution/syscalls/mod.rs +++ b/crates/blockifier/src/execution/syscalls/mod.rs @@ -786,3 +786,34 @@ pub fn sha_256_process_block( Ok(Sha256ProcessBlockResponse { state_ptr: response }) } + +// GetClassHashAt syscall. + +pub(crate) type GetClassHashAtRequest = ContractAddress; +pub(crate) type GetClassHashAtResponse = ClassHash; + +impl SyscallRequest for GetClassHashAtRequest { + fn read(vm: &VirtualMachine, ptr: &mut Relocatable) -> SyscallResult { + let address = ContractAddress::try_from(felt_from_ptr(vm, ptr)?)?; + Ok(address) + } +} + +impl SyscallResponse for GetClassHashAtResponse { + fn write(self, vm: &mut VirtualMachine, ptr: &mut Relocatable) -> WriteResponseResult { + write_felt(vm, ptr, *self)?; + Ok(()) + } +} + +pub(crate) fn get_class_hash_at( + request: GetClassHashAtRequest, + _vm: &mut VirtualMachine, + syscall_handler: &mut SyscallHintProcessor<'_>, + _remaining_gas: &mut u64, +) -> SyscallResult { + syscall_handler.accessed_contract_addresses.insert(request); + let class_hash = syscall_handler.state.get_class_hash_at(request)?; + syscall_handler.read_class_hash_values.push(class_hash); + Ok(class_hash) +} diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/constants.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/constants.rs index 3563be4c30..daf276fd65 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/constants.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/constants.rs @@ -1,3 +1,4 @@ pub const REQUIRED_GAS_CALL_CONTRACT_TEST: u64 = 170370; pub const REQUIRED_GAS_STORAGE_READ_WRITE_TEST: u64 = 16990; +pub const REQUIRED_GAS_GET_CLASS_HASH_AT_TEST: u64 = 7830; pub const REQUIRED_GAS_LIBRARY_CALL_TEST: u64 = 167970; diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/get_class_hash_at.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/get_class_hash_at.rs new file mode 100644 index 0000000000..108af5e5d3 --- /dev/null +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/get_class_hash_at.rs @@ -0,0 +1,71 @@ +use starknet_api::{calldata, class_hash, contract_address, felt}; +use test_case::test_case; + +use crate::abi::abi_utils::selector_from_name; +use crate::context::ChainInfo; +use crate::execution::call_info::CallExecution; +use crate::execution::entry_point::CallEntryPoint; +use crate::execution::syscalls::syscall_tests::constants::REQUIRED_GAS_GET_CLASS_HASH_AT_TEST; +use crate::retdata; +use crate::test_utils::contracts::FeatureContract; +use crate::test_utils::initial_test_state::test_state; +use crate::test_utils::{trivial_external_entry_point_new, CairoVersion, BALANCE}; + +/// Tests the `get_class_hash_at` syscall, ensuring that: +/// 1. `accessed_contract_addresses` contains `address` for a valid entry. +/// 2. `read_class_hash_values` includes `class_hash`. +/// 3. Execution succeeds with expected gas for valid cases. +/// 4. Execution fails if `address` has a different `class_hash`. +/// 5. Execution succeeds and returns `class_hash` = 0 if `address` is absent. +#[test_case(FeatureContract::TestContract(CairoVersion::Cairo1), REQUIRED_GAS_GET_CLASS_HASH_AT_TEST; "VM")] +fn test_get_class_hash_at(test_contract: FeatureContract, expected_gas: u64) { + let chain_info = &ChainInfo::create_for_testing(); + let mut state = test_state(chain_info, BALANCE, &[(test_contract, 1)]); + let address = contract_address!("0x111"); + let class_hash = class_hash!("0x222"); + state.state.address_to_class_hash.insert(address, class_hash); + + // Test deployed contract. + let positive_entry_point_call = CallEntryPoint { + calldata: calldata![address.into(), class_hash.0], + entry_point_selector: selector_from_name("test_get_class_hash_at"), + ..trivial_external_entry_point_new(test_contract) + }; + let positive_call_info = positive_entry_point_call.execute_directly(&mut state).unwrap(); + assert!(positive_call_info.accessed_contract_addresses.contains(&address)); + assert!(positive_call_info.read_class_hash_values[0] == class_hash); + assert_eq!( + positive_call_info.execution, + CallExecution { + retdata: retdata!(), + gas_consumed: expected_gas, + failed: false, + ..CallExecution::default() + } + ); + // Test undeployed contract - should return class_hash = 0 and succeed. + let non_existing_address = felt!("0x333"); + let class_hash_of_undeployed_contract = felt!("0x0"); + + let negative_entry_point_call = CallEntryPoint { + calldata: calldata![non_existing_address, class_hash_of_undeployed_contract], + entry_point_selector: selector_from_name("test_get_class_hash_at"), + ..trivial_external_entry_point_new(test_contract) + }; + assert!(!negative_entry_point_call.execute_directly(&mut state).unwrap().execution.failed); + + // Sanity check: giving the wrong expected class hash to the test should make it fail. + let different_class_hash = class_hash!("0x444"); + let different_class_hash_entry_point_call = CallEntryPoint { + calldata: calldata![address.into(), different_class_hash.0], + entry_point_selector: selector_from_name("test_get_class_hash_at"), + ..trivial_external_entry_point_new(test_contract) + }; + assert!( + different_class_hash_entry_point_call + .execute_directly(&mut state) + .unwrap() + .execution + .failed + ); +} diff --git a/crates/blockifier/src/execution/syscalls/syscall_tests/mod.rs b/crates/blockifier/src/execution/syscalls/syscall_tests/mod.rs index fbebf14ba8..981449e0ce 100644 --- a/crates/blockifier/src/execution/syscalls/syscall_tests/mod.rs +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/mod.rs @@ -4,6 +4,7 @@ mod deploy; mod emit_event; mod failure_format; mod get_block_hash; +mod get_class_hash_at; mod get_execution_info; mod keccak; mod library_call; diff --git a/crates/blockifier/src/versioned_constants.rs b/crates/blockifier/src/versioned_constants.rs index e648a8e0c2..d2d6eb7237 100644 --- a/crates/blockifier/src/versioned_constants.rs +++ b/crates/blockifier/src/versioned_constants.rs @@ -547,6 +547,7 @@ pub struct GasCosts { pub replace_class_gas_cost: u64, pub storage_read_gas_cost: u64, pub storage_write_gas_cost: u64, + pub get_class_hash_at_gas_cost: u64, pub emit_event_gas_cost: u64, pub send_message_to_l1_gas_cost: u64, pub secp256k1_add_gas_cost: u64,