diff --git a/crates/blockifier/resources/versioned_constants_0_13_4.json b/crates/blockifier/resources/versioned_constants_0_13_4.json index 934326e145..ce997cca5f 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": { @@ -634,4 +645,4 @@ 10000 ] } -} +} \ No newline at end of file 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..01bb6673d4 100644 --- a/crates/blockifier/src/execution/deprecated_entry_point_execution.rs +++ b/crates/blockifier/src/execution/deprecated_entry_point_execution.rs @@ -285,6 +285,8 @@ pub fn finalize_execution( ), storage_read_values: syscall_handler.read_values, accessed_storage_keys: syscall_handler.accessed_keys, + accessed_contract_addresses: syscall_handler.accessed_contract_addresses, + read_class_hash_values: syscall_handler.read_class_hash_values, }) } diff --git a/crates/blockifier/src/execution/deprecated_syscalls/hint_processor.rs b/crates/blockifier/src/execution/deprecated_syscalls/hint_processor.rs index 4cb8d872d5..dcc136b22b 100644 --- a/crates/blockifier/src/execution/deprecated_syscalls/hint_processor.rs +++ b/crates/blockifier/src/execution/deprecated_syscalls/hint_processor.rs @@ -184,6 +184,9 @@ pub struct DeprecatedSyscallHintProcessor<'a> { pub read_values: Vec, pub accessed_keys: HashSet, + pub read_class_hash_values: Vec, + pub accessed_contract_addresses: HashSet, + // Additional fields. // Invariant: must only contain allowed hints. builtin_hint_processor: BuiltinHintProcessor, @@ -215,6 +218,8 @@ impl<'a> DeprecatedSyscallHintProcessor<'a> { syscall_ptr: initial_syscall_ptr, read_values: vec![], accessed_keys: HashSet::new(), + read_class_hash_values: vec![], + accessed_contract_addresses: HashSet::new(), builtin_hint_processor: extended_builtin_hint_processor(), tx_signature_start_ptr: None, tx_info_start_ptr: None, diff --git a/crates/blockifier/src/execution/deprecated_syscalls/mod.rs b/crates/blockifier/src/execution/deprecated_syscalls/mod.rs index 5f61229f02..cea076519d 100644 --- a/crates/blockifier/src/execution/deprecated_syscalls/mod.rs +++ b/crates/blockifier/src/execution/deprecated_syscalls/mod.rs @@ -86,6 +86,7 @@ pub enum DeprecatedSyscallSelector { SendMessageToL1, StorageRead, StorageWrite, + GetClassHashAt, } impl TryFrom for DeprecatedSyscallSelector { @@ -128,6 +129,7 @@ impl TryFrom for DeprecatedSyscallSelector { b"SendMessageToL1" => Ok(Self::SendMessageToL1), b"StorageRead" => Ok(Self::StorageRead), b"StorageWrite" => Ok(Self::StorageWrite), + b"GetClassHashAt" => Ok(Self::GetClassHashAt), _ => { Err(DeprecatedSyscallExecutionError::InvalidDeprecatedSyscallSelector(raw_selector)) } 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/syscalls/hint_processor.rs b/crates/blockifier/src/execution/syscalls/hint_processor.rs index 3fd8060412..2a94ebc8ba 100644 --- a/crates/blockifier/src/execution/syscalls/hint_processor.rs +++ b/crates/blockifier/src/execution/syscalls/hint_processor.rs @@ -52,6 +52,7 @@ use crate::execution::syscalls::{ deploy, emit_event, get_block_hash, + get_class_hash_at, get_execution_info, keccak, library_call, @@ -229,6 +230,9 @@ pub struct SyscallHintProcessor<'a> { pub read_values: Vec, pub accessed_keys: HashSet, + pub read_class_hash_values: Vec, + 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. pub original_values: HashMap, @@ -277,6 +281,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, @@ -432,6 +438,11 @@ impl<'a> SyscallHintProcessor<'a> { storage_write, self.context.gas_costs().storage_write_gas_cost, ), + SyscallSelector::GetClassHashAt => self.execute_syscall( + vm, + get_class_hash_at, + self.context.gas_costs().get_class_hash_at_gas_cost, + ), _ => Err(HintError::UnknownHint( format!("Unsupported syscall selector {selector:?}.").into(), )), diff --git a/crates/blockifier/src/execution/syscalls/mod.rs b/crates/blockifier/src/execution/syscalls/mod.rs index a906faf81e..7eecb7200d 100644 --- a/crates/blockifier/src/execution/syscalls/mod.rs +++ b/crates/blockifier/src/execution/syscalls/mod.rs @@ -792,3 +792,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..091cbe8f1a --- /dev/null +++ b/crates/blockifier/src/execution/syscalls/syscall_tests/get_class_hash_at.rs @@ -0,0 +1,69 @@ +use starknet_api::{calldata, class_hash, contract_address}; +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` is absent or has a different `class_hash`. +#[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); + + // Positive case: address and class_hash are correct + 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.contains(&class_hash)); + assert_eq!( + positive_call_info.execution, + CallExecution { + retdata: retdata!(), + gas_consumed: expected_gas, + failed: false, + ..CallExecution::default() + } + ); + + // Negative case: address not present in state + let non_existing_address = contract_address!("0x333"); + let negative_entry_point_call = CallEntryPoint { + calldata: calldata![non_existing_address.into(), class_hash.0], + 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); + + // Case with existing address but different class_hash + 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 a50a696f78..a00300b841 100644 --- a/crates/blockifier/src/versioned_constants.rs +++ b/crates/blockifier/src/versioned_constants.rs @@ -565,6 +565,8 @@ pub struct GasCosts { pub replace_class_gas_cost: u64, pub storage_read_gas_cost: u64, pub storage_write_gas_cost: u64, + #[serde(default)] + 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,