Skip to content

Commit 0ed3629

Browse files
committed
Derive fuzzable trait
1 parent 5b5d5ba commit 0ed3629

35 files changed

Lines changed: 1184 additions & 12 deletions

File tree

CHANGELOG.md

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

2929
- `argent` option for `--type` flag in `account create` and `account import` commands. Use `ready` instead. Old account files with `"type": "argent"` are still loaded correctly.
3030

31+
#### Added
32+
33+
- `#[derive(Fuzzable)]` macro that automatically generates `Fuzzable` trait implementations for structs and enums
34+
3135
## [0.60.0] - 2026-04-27
3236

3337
### Forge
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#[derive(Debug, Drop, Fuzzable)]
2+
struct InnerStruct {
3+
value: u64,
4+
}
5+
6+
#[derive(Debug, Drop, Fuzzable)]
7+
struct OuterStruct {
8+
inner: InnerStruct,
9+
extra: u32,
10+
}
11+
12+
#[derive(Debug, Drop, Fuzzable)]
13+
struct MultiField<T> {
14+
a: u256,
15+
b: u128,
16+
c: felt252,
17+
flag: bool,
18+
signed: i64,
19+
address: starknet::ContractAddress,
20+
t: T,
21+
}
22+
23+
#[derive(Debug, Drop, Fuzzable)]
24+
enum InnerEnum {
25+
Red: u8,
26+
Green,
27+
Blue: u32,
28+
}
29+
30+
#[derive(Debug, Drop, Fuzzable)]
31+
enum OuterEnum<T> {
32+
North: u8,
33+
South,
34+
East: InnerEnum,
35+
West: OuterStruct,
36+
Other: MultiField<T>,
37+
}
38+
39+
#[fuzzer(runs: 10, seed: 1)]
40+
#[test]
41+
fn test_derived_enum(d: OuterEnum<u8>) {
42+
println!("{d:?}");
43+
let is_north = match d {
44+
OuterEnum::North(_) => true,
45+
_ => false,
46+
};
47+
// It fails deterministically since seed is set.
48+
assert(is_north, 'expected North');
49+
}

crates/forge/tests/e2e/fuzzing.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn fuzzing() {
3636
[PASS] fuzzing::tests::uint128_arg (runs: 256, [..]
3737
[PASS] fuzzing::tests::uint256_arg (runs: 256, [..]
3838
Running 0 test(s) from tests/
39-
Tests: 12 passed, 1 failed, 0 ignored, 12 filtered out
39+
Tests: 12 passed, 1 failed, 0 ignored, 13 filtered out
4040
Fuzzer seed: [..]
4141
4242
Failures:
@@ -81,7 +81,7 @@ fn fuzzing_set_runs() {
8181
[PASS] fuzzing::tests::uint128_arg (runs: 10, [..]
8282
[PASS] fuzzing::tests::uint256_arg (runs: 10, [..]
8383
Running 0 test(s) from tests/
84-
Tests: 12 passed, 1 failed, 0 ignored, 12 filtered out
84+
Tests: 12 passed, 1 failed, 0 ignored, 13 filtered out
8585
Fuzzer seed: [..]
8686
8787
Failures:
@@ -126,7 +126,7 @@ fn fuzzing_set_seed() {
126126
[PASS] fuzzing::tests::uint128_arg (runs: 256, [..]
127127
[PASS] fuzzing::tests::uint256_arg (runs: 256, [..]
128128
Running 0 test(s) from tests/
129-
Tests: 12 passed, 1 failed, 0 ignored, 12 filtered out
129+
Tests: 12 passed, 1 failed, 0 ignored, 13 filtered out
130130
Fuzzer seed: 1234
131131
132132
Failures:
@@ -196,7 +196,7 @@ fn fuzzing_exit_first() {
196196
Failure data:
197197
0x32202b2062203d3d2032202b2062 ('2 + b == 2 + b')
198198
199-
Tests: 0 passed, 1 failed, 0 ignored, 23 filtered out
199+
Tests: 0 passed, 1 failed, 0 ignored, 24 filtered out
200200
Interrupted execution of 1 test(s).
201201
202202
Fuzzer seed: [..]
@@ -232,7 +232,7 @@ fn fuzzing_exit_first_single_fail() {
232232
Failures:
233233
fuzzing_integrationtest::exit_first_single_fail::exit_first_fails_test
234234
235-
Tests: 0 passed, 1 failed, 0 ignored, 23 filtered out
235+
Tests: 0 passed, 1 failed, 0 ignored, 24 filtered out
236236
Interrupted execution of 1 test(s).
237237
"},
238238
);
@@ -260,7 +260,7 @@ fn fuzzing_multiple_attributes() {
260260
[PASS] fuzzing_integrationtest::multiple_attributes::with_should_panic (runs: 256, [..])
261261
[PASS] fuzzing_integrationtest::multiple_attributes::with_available_gas (runs: 50, [..])
262262
[PASS] fuzzing_integrationtest::multiple_attributes::with_both (runs: 300, [..])
263-
Tests: 3 passed, 0 failed, 1 ignored, 21 filtered out
263+
Tests: 3 passed, 0 failed, 1 ignored, 22 filtered out
264264
"},
265265
);
266266
}
@@ -286,7 +286,7 @@ fn generate_arg_cheatcode() {
286286
"`generate_arg` cheatcode: `min_value` must be <= `max_value`, provided values after deserialization: 101 and 100"
287287
288288
[PASS] fuzzing_integrationtest::generate_arg::use_generate_arg_outside_fuzzer (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~[..])
289-
Tests: 1 passed, 1 failed, 0 ignored, 23 filtered out
289+
Tests: 1 passed, 1 failed, 0 ignored, 24 filtered out
290290
"#},
291291
);
292292
}
@@ -337,7 +337,40 @@ fn fuzz_generic_struct() {
337337
Collected 1 test(s) from fuzzing package
338338
Running 1 test(s) from tests/
339339
[PASS] fuzzing_integrationtest::generic_struct::test_generic ([..])
340-
Tests: 1 passed, 0 failed, 0 ignored, 24 filtered out
340+
Tests: 1 passed, 0 failed, 0 ignored, 25 filtered out
341341
"},
342342
);
343343
}
344+
345+
#[test]
346+
fn fuzz_derive_fuzzable() {
347+
let temp = setup_package("fuzzing");
348+
349+
let output = test_runner(&temp)
350+
.arg("derive_fuzzable")
351+
.env("SNFORGE_DETERMINISTIC_OUTPUT", "1")
352+
.assert()
353+
.code(1);
354+
355+
assert_stdout_contains(
356+
output,
357+
indoc! { r#"
358+
[..]Compiling[..]
359+
[..]Finished[..]
360+
361+
362+
Collected 1 test(s) from fuzzing package
363+
Running 1 test(s) from tests/
364+
[FAIL] fuzzing_integrationtest::derive_fuzzable::test_derived_enum (runs: 1, arguments: ["OuterEnum::South(())"])
365+
366+
Failure data:
367+
0x6578706563746564204e6f727468 ('expected North')
368+
369+
Tests: 0 passed, 1 failed, 0 ignored, 25 filtered out
370+
Fuzzer seed: [..]
371+
372+
Failures:
373+
fuzzing_integrationtest::derive_fuzzable::test_derived_enum
374+
"#},
375+
);
376+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod fuzzable;

0 commit comments

Comments
 (0)