From fe0aed362cf7e2f15d96a76456932854e789d356 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Wed, 12 Jul 2023 17:20:02 -0300 Subject: [PATCH 01/12] Add casm class hash --- Makefile | 4 +- .../contract_address/casm_contract_address.rs | 190 ++++++++++++++++++ src/core/contract_address/mod.rs | 2 + 3 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/core/contract_address/casm_contract_address.rs diff --git a/Makefile b/Makefile index 63e3f52fe..cd1a82ed9 100644 --- a/Makefile +++ b/Makefile @@ -70,7 +70,7 @@ $(CAIRO_1_CONTRACTS_TEST_DIR)/%.sierra: $(CAIRO_1_CONTRACTS_TEST_DIR)/%.cairo $(STARKNET_COMPILE_CAIRO_1) --allowed-libfuncs-list-name experimental_v0.1.0 $< $@ $(CAIRO_1_CONTRACTS_TEST_DIR)/%.casm: $(CAIRO_1_CONTRACTS_TEST_DIR)/%.sierra - $(STARKNET_SIERRA_COMPILE_CAIRO_1) --allowed-libfuncs-list-name experimental_v0.1.0 $< $@ + $(STARKNET_SIERRA_COMPILE_CAIRO_1) --allowed-libfuncs-list-name experimental_v0.1.0 --add-pythonic-hints $< $@ cairo-repo-1-dir = cairo1 @@ -103,7 +103,7 @@ $(CAIRO_2_CONTRACTS_TEST_DIR)/%.sierra: $(CAIRO_2_CONTRACTS_TEST_DIR)/%.cairo $(STARKNET_COMPILE_CAIRO_2) $< $@ $(CAIRO_2_CONTRACTS_TEST_DIR)/%.casm: $(CAIRO_2_CONTRACTS_TEST_DIR)/%.sierra - $(STARKNET_SIERRA_COMPILE_CAIRO_2) $< $@ + $(STARKNET_SIERRA_COMPILE_CAIRO_2) --add-pythonic-hints $< $@ cairo-repo-2-dir = cairo2 diff --git a/src/core/contract_address/casm_contract_address.rs b/src/core/contract_address/casm_contract_address.rs new file mode 100644 index 000000000..419e72411 --- /dev/null +++ b/src/core/contract_address/casm_contract_address.rs @@ -0,0 +1,190 @@ +use crate::core::errors::contract_address_errors::ContractAddressError; +use crate::services::api::contract_classes::deprecated_contract_class::EntryPointType; +use cairo_lang_starknet::casm_contract_class::{CasmContractClass, CasmContractEntryPoint}; +use cairo_vm::felt::Felt252; +use starknet_crypto::{poseidon_hash_many, FieldElement}; + +const CONTRACT_CLASS_VERSION: &[u8] = b"COMPILED_CLASS_V1"; + +fn get_contract_entry_points_hashed( + contract_class: &CasmContractClass, + entry_point_type: &EntryPointType, +) -> Result { + let contract_entry_points = get_contract_entry_points(contract_class, entry_point_type); + + // for each entry_point, we need to store 3 FieldElements: [selector, offset, poseidon_hash_many(builtin_list)]. + let mut entry_points_flatted = Vec::with_capacity(contract_entry_points.len() * 3); + + for entry_point in contract_entry_points { + entry_points_flatted.push( + // fix this conversion later + FieldElement::from_bytes_be(&Felt252::from(entry_point.selector).to_be_bytes()) + .map_err(|_err| { + ContractAddressError::Cast("Felt252".to_string(), "FieldElement".to_string()) + })?, + ); + entry_points_flatted.push(FieldElement::from(entry_point.offset)); + let mut builtins_flatted = Vec::new(); + entry_point.builtins.iter().for_each(|builtin| { + builtins_flatted.push(FieldElement::from_byte_slice_be(builtin.as_bytes()).unwrap()); + }); + entry_points_flatted.push(poseidon_hash_many(&builtins_flatted)); + } + + Ok(poseidon_hash_many(&entry_points_flatted)) +} + +pub fn compute_casm_class_hash( + contract_class: &CasmContractClass, +) -> Result { + let api_version = + FieldElement::from_bytes_be(&Felt252::from_bytes_be(CONTRACT_CLASS_VERSION).to_be_bytes()) + .map_err(|_err| { + ContractAddressError::Cast("Felt252".to_string(), "FieldElement".to_string()) + })?; + + // 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)?; + + let mut casm_program_vector = Vec::with_capacity(contract_class.bytecode.len()); + for number in &contract_class.bytecode { + casm_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 casm program. + let casm_program_ptr = poseidon_hash_many(&casm_program_vector); + + let flatted_contract_class = vec![ + api_version, + external_functions, + l1_handlers, + constructors, + casm_program_ptr, + ]; + + Ok(Felt252::from_bytes_be( + &poseidon_hash_many(&flatted_contract_class).to_bytes_be(), + )) +} + +fn get_contract_entry_points( + contract_class: &CasmContractClass, + entry_point_type: &EntryPointType, +) -> Vec { + match entry_point_type { + EntryPointType::Constructor => contract_class.entry_points_by_type.constructor.clone(), + EntryPointType::External => contract_class.entry_points_by_type.external.clone(), + EntryPointType::L1Handler => contract_class.entry_points_by_type.l1_handler.clone(), + } +} + +#[cfg(test)] +mod tests { + /// THE VALUES IN THIS TESTS WERE TAKEN FROM THE CONTRACTS IN THE STARKNET_PROGRAMS FOLDER. + /// AND WE USE A [TOOL FOUND IN CAIRO-LANG](https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/starknet/cli/compiled_class_hash.py) + /// TO GET THE RIGHT HASH VALUE. + use std::{fs::File, io::BufReader}; + + use super::*; + use cairo_vm::felt::felt_str; + use coverage_helper::test; + + #[test] + fn test_compute_casm_class_hash_contract_a() { + // Open the file in read-only mode with buffer. + let file = File::open("starknet_programs/cairo1/contract_a.casm").unwrap(); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `User`. + let contract_class: CasmContractClass = serde_json::from_reader(reader).unwrap(); + + assert_eq!( + compute_casm_class_hash(&contract_class).unwrap(), + felt_str!( + "3a4f00bf75ba3b9230a94f104c7a4605a1901c4bd475beb59eeeeb7aceb9795", + 16 + ) + ); + } + + #[test] + fn test_compute_casm_class_hash_deploy() { + // Open the file in read-only mode with buffer. + let file = File::open("starknet_programs/cairo1/deploy.casm").unwrap(); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `User`. + let contract_class: CasmContractClass = serde_json::from_reader(reader).unwrap(); + + assert_eq!( + compute_casm_class_hash(&contract_class).unwrap(), + felt_str!( + "3bd56f1c3c1c595ac2ee6d07bdedc027d09df56235e20374649f0b3535c1f15", + 16 + ) + ); + } + + #[test] + fn test_compute_casm_class_hash_fibonacci() { + // Open the file in read-only mode with buffer. + let file = File::open("starknet_programs/cairo1/fibonacci.casm").unwrap(); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `User`. + let contract_class: CasmContractClass = serde_json::from_reader(reader).unwrap(); + + assert_eq!( + compute_casm_class_hash(&contract_class).unwrap(), + felt_str!( + "44f12e6e59232e9909d7428b913b3cc8d9059458e5027740a3ccdbdc4b1ffd2", + 16 + ) + ); + } + + #[test] + fn test_compute_casm_class_hash_factorial() { + // Open the file in read-only mode with buffer. + let file = File::open("starknet_programs/cairo1/factorial.casm").unwrap(); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `User`. + let contract_class: CasmContractClass = serde_json::from_reader(reader).unwrap(); + + assert_eq!( + compute_casm_class_hash(&contract_class).unwrap(), + felt_str!( + "189a9b8b852aedbb225aa28dce9cfc3133145dd623e2d2ca5e962b7d4e61e15", + 16 + ) + ); + } + + #[test] + fn test_compute_casm_class_hash_emit_event() { + // Open the file in read-only mode with buffer. + let file = File::open("starknet_programs/cairo1/emit_event.casm").unwrap(); + let reader = BufReader::new(file); + + // Read the JSON contents of the file as an instance of `User`. + let contract_class: CasmContractClass = serde_json::from_reader(reader).unwrap(); + + assert_eq!( + compute_casm_class_hash(&contract_class).unwrap(), + felt_str!( + "3335fe731ceda1116eda8bbc2e282953ce54618309ad474189e627c59328fff", + 16 + ) + ); + } +} diff --git a/src/core/contract_address/mod.rs b/src/core/contract_address/mod.rs index f601f646e..40641fb27 100644 --- a/src/core/contract_address/mod.rs +++ b/src/core/contract_address/mod.rs @@ -1,6 +1,8 @@ +mod casm_contract_address; mod deprecated_contract_address; mod sierra_contract_address; +pub use casm_contract_address::compute_casm_class_hash; pub use deprecated_contract_address::compute_deprecated_class_hash; pub(crate) use deprecated_contract_address::compute_hinted_class_hash; pub use sierra_contract_address::compute_sierra_class_hash; From 8d2b745af78657dddade88b5f37a9bc3971bc652 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Wed, 12 Jul 2023 18:36:47 -0300 Subject: [PATCH 02/12] add casm class hash check --- src/lib.rs | 16 +++++++++++++++- src/testing/mod.rs | 2 +- src/transaction/declare_v2.rs | 13 ++++++++++--- src/transaction/error.rs | 2 ++ tests/internals.rs | 2 +- 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 043a25fb9..c09a56ddf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -836,7 +836,7 @@ mod test { tx_type: TransactionType::Declare, validate_entry_point_selector: VALIDATE_DECLARE_ENTRY_POINT_SELECTOR.clone(), version: 1.into(), - max_fee: 2, + max_fee: 100_000_000, signature: vec![], nonce: 0.into(), hash_value: 0.into(), @@ -998,4 +998,18 @@ mod test { [(0, 1224), (0, 0)] ); } + + #[test] + fn test_declare_v2_with_invalid_compiled_clas_hash() { + let (block_context, mut state) = create_account_tx_test_state().unwrap(); + let mut declare_v2 = declarev2_tx(); + declare_v2.compiled_class_hash = Felt252::from(1); + let declare_tx = Transaction::DeclareV2(Box::new(declare_v2)); + + let err = declare_tx + .execute(&mut state, &block_context, 100_000_000) + .unwrap_err(); + + assert_eq!(err.to_string(), "Invalid compiled class, expected class hash: \"1948962768849191111780391610229754715773924969841143100991524171924131413970\", but received: \"1\"".to_string()); + } } diff --git a/src/testing/mod.rs b/src/testing/mod.rs index fe208b42a..1e7141d92 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -41,7 +41,7 @@ lazy_static! { pub static ref TEST_CLASS_HASH: Felt252 = felt_str!("272"); pub static ref TEST_EMPTY_CONTRACT_CLASS_HASH: Felt252 = felt_str!("274"); pub static ref TEST_ERC20_CONTRACT_CLASS_HASH: Felt252 = felt_str!("4112"); - pub static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH: Felt252 = felt_str!("27727"); + pub static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH: Felt252 = felt_str!("1948962768849191111780391610229754715773924969841143100991524171924131413970"); // Storage keys. pub static ref TEST_ERC20_ACCOUNT_BALANCE_KEY: Felt252 = diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index d195089e1..4f0ddeace 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -1,5 +1,5 @@ use super::{verify_version, Transaction}; -use crate::core::contract_address::compute_sierra_class_hash; +use crate::core::contract_address::{compute_casm_class_hash, compute_sierra_class_hash}; use crate::services::api::contract_classes::deprecated_contract_class::EntryPointType; use crate::{ @@ -320,6 +320,13 @@ impl DeclareV2 { }) .map_err(|e| TransactionError::SierraCompileError(e.to_string()))?; + let casm_class_hash = compute_casm_class_hash(casm_class)?; + if casm_class_hash != self.compiled_class_hash { + return Err(TransactionError::InvalidCompiledClassHash( + casm_class_hash.to_string(), + self.compiled_class_hash.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())?; @@ -400,7 +407,7 @@ mod tests { utils::Address, }; use cairo_lang_starknet::casm_contract_class::CasmContractClass; - use cairo_vm::felt::Felt252; + use cairo_vm::felt::{Felt252, felt_str}; use num_traits::{One, Zero}; #[test] @@ -432,7 +439,7 @@ mod tests { let internal_declare = DeclareV2::new_with_tx_hash( &sierra_contract_class, Some(sierra_class_hash), - Felt252::one(), + felt_str!("1948962768849191111780391610229754715773924969841143100991524171924131413970"), sender_address, 0, version, diff --git a/src/transaction/error.rs b/src/transaction/error.rs index 0e5bf97c0..6ed79c64d 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -135,4 +135,6 @@ pub enum TransactionError { CallInfoIsNone, #[error("Unsupported version {0:?}")] UnsupportedVersion(String), + #[error("Invalid compiled class, expected class hash: {0:?}, but received: {1:?}")] + InvalidCompiledClassHash(String, String), } diff --git a/tests/internals.rs b/tests/internals.rs index 75bacb1ab..4cdd99022 100644 --- a/tests/internals.rs +++ b/tests/internals.rs @@ -74,7 +74,7 @@ lazy_static! { static ref TEST_CLASS_HASH: Felt252 = felt_str!("272"); static ref TEST_EMPTY_CONTRACT_CLASS_HASH: Felt252 = felt_str!("274"); static ref TEST_ERC20_CONTRACT_CLASS_HASH: Felt252 = felt_str!("4112"); - static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH: Felt252 = felt_str!("27727"); + static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH: Felt252 = felt_str!("1948962768849191111780391610229754715773924969841143100991524171924131413970"); // Storage keys. // NOTE: this key corresponds to the lower 128 bits of an U256 From 60a3db2fb056c23e4e3d60c15754e083d991d7f2 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Thu, 13 Jul 2023 15:46:08 -0300 Subject: [PATCH 03/12] fix tests --- src/transaction/declare_v2.rs | 9 ++- .../cairo1/fibonacci_dispatcher.cairo | 3 +- .../cairo2/fibonacci_dispatcher.cairo | 3 +- tests/internals.rs | 70 ++++++++++++++++--- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index 4f0ddeace..42265176c 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -399,7 +399,7 @@ mod tests { use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf}; use super::DeclareV2; - use crate::core::contract_address::compute_sierra_class_hash; + use crate::core::contract_address::{compute_casm_class_hash, compute_sierra_class_hash}; use crate::services::api::contract_classes::compiled_class::CompiledClass; use crate::state::state_api::StateReader; use crate::{ @@ -407,7 +407,7 @@ mod tests { utils::Address, }; use cairo_lang_starknet::casm_contract_class::CasmContractClass; - use cairo_vm::felt::{Felt252, felt_str}; + use cairo_vm::felt::Felt252; use num_traits::{One, Zero}; #[test] @@ -433,13 +433,16 @@ mod tests { serde_json::from_reader(reader).unwrap(); let sierra_class_hash = compute_sierra_class_hash(&sierra_contract_class).unwrap(); let sender_address = Address(1.into()); + let casm_class = + CasmContractClass::from_contract_class(sierra_contract_class.clone(), true).unwrap(); + let casm_class_hash = compute_casm_class_hash(&casm_class).unwrap(); // create internal declare v2 let internal_declare = DeclareV2::new_with_tx_hash( &sierra_contract_class, Some(sierra_class_hash), - felt_str!("1948962768849191111780391610229754715773924969841143100991524171924131413970"), + casm_class_hash, sender_address, 0, version, diff --git a/starknet_programs/cairo1/fibonacci_dispatcher.cairo b/starknet_programs/cairo1/fibonacci_dispatcher.cairo index fc70d4e7a..3ffea098c 100644 --- a/starknet_programs/cairo1/fibonacci_dispatcher.cairo +++ b/starknet_programs/cairo1/fibonacci_dispatcher.cairo @@ -42,7 +42,8 @@ mod Dispatcher { class_hash: felt252, selector: felt252, a: felt252, b: felt252, n: felt252 ) -> felt252 { FibonacciLibraryDispatcher { - class_hash: starknet::class_hash_const::<27727>(), + // THIS VALUE IS THE HASH OF THE FIBONACCI CASM CLASS HASH. THE SAME AS THE CONSTANT: TEST_FIB_COMPILED_CONTRACT_CLASS_HASH + class_hash: starknet::class_hash_const::<1948962768849191111780391610229754715773924969841143100991524171924131413970>(), selector }.fib(a, b, n) } diff --git a/starknet_programs/cairo2/fibonacci_dispatcher.cairo b/starknet_programs/cairo2/fibonacci_dispatcher.cairo index 3cbb12b14..5dfe9c6be 100644 --- a/starknet_programs/cairo2/fibonacci_dispatcher.cairo +++ b/starknet_programs/cairo2/fibonacci_dispatcher.cairo @@ -51,7 +51,8 @@ mod Dispatcher { self: @ContractState, class_hash: felt252, selector: felt252, a: felt252, b: felt252, n: felt252 ) -> felt252 { FibonacciLibraryDispatcher { - class_hash: starknet::class_hash_const::<27727>(), + // THIS VALUE IS THE HASH OF THE FIBONACCI CASM CLASS HASH. + class_hash: starknet::class_hash_const::<2889767417435368609058888822622483550637539736178264636938129582300971548553>(), selector }.fib(a, b, n) } diff --git a/tests/internals.rs b/tests/internals.rs index 4cdd99022..6278deae4 100644 --- a/tests/internals.rs +++ b/tests/internals.rs @@ -12,7 +12,9 @@ use cairo_vm::vm::{ use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::{FromPrimitive, Num, One, Zero}; -use starknet_in_rust::core::contract_address::compute_sierra_class_hash; +use starknet_in_rust::core::contract_address::{ + compute_casm_class_hash, compute_sierra_class_hash, +}; use starknet_in_rust::core::errors::state_errors::StateError; use starknet_in_rust::definitions::constants::{ DEFAULT_CAIRO_RESOURCE_FEE_WEIGHTS, VALIDATE_ENTRY_POINT_SELECTOR, @@ -74,7 +76,8 @@ lazy_static! { static ref TEST_CLASS_HASH: Felt252 = felt_str!("272"); static ref TEST_EMPTY_CONTRACT_CLASS_HASH: Felt252 = felt_str!("274"); static ref TEST_ERC20_CONTRACT_CLASS_HASH: Felt252 = felt_str!("4112"); - static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH: Felt252 = felt_str!("1948962768849191111780391610229754715773924969841143100991524171924131413970"); + static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO1: Felt252 = felt_str!("1948962768849191111780391610229754715773924969841143100991524171924131413970"); + static ref TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO2: Felt252 = felt_str!("2889767417435368609058888822622483550637539736178264636938129582300971548553"); // Storage keys. // NOTE: this key corresponds to the lower 128 bits of an U256 @@ -699,6 +702,9 @@ fn declarev2_tx() -> DeclareV2 { let program_data = include_bytes!("../starknet_programs/cairo1/fibonacci.sierra"); let sierra_contract_class: SierraContractClass = serde_json::from_slice(program_data).unwrap(); let sierra_class_hash = compute_sierra_class_hash(&sierra_contract_class).unwrap(); + let casm_class = + CasmContractClass::from_contract_class(sierra_contract_class.clone(), true).unwrap(); + let casm_class_hash = compute_casm_class_hash(&casm_class).unwrap(); DeclareV2 { sender_address: TEST_ACCOUNT_CONTRACT_ADDRESS.clone(), @@ -709,10 +715,10 @@ fn declarev2_tx() -> DeclareV2 { signature: vec![], nonce: 0.into(), hash_value: 0.into(), - compiled_class_hash: TEST_FIB_COMPILED_CONTRACT_CLASS_HASH.clone(), + compiled_class_hash: casm_class_hash, sierra_contract_class, sierra_class_hash, - casm_class: Default::default(), + casm_class: casm_class.into(), skip_execute: false, skip_fee_transfer: false, skip_validate: false, @@ -720,12 +726,21 @@ fn declarev2_tx() -> DeclareV2 { } fn deploy_fib_syscall() -> Deploy { + let contract_hash; + #[cfg(not(feature = "cairo_1_tests"))] + { + contract_hash = felt_to_hash(&TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO2.clone()) + } + #[cfg(feature = "cairo_1_tests")] + { + contract_hash = felt_to_hash(&TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO1.clone()) + } Deploy { hash_value: 0.into(), version: 1.into(), contract_address: TEST_FIB_CONTRACT_ADDRESS.clone(), contract_address_salt: 0.into(), - contract_hash: felt_to_hash(&TEST_FIB_COMPILED_CONTRACT_CLASS_HASH.clone()), + contract_hash, constructor_calldata: Vec::new(), tx_type: TransactionType::Deploy, skip_execute: false, @@ -862,6 +877,15 @@ fn test_declarev2_tx() { ]); let fee = calculate_tx_fee(&resources, *GAS_PRICE, &block_context).unwrap(); + let contract_hash; + #[cfg(not(feature = "cairo_1_tests"))] + { + contract_hash = TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO2.clone(); + } + #[cfg(feature = "cairo_1_tests")] + { + contract_hash = TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO1.clone(); + } let expected_execution_info = TransactionExecutionInfo::new( Some(CallInfo { call_type: Some(CallType::Call), @@ -869,7 +893,7 @@ fn test_declarev2_tx() { class_hash: Some(felt_to_hash(&TEST_ACCOUNT_CONTRACT_CLASS_HASH)), entry_point_selector: Some(VALIDATE_DECLARE_ENTRY_POINT_SELECTOR.clone()), entry_point_type: Some(EntryPointType::External), - calldata: vec![TEST_FIB_COMPILED_CONTRACT_CLASS_HASH.clone()], + calldata: vec![contract_hash], execution_resources: ExecutionResources { n_steps: 12, ..Default::default() @@ -943,6 +967,15 @@ fn expected_execute_call_info() -> CallInfo { } fn expected_fib_execute_call_info() -> CallInfo { + let contract_hash; + #[cfg(not(feature = "cairo_1_tests"))] + { + contract_hash = felt_to_hash(&TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO2.clone()); + } + #[cfg(feature = "cairo_1_tests")] + { + contract_hash = felt_to_hash(&TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO1.clone()); + } CallInfo { caller_address: Address(Felt252::zero()), call_type: Some(CallType::Call), @@ -972,7 +1005,7 @@ fn expected_fib_execute_call_info() -> CallInfo { internal_calls: vec![CallInfo { caller_address: TEST_ACCOUNT_CONTRACT_ADDRESS.clone(), call_type: Some(CallType::Call), - class_hash: Some(felt_to_hash(&TEST_FIB_COMPILED_CONTRACT_CLASS_HASH.clone())), + class_hash: Some(contract_hash), entry_point_selector: Some(Felt252::from_bytes_be(&calculate_sn_keccak(b"fib"))), entry_point_type: Some(EntryPointType::External), calldata: vec![Felt252::from(42), Felt252::from(0), Felt252::from(0)], @@ -1760,9 +1793,18 @@ fn test_library_call_with_declare_v2() { ) }; + let casm_contract_hash; + #[cfg(not(feature = "cairo_1_tests"))] + { + casm_contract_hash = TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO2.clone() + } + #[cfg(feature = "cairo_1_tests")] + { + casm_contract_hash = TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO1.clone() + } // Create an execution entry point let calldata = vec![ - TEST_FIB_COMPILED_CONTRACT_CLASS_HASH.clone(), + casm_contract_hash, Felt252::from_bytes_be(&calculate_sn_keccak(b"fib")), 1.into(), 1.into(), @@ -1798,11 +1840,21 @@ fn test_library_call_with_declare_v2() { ) .unwrap(); + let casm_contract_hash; + #[cfg(not(feature = "cairo_1_tests"))] + { + casm_contract_hash = TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO2.clone() + } + #[cfg(feature = "cairo_1_tests")] + { + casm_contract_hash = TEST_FIB_COMPILED_CONTRACT_CLASS_HASH_CAIRO1.clone() + } + let expected_internal_call_info = CallInfo { caller_address: Address(0.into()), call_type: Some(CallType::Delegate), contract_address: address.clone(), - class_hash: Some(TEST_FIB_COMPILED_CONTRACT_CLASS_HASH.clone().to_be_bytes()), + class_hash: Some(casm_contract_hash.to_be_bytes()), entry_point_selector: Some(external_entrypoint_selector.into()), entry_point_type: Some(EntryPointType::External), #[cfg(not(feature = "cairo_1_tests"))] From 87c75cc7481932241217430bb3b5508639b0ce86 Mon Sep 17 00:00:00 2001 From: Santiago Pittella <87827390+SantiagoPittella@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:08:59 -0300 Subject: [PATCH 04/12] Update src/lib.rs Co-authored-by: Mario Rugiero --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c09a56ddf..e622742fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1000,7 +1000,7 @@ mod test { } #[test] - fn test_declare_v2_with_invalid_compiled_clas_hash() { + fn test_declare_v2_with_invalid_compiled_class_hash() { let (block_context, mut state) = create_account_tx_test_state().unwrap(); let mut declare_v2 = declarev2_tx(); declare_v2.compiled_class_hash = Felt252::from(1); From 939fffd6658ce5061452f1891fce6ea48a68557b Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Fri, 14 Jul 2023 17:42:26 -0300 Subject: [PATCH 05/12] Add casm contract class as optional --- src/transaction/declare_v2.rs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index bad87803d..61da04804 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, @@ -65,6 +65,7 @@ impl DeclareV2 { #[allow(clippy::too_many_arguments)] pub fn new( sierra_contract_class: &SierraContractClass, + casm_contract_class: Option, compiled_class_hash: Felt252, chain_id: Felt252, sender_address: Address, @@ -88,6 +89,7 @@ impl DeclareV2 { Self::new_with_sierra_class_hash_and_tx_hash( sierra_contract_class, sierra_class_hash, + casm_contract_class, compiled_class_hash, sender_address, max_fee, @@ -115,6 +117,7 @@ impl DeclareV2 { pub fn new_with_sierra_class_hash_and_tx_hash( sierra_contract_class: &SierraContractClass, sierra_class_hash: Felt252, + casm_contract_class: Option, compiled_class_hash: Felt252, sender_address: Address, max_fee: u128, @@ -137,7 +140,7 @@ impl DeclareV2 { nonce, compiled_class_hash, hash_value, - casm_class: Default::default(), + casm_class: casm_contract_class, skip_execute: false, skip_validate: false, skip_fee_transfer: false, @@ -166,6 +169,7 @@ impl DeclareV2 { #[allow(clippy::too_many_arguments)] pub fn new_with_tx_hash( sierra_contract_class: &SierraContractClass, + casm_contract_class: Option, compiled_class_hash: Felt252, sender_address: Address, max_fee: u128, @@ -179,6 +183,7 @@ impl DeclareV2 { Self::new_with_sierra_class_hash_and_tx_hash( sierra_contract_class, sierra_class_hash, + casm_contract_class, compiled_class_hash, sender_address, max_fee, @@ -204,6 +209,7 @@ impl DeclareV2 { pub fn new_with_sierra_class_hash( sierra_contract_class: &SierraContractClass, sierra_class_hash: Felt252, + casm_contract_class: Option, compiled_class_hash: Felt252, chain_id: Felt252, sender_address: Address, @@ -225,6 +231,7 @@ impl DeclareV2 { Self::new_with_sierra_class_hash_and_tx_hash( sierra_contract_class, sierra_class_hash, + casm_contract_class, compiled_class_hash, sender_address, max_fee, @@ -373,14 +380,15 @@ 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 { + 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()))? + } + Some(casm_contract_class) => casm_contract_class.clone(), + }; - let casm_class_hash = compute_casm_class_hash(casm_class)?; + let casm_class_hash = compute_casm_class_hash(&casm_class)?; if casm_class_hash != self.compiled_class_hash { return Err(TransactionError::InvalidCompiledClassHash( casm_class_hash.to_string(), @@ -388,7 +396,7 @@ impl DeclareV2 { )); } 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(()) } @@ -500,6 +508,7 @@ mod tests { let internal_declare = DeclareV2::new_with_tx_hash( &sierra_contract_class, + None, casm_class_hash, sender_address, 0, From 67898adfcb506988f8af53eb4c65b3b2f7718cf6 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Fri, 14 Jul 2023 17:47:24 -0300 Subject: [PATCH 06/12] Add test with casm contract class --- src/transaction/declare_v2.rs | 71 ++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index 61da04804..9672e4cee 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -479,7 +479,7 @@ mod tests { use num_traits::{One, Zero}; #[test] - fn create_declare_v2_test() { + fn create_declare_v2_without_casm_contract_class_test() { // read file to create sierra contract class let version; let path; @@ -546,4 +546,73 @@ mod tests { assert_eq!(expected_casm_class, casm_class); } + + #[test] + fn create_declare_v2_with_casm_contract_class_test() { + // read file to create sierra contract class + let version; + let path; + #[cfg(not(feature = "cairo_1_tests"))] + { + version = Felt252::from(2); + path = PathBuf::from("starknet_programs/cairo2/fibonacci.sierra"); + } + + #[cfg(feature = "cairo_1_tests")] + { + version = Felt252::from(1); + path = PathBuf::from("starknet_programs/cairo1/fibonacci.sierra"); + } + + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + let sierra_contract_class: cairo_lang_starknet::contract_class::ContractClass = + serde_json::from_reader(reader).unwrap(); + let sender_address = Address(1.into()); + let casm_class = + CasmContractClass::from_contract_class(sierra_contract_class.clone(), true).unwrap(); + let casm_class_hash = compute_casm_class_hash(&casm_class).unwrap(); + + // create internal declare v2 + + let internal_declare = DeclareV2::new_with_tx_hash( + &sierra_contract_class, + Some(casm_class), + casm_class_hash, + sender_address, + 0, + version, + [1.into()].to_vec(), + Felt252::zero(), + Felt252::one(), + ) + .unwrap(); + + // crate state to store casm contract class + let casm_contract_class_cache = HashMap::new(); + let state_reader = InMemoryStateReader::default(); + let mut state = CachedState::new(state_reader, None, Some(casm_contract_class_cache)); + + // call compile and store + assert!(internal_declare + .compile_and_store_casm_class(&mut state) + .is_ok()); + + // test we can retreive the data + let expected_casm_class = CasmContractClass::from_contract_class( + internal_declare.sierra_contract_class.clone(), + true, + ) + .unwrap(); + + let casm_class = match state + .get_contract_class(&internal_declare.compiled_class_hash.to_be_bytes()) + .unwrap() + { + CompiledClass::Casm(casm) => *casm, + _ => unreachable!(), + }; + + assert_eq!(expected_casm_class, casm_class); + } } From 7ff2e8a156169bcc24425010cbd2dafaf089bd8c Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Fri, 14 Jul 2023 17:49:41 -0300 Subject: [PATCH 07/12] update doc --- src/transaction/declare_v2.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index 9672e4cee..ab1e97f79 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -55,6 +55,7 @@ impl DeclareV2 { /// It will calculate the sierra class hash and the transaction hash. /// ## Parameters: /// - sierra_contract_class: The sierra contract class of the contract to declare + /// - casm_contract_class: The casm contract class of the contract to declare. This is optional. /// - compiled_class_hash: the class hash of the contract compiled with Cairo1 or newer. /// - chain_id: Id of the network where is going to be declare, those can be: Mainnet, Testnet. /// - sender_address: The address of the account declaring the contract. @@ -104,6 +105,7 @@ impl DeclareV2 { /// ## Parameters: /// - sierra_contract_class: The sierra contract class of the contract to declare /// - sierra_class_hash: The precomputed hash for the sierra contract + /// - casm_contract_class: The casm contract class of the contract to declare. This is optional. /// - compiled_class_hash: the class hash of the contract compiled with Cairo1 or newer. /// - sender_address: The address of the account declaring the contract. /// - max_fee: refers to max amount of fee that a declare takes. @@ -158,7 +160,8 @@ impl DeclareV2 { // creates a new instance of a declare but without the computation of the transaction hash. /// ## Parameters: - /// - sierra_contract_class: The sierra contract class of the contract to declare + /// - sierra_contract_class: The sierra contract class of the contract to declare. + /// - casm_contract_class: The casm contract class of the contract to declare. This is optional. /// - compiled_class_hash: the class hash of the contract compiled with Cairo1 or newer. /// - sender_address: The address of the account declaring the contract. /// - max_fee: refers to max amount of fee that a declare takes. @@ -198,6 +201,7 @@ impl DeclareV2 { /// ## Parameters: /// - sierra_contract_class: The sierra contract class of the contract to declare /// - sierra_class_hash: The precomputed hash for the sierra contract + /// - casm_contract_class: The casm contract class of the contract to declare. This is optional. /// - compiled_class_hash: the class hash of the contract compiled with Cairo1 or newer. /// - chain_id: Id of the network where is going to be declare, those can be: Mainnet, Testnet. /// - sender_address: The address of the account declaring the contract. From 499b8c3e4038bbbd67471fa4ff86a3119dac1ef4 Mon Sep 17 00:00:00 2001 From: Santiago Pittella <87827390+SantiagoPittella@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:04:39 -0300 Subject: [PATCH 08/12] Update src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Estéfano Bargas --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e622742fd..22003c4eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1007,7 +1007,7 @@ mod test { let declare_tx = Transaction::DeclareV2(Box::new(declare_v2)); let err = declare_tx - .execute(&mut state, &block_context, 100_000_000) + .execute(&mut state, &block_context, INITIAL_GAS_COST) .unwrap_err(); assert_eq!(err.to_string(), "Invalid compiled class, expected class hash: \"1948962768849191111780391610229754715773924969841143100991524171924131413970\", but received: \"1\"".to_string()); From 5e0174222e4b8b5d3be384501b3ab6db03091a9a Mon Sep 17 00:00:00 2001 From: Santiago Pittella <87827390+SantiagoPittella@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:04:46 -0300 Subject: [PATCH 09/12] Update src/lib.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Estéfano Bargas --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 22003c4eb..17598681b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -836,7 +836,7 @@ mod test { tx_type: TransactionType::Declare, validate_entry_point_selector: VALIDATE_DECLARE_ENTRY_POINT_SELECTOR.clone(), version: 1.into(), - max_fee: 100_000_000, + max_fee: INITIAL_GAS_COST, signature: vec![], nonce: 0.into(), hash_value: 0.into(), From a0cbb66cfa737abd2392e65d10e53c80b741fa95 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 17 Jul 2023 11:07:26 -0300 Subject: [PATCH 10/12] Add missed import --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 17598681b..cb9bdd7db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,6 +198,7 @@ mod test { use std::path::PathBuf; use crate::core::contract_address::{compute_deprecated_class_hash, compute_sierra_class_hash}; + use crate::definitions::constants::INITIAL_GAS_COST; use crate::definitions::{ block_context::StarknetChainId, constants::{ From 52273905a71bf581552cec92761d784d69a558b0 Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 17 Jul 2023 11:11:33 -0300 Subject: [PATCH 11/12] fix test --- src/transaction/declare_v2.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index 63514fcd5..2cb931b3c 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -654,6 +654,7 @@ mod tests { let internal_declare = DeclareV2::new_with_sierra_class_hash_and_tx_hash( &sierra_contract_class, sierra_class_hash, + Some(casm_class), casm_class_hash, sender_address, 0, From 5e4a64f57cc91b264751d6da9fe4d66e07efd7ad Mon Sep 17 00:00:00 2001 From: SantiagoPittella Date: Mon, 17 Jul 2023 11:46:00 -0300 Subject: [PATCH 12/12] add tests --- src/lib.rs | 12 +++- src/transaction/declare_v2.rs | 129 ++++++++++++++++++++++++++++++++++ src/transaction/error.rs | 2 +- 3 files changed, 140 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cb9bdd7db..bee36a39e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1004,13 +1004,21 @@ mod test { fn test_declare_v2_with_invalid_compiled_class_hash() { let (block_context, mut state) = create_account_tx_test_state().unwrap(); let mut declare_v2 = declarev2_tx(); - declare_v2.compiled_class_hash = Felt252::from(1); + let real_casm_class_hash = declare_v2.compiled_class_hash; + let wrong_casm_class_hash = Felt252::from(1); + declare_v2.compiled_class_hash = wrong_casm_class_hash.clone(); let declare_tx = Transaction::DeclareV2(Box::new(declare_v2)); let err = declare_tx .execute(&mut state, &block_context, INITIAL_GAS_COST) .unwrap_err(); - assert_eq!(err.to_string(), "Invalid compiled class, expected class hash: \"1948962768849191111780391610229754715773924969841143100991524171924131413970\", but received: \"1\"".to_string()); + assert_eq!( + err.to_string(), + format!( + "Invalid compiled class, expected class hash: {}, but received: {}", + real_casm_class_hash, wrong_casm_class_hash + ) + ); } } diff --git a/src/transaction/declare_v2.rs b/src/transaction/declare_v2.rs index 2cb931b3c..c034fb47f 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -692,4 +692,133 @@ mod tests { assert_eq!(expected_casm_class, casm_class); } + + #[test] + fn create_declare_v2_with_casm_contract_class_none_test() { + // read file to create sierra contract class + let version; + let path; + #[cfg(not(feature = "cairo_1_tests"))] + { + version = Felt252::from(2); + path = PathBuf::from("starknet_programs/cairo2/fibonacci.sierra"); + } + + #[cfg(feature = "cairo_1_tests")] + { + version = Felt252::from(1); + path = PathBuf::from("starknet_programs/cairo1/fibonacci.sierra"); + } + + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + let sierra_contract_class: cairo_lang_starknet::contract_class::ContractClass = + serde_json::from_reader(reader).unwrap(); + let sender_address = Address(1.into()); + let casm_class = + CasmContractClass::from_contract_class(sierra_contract_class.clone(), true).unwrap(); + let casm_class_hash = compute_casm_class_hash(&casm_class).unwrap(); + + // create internal declare v2 + + let internal_declare = DeclareV2::new_with_tx_hash( + &sierra_contract_class, + None, + casm_class_hash, + sender_address, + 0, + version, + [1.into()].to_vec(), + Felt252::zero(), + Felt252::one(), + ) + .unwrap(); + + // crate state to store casm contract class + let casm_contract_class_cache = HashMap::new(); + let state_reader = InMemoryStateReader::default(); + let mut state = CachedState::new(state_reader, None, Some(casm_contract_class_cache)); + + // call compile and store + assert!(internal_declare + .compile_and_store_casm_class(&mut state) + .is_ok()); + + // test we can retreive the data + let expected_casm_class = CasmContractClass::from_contract_class( + internal_declare.sierra_contract_class.clone(), + true, + ) + .unwrap(); + + let casm_class = match state + .get_contract_class(&internal_declare.compiled_class_hash.to_be_bytes()) + .unwrap() + { + CompiledClass::Casm(casm) => *casm, + _ => unreachable!(), + }; + + assert_eq!(expected_casm_class, casm_class); + } + + #[test] + fn create_declare_v2_wrong_casm_class_hash_test() { + // read file to create sierra contract class + let version; + let path; + #[cfg(not(feature = "cairo_1_tests"))] + { + version = Felt252::from(2); + path = PathBuf::from("starknet_programs/cairo2/fibonacci.sierra"); + } + + #[cfg(feature = "cairo_1_tests")] + { + version = Felt252::from(1); + path = PathBuf::from("starknet_programs/cairo1/fibonacci.sierra"); + } + + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + let sierra_contract_class: cairo_lang_starknet::contract_class::ContractClass = + serde_json::from_reader(reader).unwrap(); + let sender_address = Address(1.into()); + let casm_class = + CasmContractClass::from_contract_class(sierra_contract_class.clone(), true).unwrap(); + let casm_class_hash = compute_casm_class_hash(&casm_class).unwrap(); + + let sended_class_hash = Felt252::from(5); + // create internal declare v2 + + let internal_declare = DeclareV2::new_with_tx_hash( + &sierra_contract_class, + None, + sended_class_hash.clone(), + sender_address, + 0, + version, + [1.into()].to_vec(), + Felt252::zero(), + Felt252::one(), + ) + .unwrap(); + + // crate state to store casm contract class + let casm_contract_class_cache = HashMap::new(); + let state_reader = InMemoryStateReader::default(); + let mut state = CachedState::new(state_reader, None, Some(casm_contract_class_cache)); + + let expected_err = format!( + "Invalid compiled class, expected class hash: {}, but received: {}", + casm_class_hash, sended_class_hash + ); + assert_eq!( + internal_declare + .compile_and_store_casm_class(&mut state) + .unwrap_err() + .to_string(), + expected_err + ); + } } diff --git a/src/transaction/error.rs b/src/transaction/error.rs index 6ed79c64d..e7acd92cc 100644 --- a/src/transaction/error.rs +++ b/src/transaction/error.rs @@ -135,6 +135,6 @@ pub enum TransactionError { CallInfoIsNone, #[error("Unsupported version {0:?}")] UnsupportedVersion(String), - #[error("Invalid compiled class, expected class hash: {0:?}, but received: {1:?}")] + #[error("Invalid compiled class, expected class hash: {0}, but received: {1}")] InvalidCompiledClassHash(String, String), }