Skip to content

Commit

Permalink
Add handling deploy constructor errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Arcticae committed Jun 30, 2023
1 parent 5502362 commit 4da575a
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 8 deletions.
1 change: 1 addition & 0 deletions protostar-rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion protostar-rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
153 changes: 146 additions & 7 deletions protostar-rust/src/cheatcodes_hint_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ use cairo_lang_runner::{
use cairo_vm::hint_processor::hint_processor_definition::HintReference;
use cairo_vm::serde::deserialize_program::ApTracking;
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, PatriciaKey};
use starknet_api::hash::{StarkFelt, StarkHash};
Expand Down Expand Up @@ -255,15 +258,61 @@ fn execute_cheatcode_hint(
deploy_account_tx.max_fee = Fee(0);
let account_tx = AccountTransaction::DeployAccount(deploy_account_tx);
let block_context = &BlockContext::create_for_account_testing();
let actual_execution_info = account_tx
.execute(blockifier_state, block_context)
.expect("error executing transaction deploy");
let entry_point_selector = selector_from_name("deploy_contract");
let salt = ContractAddressSalt::default();
let class_hash = ClassHash(StarkFelt::new(class_hash.to_be_bytes()).unwrap());

let execute_calldata = create_execute_calldata(
&calldata,
&class_hash,
&account_address,
&entry_point_selector,
&salt,
);

let nonce = blockifier_state
.get_nonce_at(account_address)
.expect("Failed to get nonce");
let tx = invoke_tx(execute_calldata, account_address, Fee(MAX_FEE), None);
let account_tx =
AccountTransaction::Invoke(InvokeTransaction::V1(InvokeTransactionV1 {
nonce,
..tx
}));
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) => {
let msg = e.source().expect("No source error found").to_string();
let unparseable_msg = format!("Deploy failed, full error message: {msg}");
let unparseable_msg = unparseable_msg.as_str();

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))?;
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)?;
}
}
Ok(())
}
&ProtostarHint::Prepare { .. } => todo!(),
Expand Down Expand Up @@ -291,3 +340,93 @@ fn execute_cheatcode_hint(
}
}
}

fn create_execute_calldata(
calldata: &[Felt252],
class_hash: &ClassHash,
account_address: &ContractAddress,
entry_point_selector: &EntryPointSelector,
salt: &ContractAddressSalt,
) -> Calldata {
let calldata_len = u128::try_from(calldata.len()).unwrap();
let mut execute_calldata = vec![
*account_address.0.key(), // Contract address.
entry_point_selector.0, // EP selector.
stark_felt!(calldata_len + 3), // Calldata length.
class_hash.0, // Calldata: class_hash.
salt.0, // Contract_address_salt.
stark_felt!(calldata_len), // Constructor calldata length.
];
let mut calldata: Vec<StarkFelt> = calldata
.iter()
.map(|data| StarkFelt::new(data.to_be_bytes()).unwrap())
.collect();
execute_calldata.append(&mut calldata);
Calldata(execute_calldata.into())
}

fn read_data_from_range(
vm: &mut VirtualMachine,
mut start: Relocatable,
end: Relocatable,
) -> Result<Vec<Felt252>> {
let mut calldata: Vec<Felt252> = vec![];
while start != end {
let value = felt_from_pointer(vm, &mut start)?;
calldata.push(value);
}
Ok(calldata)
}

fn insert_at_pointer<T: Into<MaybeRelocatable>>(
vm: &mut VirtualMachine,
ptr: &mut Relocatable,
value: T,
) -> Result<()> {
vm.insert_value(*ptr, value)?;
*ptr += 1;
Ok(())
}

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<Vec<Felt252>> {
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<Felt252> = panic_data_match
.as_str()
.split(", ")
.map(felt_from_short_string)
.collect();

return Some(panic_data_felts);
}
}
None
}

fn usize_from_pointer(vm: &mut VirtualMachine, ptr: &mut Relocatable) -> Result<usize> {
let gas_counter = vm
.get_integer(*ptr)?
.to_usize()
.ok_or_else(|| anyhow!("Failed to convert to usize"))?;
*ptr += 1;
Ok(gas_counter)
}

fn relocatable_from_pointer(vm: &mut VirtualMachine, ptr: &mut Relocatable) -> Result<Relocatable> {
let start = vm.get_relocatable(*ptr)?;
*ptr += 1;
Ok(start)
}

fn felt_from_pointer(vm: &mut VirtualMachine, ptr: &mut Relocatable) -> Result<Felt252> {
let entry_point_selector = vm.get_integer(*ptr)?.into_owned();
*ptr += 1;
Ok(entry_point_selector)
}
11 changes: 11 additions & 0 deletions protostar-rust/tests/data/deploy_error_handling_test/Scarb.toml
Original file line number Diff line number Diff line change
@@ -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]
15 changes: 15 additions & 0 deletions protostar-rust/tests/data/deploy_error_handling_test/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
41 changes: 41 additions & 0 deletions protostar-rust/tests/e2e/running.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,44 @@ fn exit_first_flag() {
Tests: 7 passed, 1 failed, 1 skipped
"#});
}

#[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
"#});
}

#[test]
fn dispatchers() {
let temp = assert_fs::TempDir::new().unwrap();
temp.copy_from("tests/data/dispatchers", &["**/*"]).unwrap();

let snapbox = runner();

snapbox
.current_dir(&temp)
.assert()
.success()
.stdout_matches(indoc! {r#"Collected 2 test(s) and 4 test file(s)
Running 0 test(s) from src/erc20.cairo
Running 0 test(s) from src/hello_starknet.cairo
Running 0 test(s) from src/lib.cairo
Running 2 test(s) from tests/using_dispatchers.cairo
[PASS] using_dispatchers::using_dispatchers::call_and_invoke
[PASS] using_dispatchers::using_dispatchers::advanced_types
Tests: 2 passed, 0 failed, 0 skipped
"#});
}

0 comments on commit 4da575a

Please sign in to comment.