diff --git a/protostar-rust/Cargo.lock b/protostar-rust/Cargo.lock index 48f68b7ca..cdc1e3f0c 100644 --- a/protostar-rust/Cargo.lock +++ b/protostar-rust/Cargo.lock @@ -3507,6 +3507,7 @@ dependencies = [ "parity-scale-codec 3.6.1", "parity-scale-codec-derive 3.6.1", "project-root", + "regex", "scarb-metadata", "schemars", "serde", diff --git a/protostar-rust/Cargo.toml b/protostar-rust/Cargo.toml index 8ee91748c..8cc61782e 100644 --- a/protostar-rust/Cargo.toml +++ b/protostar-rust/Cargo.toml @@ -15,7 +15,7 @@ cairo-lang-sierra = { path = "../cairo/crates/cairo-lang-sierra", version = "2.0 cairo-lang-utils = { path = "../cairo/crates/cairo-lang-utils", version = "2.0.0-rc2" } cairo-lang-casm = { path = "../cairo/crates/cairo-lang-casm", version = "2.0.0-rc2" } cairo-lang-starknet = { path = "../cairo/crates/cairo-lang-starknet", version = "2.0.0-rc2" } -blockifier = { git = "https://github.com/software-mansion-labs/blockifier.git", branch="kb/use-in-protostar" } +blockifier = { git = "https://github.com/software-mansion-labs/blockifier.git", branch = "kb/use-in-protostar" } starknet_api = { git = "https://github.com/starkware-libs/starknet-api", rev = "24a7249" } schemars = { version = "0.8.12", features = ["preserve_order"] } parity-scale-codec = "3.5.0" @@ -42,6 +42,7 @@ ark-std = "0.3.0" ark-secp256k1 = "0.4.0" ark-secp256r1 = "0.4.0" num-traits = "0.2" +regex = "1.8.4" [lib] name = "rust_test_runner" diff --git a/protostar-rust/src/cheatcodes_hint_processor.rs b/protostar-rust/src/cheatcodes_hint_processor.rs index 6e382985c..d5fd8e243 100644 --- a/protostar-rust/src/cheatcodes_hint_processor.rs +++ b/protostar-rust/src/cheatcodes_hint_processor.rs @@ -13,11 +13,13 @@ use blockifier::execution::contract_class::{ ContractClass as BlockifierContractClass, ContractClassV1, }; use blockifier::execution::entry_point::{CallEntryPoint, CallType}; +use blockifier::execution::errors::EntryPointExecutionError; use blockifier::state::cached_state::CachedState; use blockifier::state::state_api::StateReader; use blockifier::test_utils::{invoke_tx, DictStateReader}; use blockifier::test_utils::{MAX_FEE, TEST_ACCOUNT_CONTRACT_ADDRESS}; use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::errors::TransactionExecutionError; use blockifier::transaction::transaction_utils_for_protostar::declare_tx_default; use blockifier::transaction::transactions::{DeclareTransaction, ExecutableTransaction}; use cairo_felt::Felt252; @@ -40,6 +42,7 @@ use cairo_vm::vm::errors::hint_errors::HintError; use cairo_vm::vm::errors::vm_errors::VirtualMachineError; use cairo_vm::vm::vm_core::VirtualMachine; use num_traits::{Num, ToPrimitive}; +use regex::Regex; use serde::Deserialize; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, PatriciaKey}; use starknet_api::deprecated_contract_class::EntryPointType; @@ -365,23 +368,51 @@ fn execute_cheatcode_hint( nonce, ..tx })); - let tx_result = account_tx.execute(blockifier_state, block_context).unwrap(); - let return_data = tx_result - .execute_call_info - .expect("Failed to get execution data from method") - .execution - .retdata; - let contract_address = return_data - .0 - .get(0) - .expect("Failed to get contract_address from return_data"); - let contract_address = Felt252::from_bytes_be(contract_address.bytes()); - - insert_value_to_cellref!(vm, deployed_contract_address, contract_address)?; - // todo in case of error, consider filling the panic data instead of packing in rust - insert_value_to_cellref!(vm, panic_data_start, Felt252::from(0))?; - insert_value_to_cellref!(vm, panic_data_end, Felt252::from(0))?; - + match account_tx.execute(blockifier_state, block_context) { + Result::Ok(tx_info) => { + let return_data = tx_info + .execute_call_info + .expect("Failed to get execution data from method") + .execution + .retdata; + let contract_address = return_data + .0 + .get(0) + .expect("Failed to get contract_address from return_data"); + let contract_address = Felt252::from_bytes_be(contract_address.bytes()); + + insert_value_to_cellref!(vm, deployed_contract_address, contract_address)?; + insert_value_to_cellref!(vm, panic_data_start, Felt252::from(0))?; + insert_value_to_cellref!(vm, panic_data_end, Felt252::from(0))?; + } + Result::Err(e) => { + if let TransactionExecutionError::ExecutionError( + EntryPointExecutionError::VirtualMachineExecutionErrorWithTrace { + source, + trace, + }, + ) = e + { + let msg = source.to_string(); + let unparseable_msg = format!("Deploy failed, full error message: {msg}"); + let unparseable_msg = unparseable_msg.as_str(); + + let extracted_panic_data = + try_extract_panic_data(&msg).expect(unparseable_msg); + let mut ptr = vm.add_memory_segment(); + insert_value_to_cellref!(vm, panic_data_start, ptr)?; + + for datum in extracted_panic_data { + insert_at_pointer(vm, &mut ptr, datum); + } + + insert_value_to_cellref!(vm, deployed_contract_address, contract_address)?; + insert_value_to_cellref!(vm, panic_data_end, ptr)?; + } else { + panic!("Unparseable error message: {e:?}") + } + } + } Ok(()) } &ProtostarHint::Prepare { .. } => todo!(), @@ -484,6 +515,28 @@ fn felt252_from_hex_string(value: &str) -> Result { .map_err(|_| anyhow!("Failed to convert value = {value} to Felt252")) } +fn felt_from_short_string(short_str: &str) -> Felt252 { + return Felt252::from_bytes_be(short_str.as_bytes()); +} + +fn try_extract_panic_data(err: &str) -> Option> { + let re = Regex::new(r#"(?m)^Got an exception while executing a hint: Custom Hint Error: Execution failed. Failure reason: "(.*)"\.$"#) + .expect("Could not create panic_data matching regex"); + + if let Some(captures) = re.captures(err) { + if let Some(panic_data_match) = captures.get(1) { + let panic_data_felts: Vec = panic_data_match + .as_str() + .split(", ") + .map(felt_from_short_string) + .collect(); + + return Some(panic_data_felts); + } + } + None +} + #[cfg(test)] mod test { use super::*; diff --git a/protostar-rust/tests/data/deploy_error_handling_test/Scarb.toml b/protostar-rust/tests/data/deploy_error_handling_test/Scarb.toml new file mode 100644 index 000000000..e9b336a4a --- /dev/null +++ b/protostar-rust/tests/data/deploy_error_handling_test/Scarb.toml @@ -0,0 +1,11 @@ +[package] +name = "deploy_error_handling_test" +version = "0.1.0" + +[[target.starknet-contract]] +sierra = true + +[dependencies] +starknet = "2.0.0-rc2" + +[tool.protostar] diff --git a/protostar-rust/tests/data/deploy_error_handling_test/src/lib.cairo b/protostar-rust/tests/data/deploy_error_handling_test/src/lib.cairo new file mode 100644 index 000000000..bab3b894a --- /dev/null +++ b/protostar-rust/tests/data/deploy_error_handling_test/src/lib.cairo @@ -0,0 +1,15 @@ +#[starknet::contract] +mod PanickingConstructor { + use array::ArrayTrait; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) { + let mut panic_data = ArrayTrait::new(); + panic_data.append('PANIK'); + panic_data.append('DEJTA'); + panic(panic_data); + } +} diff --git a/protostar-rust/tests/data/deploy_error_handling_test/tests/test_deploy_error_handling.cairo b/protostar-rust/tests/data/deploy_error_handling_test/tests/test_deploy_error_handling.cairo new file mode 100644 index 000000000..d16748f5f --- /dev/null +++ b/protostar-rust/tests/data/deploy_error_handling_test/tests/test_deploy_error_handling.cairo @@ -0,0 +1,23 @@ +use result::ResultTrait; +use protostar_print::PrintTrait; +use cheatcodes::RevertedTransactionTrait; +use cheatcodes::PreparedContract; +use array::ArrayTrait; + +#[test] +fn test_deploy_error_handling() { + let class_hash = declare('PanickingConstructor').expect('Could not declare'); + let prepared_contract = PreparedContract { + contract_address: 'addr', + class_hash: class_hash, + constructor_calldata: @ArrayTrait::new() + }; + + match deploy(prepared_contract) { + Result::Ok(_) => panic_with_felt252('Should have panicked'), + Result::Err(x) => { + assert(*x.panic_data.at(0_usize) == 'PANIK', *x.panic_data.at(0_usize)); + assert(*x.panic_data.at(1_usize) == 'DEJTA', *x.panic_data.at(1_usize)); + } + } +} diff --git a/protostar-rust/tests/e2e/running.rs b/protostar-rust/tests/e2e/running.rs index 33e64a61a..8da01a093 100644 --- a/protostar-rust/tests/e2e/running.rs +++ b/protostar-rust/tests/e2e/running.rs @@ -280,6 +280,25 @@ fn exit_first_flag() { "#}); } +#[test] +fn test_deploy_error_handling() { + let temp = assert_fs::TempDir::new().unwrap(); + temp.copy_from("tests/data/deploy_error_handling_test", &["**/*"]) + .unwrap(); + + runner() + .current_dir(&temp) + .assert() + .success() + .stdout_matches(indoc! { r#" + Collected 1 test(s) and 2 test file(s) + Running 0 test(s) from src/lib.cairo + Running 1 test(s) from tests/test_deploy_error_handling.cairo + [PASS] test_deploy_error_handling::test_deploy_error_handling::test_deploy_error_handling + Tests: 1 passed, 0 failed, 0 skipped + "#}); +} + #[test] fn dispatchers() { let temp = assert_fs::TempDir::new().unwrap();