Skip to content

Commit 7f2ff86

Browse files
Arcticaepiotmag769drknzz
authored
ByteArray based errors handling (#1679)
<!-- Reference any GitHub issues resolved by this PR --> Closes #1627 ## Introduced changes <!-- A brief description of the changes --> - Adds a mechanism for parsing the errors from the trace and passing it on to test runner - Adds an extension for ease of further parsing the array ## Checklist <!-- Make sure all of these are complete --> - [x] Linked relevant issue - [x] Updated relevant documentation - [x] Added relevant tests - [x] Performed self-review of the code - [x] Added changes to `CHANGELOG.md` --------- Co-authored-by: Piotr Magiera <[email protected]> Co-authored-by: Kamil Jankowski <[email protected]>
1 parent 9505d79 commit 7f2ff86

File tree

14 files changed

+350
-5
lines changed

14 files changed

+350
-5
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Forge
11+
12+
#### Added
13+
- `map_string_error` for use with dispatchers, which automatically converts string errors from the syscall result (read more [here](https://foundry-rs.github.io/starknet-foundry/testing/contracts#handling-errors))
14+
1015
## [0.17.0] - 2024-02-07
1116

1217
### Forge

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/panic_data.rs

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use cairo_felt::Felt252;
2+
use conversions::byte_array::ByteArray;
23
use regex::Regex;
34

45
#[must_use]
56
pub fn try_extract_panic_data(err: &str) -> Option<Vec<Felt252>> {
6-
let re = Regex::new(r"Got an exception while executing a hint: Hint Error: Execution failed. Failure reason:\s\w*\s\(\'(.*)\'\)\.")
7-
.expect("Could not create panic_data matching regex");
7+
let re_felt_array = Regex::new(r"Got an exception while executing a hint: Hint Error: Execution failed\. Failure reason: \w+ \('(.*)'\)\.")
8+
.expect("Could not create felt panic_data matching regex");
89

9-
if let Some(captures) = re.captures(err) {
10+
let re_string = Regex::new(r#"(?s)Got an exception while executing a hint: Hint Error: Execution failed\. Failure reason: "(.*)"\."#)
11+
.expect("Could not create string panic_data matching regex");
12+
13+
if let Some(captures) = re_felt_array.captures(err) {
1014
if let Some(panic_data_match) = captures.get(1) {
1115
if panic_data_match.as_str().is_empty() {
1216
return Some(vec![]);
@@ -20,16 +24,29 @@ pub fn try_extract_panic_data(err: &str) -> Option<Vec<Felt252>> {
2024
return Some(panic_data_felts);
2125
}
2226
}
27+
28+
if let Some(captures) = re_string.captures(err) {
29+
if let Some(string_match) = captures.get(1) {
30+
let panic_data_felts: Vec<Felt252> =
31+
ByteArray::from(string_match.as_str().to_string()).serialize();
32+
return Some(panic_data_felts);
33+
}
34+
}
35+
2336
None
2437
}
2538

2639
#[cfg(test)]
2740
mod test {
2841
use super::*;
2942
use cairo_felt::Felt252;
43+
use cairo_lang_utils::byte_array::BYTE_ARRAY_MAGIC;
44+
use conversions::felt252::FromShortString;
45+
use indoc::indoc;
46+
use num_traits::Num;
3047

3148
#[test]
32-
fn string_extracting_panic_data() {
49+
fn extracting_plain_panic_data() {
3350
let cases: [(&str, Option<Vec<Felt252>>); 4] = [
3451
(
3552
"Beginning of trace\nGot an exception while executing a hint: Hint Error: Execution failed. Failure reason: 0x434d3232 ('PANIK, DAYTA').\n
@@ -51,4 +68,66 @@ mod test {
5168
assert_eq!(try_extract_panic_data(str), expected);
5269
}
5370
}
71+
72+
#[test]
73+
fn extracting_string_panic_data() {
74+
let cases: [(&str, Option<Vec<Felt252>>); 4] = [
75+
(
76+
indoc!(
77+
r#"
78+
Beginning of trace
79+
Got an exception while executing a hint: Hint Error: Execution failed. Failure reason: "wow message is exactly 31 chars".
80+
End of trace
81+
"#
82+
),
83+
Some(vec![
84+
Felt252::from_str_radix(BYTE_ARRAY_MAGIC, 16).unwrap(),
85+
Felt252::from(1),
86+
Felt252::from_short_string("wow message is exactly 31 chars").unwrap(),
87+
Felt252::from(0),
88+
Felt252::from(0),
89+
]),
90+
),
91+
(
92+
indoc!(
93+
r#"
94+
Beginning of trace
95+
Got an exception while executing a hint: Hint Error: Execution failed. Failure reason: "".
96+
End of trace
97+
"#
98+
),
99+
Some(vec![
100+
Felt252::from_str_radix(BYTE_ARRAY_MAGIC, 16).unwrap(),
101+
Felt252::from(0),
102+
Felt252::from(0),
103+
Felt252::from(0),
104+
]),
105+
),
106+
(
107+
indoc!(
108+
r#"
109+
Beginning of trace
110+
Got an exception while executing a hint: Hint Error: Execution failed. Failure reason: "A very long and multiline
111+
thing is also being parsed, and can
112+
also can be very long as you can see".
113+
End of trace
114+
"#
115+
),
116+
Some(vec![
117+
Felt252::from_str_radix(BYTE_ARRAY_MAGIC, 16).unwrap(),
118+
Felt252::from(3),
119+
Felt252::from_short_string("A very long and multiline\nthing").unwrap(),
120+
Felt252::from_short_string(" is also being parsed, and can\n").unwrap(),
121+
Felt252::from_short_string("also can be very long as you ca").unwrap(),
122+
Felt252::from_short_string("n see").unwrap(),
123+
Felt252::from(5),
124+
]),
125+
),
126+
("Custom Hint Error: Invalid trace: \"PANIC DATA\"", None),
127+
];
128+
129+
for (str, expected) in cases {
130+
assert_eq!(try_extract_panic_data(str), expected);
131+
}
132+
}
54133
}

crates/cheatnet/tests/builtins/panic_call.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use crate::common::state::build_runtime_state;
33
use crate::common::{deploy_contract, felt_selector_from_name, state::create_cached_state};
44
use crate::{assert_error, assert_panic};
55
use cairo_felt::Felt252;
6+
use cairo_lang_utils::byte_array::BYTE_ARRAY_MAGIC;
67
use cheatnet::state::CheatnetState;
78
use conversions::felt252::FromShortString;
8-
use num_traits::Bounded;
9+
use conversions::IntoConv;
10+
use num_traits::{Bounded, Num};
911

1012
#[test]
1113
fn call_contract_error() {
@@ -56,3 +58,45 @@ fn call_contract_panic() {
5658
]
5759
);
5860
}
61+
62+
#[test]
63+
fn call_proxied_contract_bytearray_panic() {
64+
let mut cached_state = create_cached_state();
65+
let mut cheatnet_state = CheatnetState::default();
66+
let mut runtime_state = build_runtime_state(&mut cheatnet_state);
67+
68+
let proxy = deploy_contract(
69+
&mut cached_state,
70+
&mut runtime_state,
71+
"ByteArrayPanickingContractProxy",
72+
&[],
73+
);
74+
let bytearray_panicking_contract = deploy_contract(
75+
&mut cached_state,
76+
&mut runtime_state,
77+
"ByteArrayPanickingContract",
78+
&[],
79+
);
80+
81+
let selector = felt_selector_from_name("call_bytearray_panicking_contract");
82+
83+
let output = call_contract(
84+
&mut cached_state,
85+
&mut runtime_state,
86+
&proxy,
87+
&selector,
88+
&[bytearray_panicking_contract.into_()],
89+
);
90+
91+
assert_panic!(
92+
output,
93+
vec![
94+
Felt252::from_str_radix(BYTE_ARRAY_MAGIC, 16).unwrap(),
95+
Felt252::from(2),
96+
Felt252::from_short_string("This is a very long\n and multil").unwrap(),
97+
Felt252::from_short_string("ine string, that will for sure ").unwrap(),
98+
Felt252::from_short_string("saturate the pending_word").unwrap(),
99+
Felt252::from(25),
100+
]
101+
);
102+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use starknet::ContractAddress;
2+
3+
#[starknet::interface]
4+
trait IByteArrayPanickingContract<TContractState> {
5+
fn do_panic(self: @TContractState);
6+
}
7+
8+
#[starknet::contract]
9+
mod ByteArrayPanickingContract {
10+
#[storage]
11+
struct Storage {}
12+
13+
#[abi(embed_v0)]
14+
impl Impl of super::IByteArrayPanickingContract<ContractState> {
15+
fn do_panic(self: @ContractState) {
16+
assert!(
17+
false,
18+
"This is a very long\n and multiline string, that will for sure saturate the pending_word"
19+
);
20+
}
21+
}
22+
}
23+
24+
#[starknet::interface]
25+
trait IByteArrayPanickingContractProxy<TContractState> {
26+
fn call_bytearray_panicking_contract(self: @TContractState, contract_address: ContractAddress);
27+
}
28+
29+
#[starknet::contract]
30+
mod ByteArrayPanickingContractProxy {
31+
use starknet::ContractAddress;
32+
use super::{IByteArrayPanickingContractDispatcherTrait, IByteArrayPanickingContractDispatcher};
33+
34+
#[storage]
35+
struct Storage {}
36+
37+
#[abi(embed_v0)]
38+
impl Impl of super::IByteArrayPanickingContractProxy<ContractState> {
39+
fn call_bytearray_panicking_contract(
40+
self: @ContractState, contract_address: ContractAddress
41+
) {
42+
IByteArrayPanickingContractDispatcher { contract_address }.do_panic();
43+
}
44+
}
45+
}

crates/cheatnet/tests/contracts/src/lib.cairo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ mod warp;
1111
mod segment_arena_user;
1212
mod panic_call;
1313
mod store_load;
14+
mod bytearray_string_panic_call;

crates/conversions/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ thiserror.workspace = true
2222
serde_json.workspace = true
2323
serde.workspace = true
2424
num-traits.workspace = true
25+
itertools.workspace = true
2526

2627
[dev-dependencies]
2728
ctor.workspace = true
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use cairo_felt::Felt252;
2+
use cairo_lang_utils::byte_array::{BYTES_IN_WORD, BYTE_ARRAY_MAGIC};
3+
use itertools::chain;
4+
use num_traits::Num;
5+
6+
pub struct ByteArray {
7+
words: Vec<Felt252>,
8+
pending_word_len: usize,
9+
pending_word: Felt252,
10+
}
11+
12+
impl From<String> for ByteArray {
13+
fn from(value: String) -> Self {
14+
let chunks = value.as_bytes().chunks_exact(BYTES_IN_WORD);
15+
let remainder = chunks.remainder();
16+
let pending_word_len = remainder.len();
17+
18+
let words = chunks.map(Felt252::from_bytes_be).collect();
19+
let pending_word = Felt252::from_bytes_be(remainder);
20+
21+
Self {
22+
words,
23+
pending_word_len,
24+
pending_word,
25+
}
26+
}
27+
}
28+
29+
impl ByteArray {
30+
#[must_use]
31+
pub fn serialize(self) -> Vec<Felt252> {
32+
chain!(
33+
[
34+
Felt252::from_str_radix(BYTE_ARRAY_MAGIC, 16).unwrap(),
35+
self.words.len().into()
36+
],
37+
self.words.into_iter(),
38+
[self.pending_word, self.pending_word_len.into()]
39+
)
40+
.collect()
41+
}
42+
}

crates/conversions/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::convert::Infallible;
22

3+
pub mod byte_array;
34
pub mod class_hash;
45
pub mod contract_address;
56
pub mod dec_string;

crates/forge/tests/data/contracts/hello_starknet.cairo

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ trait IHelloStarknet<TContractState> {
44
fn get_balance(self: @TContractState) -> felt252;
55
fn do_a_panic(self: @TContractState);
66
fn do_a_panic_with(self: @TContractState, panic_data: Array<felt252>);
7+
fn do_a_panic_with_bytearray(self: @TContractState);
78
}
89

910
#[starknet::contract]
@@ -39,5 +40,10 @@ mod HelloStarknet {
3940
fn do_a_panic_with(self: @ContractState, panic_data: Array<felt252>) {
4041
panic(panic_data);
4142
}
43+
44+
// Panics with a bytearray
45+
fn do_a_panic_with_bytearray(self: @ContractState) {
46+
assert!(false, "This is a very long\n and multiline message that is certain to fill the buffer");
47+
}
4248
}
4349
}

0 commit comments

Comments
 (0)