diff --git a/src/core/contract_address/sierra_contract_address.rs b/src/core/contract_address/sierra_contract_address.rs index b9b9666cd..e1312aaf2 100644 --- a/src/core/contract_address/sierra_contract_address.rs +++ b/src/core/contract_address/sierra_contract_address.rs @@ -21,7 +21,8 @@ fn get_contract_entry_points_hashed( let contract_entry_points = get_contract_entry_points(contract_class, entry_point_type)?; // for each entry_point, we need to store 2 FieldElements: selector and offset. - let mut entry_points_flatted = Vec::with_capacity(contract_entry_points.len() * 2); + let mut entry_points_flatted: Vec = + Vec::with_capacity(contract_entry_points.len() * 2); for entry_point in contract_entry_points { entry_points_flatted.push( diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index d195089e1..cf2789ed9 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -44,7 +44,7 @@ pub struct DeclareV2 { pub sierra_contract_class: SierraContractClass, pub sierra_class_hash: Felt252, pub hash_value: Felt252, - pub casm_class: once_cell::unsync::OnceCell, + pub casm_class: Option, pub skip_validate: bool, pub skip_execute: bool, pub skip_fee_transfer: bool, @@ -159,7 +159,69 @@ impl DeclareV2 { nonce, compiled_class_hash, hash_value, - casm_class: Default::default(), + casm_class: None, + skip_execute: false, + skip_validate: false, + skip_fee_transfer: false, + }; + + verify_version( + &internal_declare.version, + internal_declare.max_fee, + &internal_declare.nonce, + &internal_declare.signature, + )?; + + Ok(internal_declare) + } + + #[allow(clippy::too_many_arguments)] + pub fn new_with_casm( + sierra_contract_class: &SierraContractClass, + sierra_class_hash: Option, + compiled_class_hash: Felt252, + casm_contract_class: CasmContractClass, + chain_id: Felt252, + sender_address: Address, + max_fee: u128, + version: Felt252, + signature: Vec, + nonce: Felt252, + hash_value: Option, + ) -> Result { + let validate_entry_point_selector = VALIDATE_DECLARE_ENTRY_POINT_SELECTOR.clone(); + + let sierra_class_hash = match sierra_class_hash { + Some(h) => h, + None => compute_sierra_class_hash(sierra_contract_class)?, + }; + + let hash_value = match hash_value { + Some(hash) => hash, + None => calculate_declare_v2_transaction_hash( + sierra_class_hash.clone(), + compiled_class_hash.clone(), + chain_id, + &sender_address, + max_fee, + version.clone(), + nonce.clone(), + )?, + }; + + let internal_declare = DeclareV2 { + sierra_contract_class: sierra_contract_class.to_owned(), + sender_address, + sierra_class_hash, + tx_type: TransactionType::Declare, + validate_entry_point_selector, + version, + max_fee, + signature, + nonce, + compiled_class_hash, + hash_value, + casm_class: Some(casm_contract_class), skip_execute: false, skip_validate: false, skip_fee_transfer: false, @@ -313,15 +375,16 @@ impl DeclareV2 { &self, state: &mut S, ) -> Result<(), TransactionError> { - let casm_class = self - .casm_class - .get_or_try_init(|| { + let casm_class = match self.casm_class.clone() { + Some(class) => class, + None => { CasmContractClass::from_contract_class(self.sierra_contract_class.clone(), true) - }) - .map_err(|e| TransactionError::SierraCompileError(e.to_string()))?; + .map_err(|e| TransactionError::SierraCompileError(e.to_string()))? + } + }; state.set_compiled_class_hash(&self.sierra_class_hash, &self.compiled_class_hash)?; - state.set_compiled_class(&self.compiled_class_hash, casm_class.clone())?; + state.set_compiled_class(&self.compiled_class_hash, casm_class)?; Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index ba69dffeb..aeb4cc6f7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,5 @@ +use crate::core::errors::contract_address_errors::ContractAddressError; +use crate::services::api::contract_classes::compiled_class::CompiledClass; use crate::services::api::contract_classes::deprecated_contract_class::EntryPointType; use crate::{ definitions::transaction_type::TransactionType, @@ -11,6 +13,9 @@ use crate::{ syscalls::syscall_handler_errors::SyscallHandlerError, transaction::error::TransactionError, }; +use cairo_lang_starknet::casm_contract_class::CasmContractClass; +use cairo_lang_starknet::contract::starknet_keccak; +use cairo_lang_starknet::contract_class::ContractEntryPoint; use cairo_vm::{ felt::Felt252, serde::deserialize_program::BuiltinName, vm::runners::builtin_runner, }; @@ -18,7 +23,7 @@ use cairo_vm::{types::relocatable::Relocatable, vm::vm_core::VirtualMachine}; use num_traits::{Num, ToPrimitive}; use serde::{Deserialize, Serialize}; use sha3::{Digest, Keccak256}; -use starknet_crypto::FieldElement; +use starknet_crypto::{poseidon_hash_many, poseidon_hash_single, FieldElement}; use std::{ collections::{HashMap, HashSet}, hash::Hash, @@ -307,6 +312,109 @@ pub fn calculate_sn_keccak(data: &[u8]) -> ClassHash { result } +//* ----------------------- +//* Compute compile hash +//* ----------------------- + +fn get_contract_entrypoints( + casm: CasmContractClass, + entry_point_type: EntryPointType, +) -> Result, ContractAddressError> { + let program_length = casm.bytecode.len(); + + let entry_points = match entry_point_type { + EntryPointType::Constructor => casm.entry_points_by_type.constructor.clone(), + EntryPointType::External => casm.entry_points_by_type.external.clone(), + EntryPointType::L1Handler => casm.entry_points_by_type.l1_handler.clone(), + }; + + let program_len = program_length; + for entry_point in &entry_points { + if entry_point.offset > program_len { + return Err(ContractAddressError::InvalidOffset(entry_point.offset)); + } + } + + Ok(entry_points + .iter() + .map(|entry_point| { + ContractEntryPoint::new(entry_point.selector.clone().into(), entry_point.offset) + }) + .collect()) +} + +fn get_contract_entry_points_hashed( + casm: CasmContractClass, + entrypoint_type: &EntryPointType, +) -> Result { + let contract_entry_points = get_contract_entrypoints(casm, *entrypoint_type)?; + + let mut entry_points_flatted: Vec = + Vec::with_capacity(contract_entry_points.len() * 2); + + for entry_point in contract_entry_points { + entry_points_flatted.push(entry_point.selector.into()); + entry_points_flatted.push(FieldElement::from(entry_point.function_idx)); + } + + Ok(poseidon_hash_many(&entry_points_flatted)) +} + +fn compute_class_hash(contract_class: CasmContractClass) -> Felt252 { + // Entrypoints by type, hashed. + let external_functions = + get_contract_entry_points_hashed(contract_class, &EntryPointType::External)?; + let l1_handlers = get_contract_entry_points_hashed(contract_class, &EntryPointType::L1Handler)?; + let constructors = + get_contract_entry_points_hashed(contract_class, &EntryPointType::Constructor)?; + + // Hash abi_hash. + let abi = contract_class + .abi + .clone() + .ok_or(ContractAddressError::MissingAbi)? + .json(); + + let abi_hash = + FieldElement::from_bytes_be(&Felt252::from(starknet_keccak(abi.as_bytes())).to_be_bytes()) + .map_err(|_err| { + ContractAddressError::Cast("Felt252".to_string(), "FieldElement".to_string()) + })?; + + let mut sierra_program_vector = Vec::with_capacity(contract_class.bytecode.len()); + for number in &contract_class.bytecode { + sierra_program_vector.push( + FieldElement::from_bytes_be(&Felt252::from(number.value.clone()).to_be_bytes()) + .map_err(|_err| { + ContractAddressError::Cast("Felt252".to_string(), "FieldElement".to_string()) + })?, + ); + } + + // Hash Sierra program. + let sierra_program_ptr = poseidon_hash_many(&sierra_program_vector); + + let flatted_contract_class = vec![ + api_version, + external_functions, + l1_handlers, + constructors, + abi_hash, + sierra_program_ptr, + ]; + + Ok(Felt252::from_bytes_be( + &poseidon_hash_many(&flatted_contract_class).to_bytes_be(), + )) +} + +pub fn compute_compiled_class_hash(compiled_class: CompiledClass) -> Felt252 { + match compiled_class { + CompiledClass::Casm(class) => compute_class_hash(*class), + _ => unreachable!(), + } +} + //* ------------------------ //* Other utils //* ------------------------