From a66205ba1b6f9c2caac5b46009b627cea327b7ed Mon Sep 17 00:00:00 2001 From: Santiago Pittella <87827390+SantiagoPittella@users.noreply.github.com> Date: Mon, 17 Jul 2023 12:15:43 -0300 Subject: [PATCH] Add new constructor declare v2 (#823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add casm class hash * add casm class hash check * fix tests * Update src/lib.rs Co-authored-by: Mario Rugiero * Add casm contract class as optional * Add test with casm contract class * update doc * Update src/lib.rs Co-authored-by: Estéfano Bargas * Update src/lib.rs Co-authored-by: Estéfano Bargas * Add missed import * fix test * add tests --------- Co-authored-by: Mario Rugiero Co-authored-by: Estéfano Bargas --- src/lib.rs | 12 +- src/transaction/declare_v2.rs | 234 ++++++++++++++++++++++++++++++++-- src/transaction/error.rs | 2 +- 3 files changed, 234 insertions(+), 14 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 b1d6c490f..c034fb47f 100644 --- a/src/transaction/declare_v2.rs +++ b/src/transaction/declare_v2.rs @@ -45,7 +45,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, @@ -56,6 +56,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. @@ -66,6 +67,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, @@ -89,6 +91,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, @@ -103,6 +106,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. @@ -116,6 +120,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, @@ -138,7 +143,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, @@ -156,7 +161,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. @@ -167,6 +173,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, @@ -180,6 +187,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, @@ -194,6 +202,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. @@ -205,6 +214,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, @@ -226,6 +236,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, @@ -374,14 +385,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(), @@ -389,7 +401,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(()) } @@ -473,7 +485,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; @@ -502,6 +514,76 @@ mod tests { 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_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, @@ -572,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, @@ -609,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), }