├── .github
└── workflows
│ └── rust.yml
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── cairo-fuzzer.png
├── cairo-native-fuzzer
├── .gitignore
├── Cargo.toml
├── README.md
├── doc
│ ├── cairo_fuzzer_demo.png
│ └── property_testing.png
├── examples
│ ├── cairo
│ │ ├── echo.cairo
│ │ ├── fuzzinglabs.cairo
│ │ └── proptesting.cairo
│ └── sierra
│ │ ├── hello_starknet__hello_starknet.contract_class.json
│ │ └── zklend_fuzzing.json
├── install.sh
└── src
│ ├── custom_rand
│ ├── mod.rs
│ └── rng.rs
│ ├── fuzzer
│ ├── fuzzer.rs
│ ├── mod.rs
│ ├── statistics.rs
│ └── utils.rs
│ ├── main.rs
│ ├── mutator
│ ├── argument_type.rs
│ ├── basic_mutator.rs
│ ├── magic_values.rs
│ └── mod.rs
│ ├── runner
│ ├── mod.rs
│ ├── runner.rs
│ └── syscall_handler.rs
│ └── utils.rs
├── docs
├── TUTO101.md
├── USAGE.md
├── crash.png
└── fuzzer_running.png
├── scripts
├── Cargo.toml
└── src
│ └── main.rs
├── setup.sh
├── src
├── cli
│ ├── args.rs
│ ├── config.rs
│ └── mod.rs
├── custom_rand
│ ├── mod.rs
│ └── rng.rs
├── fuzzer
│ ├── corpus_crash.rs
│ ├── corpus_input.rs
│ ├── dict.rs
│ ├── fuzzer.rs
│ ├── mod.rs
│ ├── starknet_worker.rs
│ ├── stats.rs
│ └── utils.rs
├── json
│ ├── json_parser.rs
│ └── mod.rs
├── lib.rs
├── main.rs
├── mutator
│ ├── magic_values.rs
│ ├── mod.rs
│ └── mutator_felt252.rs
└── runner
│ ├── mod.rs
│ ├── runner.rs
│ └── starknet_runner.rs
├── test-generator
├── Cargo.toml
├── README.md
└── src
│ └── main.rs
└── tests1.0
├── config.json
├── dict
├── fuzzinglabs.cairo
├── fuzzinglabs.casm
├── fuzzinglabs.json
├── fuzzinglabs_fuzz.cairo
├── fuzzinglabs_fuzz.casm
├── fuzzinglabs_fuzz.json
├── fuzzinglabs_init.cairo
├── fuzzinglabs_init.casm
├── fuzzinglabs_init.json
├── fuzzinglabs_starknet_2023-04-04--12:38:47.json
├── test_symbolic_execution_2022-12-22--10:18:57.json
├── teststorage.cairo
├── teststorage.casm
└── teststorage.json
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | name: Rust
2 |
3 | on:
4 | push:
5 | branches: [ "starknet-rs-fuzzer" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | env:
10 | CARGO_TERM_COLOR: always
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Build
20 | run: cargo build --verbose
21 | - name: Run tests
22 | run: cargo test --verbose
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | */target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 | .vscode/
12 | *_workspace/
13 |
14 | # Added by cargo
15 |
16 | /target
17 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = 'cairo-fuzzer'
3 | version = '0.1.1'
4 | edition = '2021'
5 |
6 | [dependencies]
7 | chrono = '0.4.19'
8 | rand = '0.8.5'
9 | serde_json = '1.0'
10 | serde = '*'
11 | log = '0.4.20'
12 | thiserror = '1.0.32'
13 | sha3 = '0.10.8'
14 | num-traits = '0.2.15'
15 | cairo-lang-starknet = { version = "2.1.0-rc2", default-features = false }
16 | cairo-lang-casm = { version = "2.1.0-rc2", default-features = false }
17 |
18 | [dependencies.cairo-rs]
19 | git = 'https://github.com/FuzzingLabs/cairo-rs'
20 | rev = '48af153240392992f18a09e969bae6518eec9639'
21 | package = 'cairo-vm'
22 |
23 | [dependencies.felt]
24 | git = 'https://github.com/FuzzingLabs/cairo-rs'
25 | rev = '48af153240392992f18a09e969bae6518eec9639'
26 | package = 'cairo-felt'
27 |
28 | [dependencies.starknet-rs]
29 | git = 'https://github.com/FuzzingLabs/starknet_in_rust/'
30 | rev = '6ee8eef52af4c0fea78d951ee29e52940363be69'
31 | package = 'starknet_in_rust'
32 |
33 | [dependencies.num-bigint]
34 | version = '0.4'
35 | features = ['serde']
36 |
37 | [dependencies.clap]
38 | version = ' 4.1.16'
39 | features = ['derive']
40 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | fuzzinglabs:
2 | cargo run --release -- --cores 10 --contract ./tests1.0/fuzzinglabs.json --casm ./tests1.0/fuzzinglabs.casm --function "Fuzz_symbolic_execution"
3 |
4 | teststorage:
5 | cargo run --release -- --cores 1 --contract ./tests1.0/teststorage.json --casm ./tests1.0/teststorage.casm --function "storage_test"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cairo-Fuzzer -- Cairo Smart Contract Fuzzer
2 |
3 | > [!IMPORTANT]
4 | > This repository is no longer maintained. If you have any questions or need further assistance, please contact [FuzzingLabs](https://fuzzinglabs.com/).
5 |
6 | Release version 1.2
7 | Developped and maintained by [@FuzzingLabs](https://github.com/FuzzingLabs)
8 |
9 | ## Description:
10 |
11 | Cairo-fuzzer is a tool designed for smart contract developers to test the security. It can be used as an independent tool or as a library.
12 |
13 | ## Features:
14 |
15 |
16 |
17 |
18 |
19 | - Run Starknet contract
20 | - Replayer of fuzzing corpus
21 | - Minimizer of fuzzing corpus
22 | - Load old corpus
23 | - Handle multiple arguments
24 | - Workspace architecture
25 | - Import dictionnary
26 | - Use Cairo-fuzzer as a library
27 |
28 |
29 | ## Usage:
30 | ```
31 | cargo run --release -- --cores 10 --contract ./tests1.0/fuzzinglabs.json --casm ./tests1.0/fuzzinglabs.casm --function "Fuzz_symbolic_execution"
32 |
33 | ```
34 |
35 | For more usage information, follow our [tutorial](docs/TUTO101.md)
36 |
37 | ## CMDLINE (--help):
38 |
39 | ```
40 | Usage: cairo-fuzzer [OPTIONS]
41 |
42 | Options:
43 | --cores Set the number of threads to run [default: 1]
44 | --contract Set the path of the JSON artifact to load [default: ]
45 | --casm Set the path of the JSON CASM artifact to load [default: ]
46 | --function Set the function to fuzz [default: ]
47 | --workspace Workspace of the fuzzer [default: fuzzer_workspace]
48 | --inputfolder Path to the inputs folder to load [default: ]
49 | --crashfolder Path to the crashes folder to load [default: ]
50 | --inputfile Path to the inputs file to load [default: ]
51 | --crashfile Path to the crashes file to load [default: ]
52 | --dict Path to the dictionnary file to load [default: ]
53 | --logs Enable fuzzer logs in file
54 | --seed Set a custom seed (only applicable for 1 core run)
55 | --run-time Number of seconds this fuzzing session will last
56 | --config Load config file
57 | --replay Replay the corpus folder
58 | --minimizer Minimize Corpora
59 | --proptesting Property Testing
60 | --analyze Dump functions prototypes
61 | --iter Iteration Number [default: -1]
62 | -h, --help Print help
63 | ```
64 |
65 | # F.A.Q
66 |
67 | ## How to find a Cairo/Starknet compilation artifact (json file)?
68 |
69 | Cairo-Fuzzer supports starknet compilation artifact (json and casm files) generated after compilation using `starknet-compile` and `starknet-sierra-compile`.
70 | Cairo-Fuzzer does not support Cairo2.0 and pure cairo contract.
71 |
72 | ## How to run the tests?
73 |
74 | ```
75 | cargo test
76 | ```
77 |
78 | # License
79 |
80 | Cairo-Fuzzer is licensed and distributed under the AGPLv3 license. [Contact us](mailto:contact@fuzzinglabs.com) if you're looking for an exception to the terms.
81 |
--------------------------------------------------------------------------------
/cairo-fuzzer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FuzzingLabs/cairo-fuzzer/9b063a9e99bb0aff44f8444557682f7ff6fd4708/cairo-fuzzer.png
--------------------------------------------------------------------------------
/cairo-native-fuzzer/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /cairo2
3 | /corelib
4 | cairo*.tar
--------------------------------------------------------------------------------
/cairo-native-fuzzer/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "cairo-native-fuzzer"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | cairo-lang-compiler = "=2.9.3"
8 | cairo-lang-sierra = "=2.9.3"
9 | cairo-lang-starknet = "=2.9.3"
10 | cairo-lang-starknet-classes = "=2.9.3"
11 | cairo-native = { version = "=0.2.5-rc1", features = ["with-runtime"] }
12 | cairo-native-runtime = { version = "=0.2.5-rc1", optional = true }
13 | clap = "4.5.20"
14 | colog = "1.3.0"
15 | colored = "2.1.0"
16 | env_logger = "0.11.5"
17 | log = "0.4.22"
18 | rand = "0.8.5"
19 | regex = "1.11.1"
20 | serde_json = "1.0.138"
21 | starknet-types-core = "0.1.7"
22 |
23 | [dependencies.felt]
24 | git = 'https://github.com/FuzzingLabs/cairo-rs'
25 | rev = '48af153240392992f18a09e969bae6518eec9639'
26 | package = 'cairo-felt'
27 |
28 | [features]
29 | default = ["with-runtime"]
30 | with-runtime = ["dep:cairo-native-runtime"]
31 |
32 | [profile.dev]
33 | opt-level = 0
34 | debug = true
35 | debug-assertions = true
36 | overflow-checks = true
37 | lto = false
38 | panic = 'unwind'
39 | incremental = true
40 | codegen-units = 256
41 | rpath = false
42 |
43 | [profile.release]
44 | opt-level = 3
45 | debug = false
46 | debug-assertions = false
47 | overflow-checks = false
48 | lto = true
49 | panic = 'unwind'
50 | incremental = false
51 | codegen-units = 1
52 | rpath = false
53 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/README.md:
--------------------------------------------------------------------------------
1 | ## Cairo Native Fuzzer
2 |
3 | Cairo Native Fuzzer is a rewrite of the Cairo Fuzzer based on [Cairo native from Lambdaclass](https://github.com/lambdaclass/cairo_native) developed to enhance fuzzer execution speed.
4 |
5 |
6 |
7 |
8 |
9 | ### Installation
10 |
11 | - Install LLVM 19.
12 | - Run `insall.sh`.
13 |
14 | #### Print the functions prototypes
15 |
16 | ```sh
17 | ## With a cairo program
18 | cargo run -- --program-path examples/fuzzinglabs.cairo --analyze
19 |
20 | # With a Sierra program
21 | cargo run -- --sierra-program ./examples/sierra/zklend_fuzzing.json --analyze
22 | ```
23 |
24 | #### Run the fuzzer
25 |
26 | ```sh
27 | cargo run -- --program-path ./examples/cairo/echo.cairo --entry-point echo::echo::Echo::__wrapper__echo_felt
28 |
29 | ## Use a seed
30 | cargo run -- --program-path ./examples/cairo/echo.cairo --entry-point echo::echo::Echo::__wrapper__echo_felt --seed 42
31 |
32 | ## With a sierra input file
33 | cargo run -- --sierra-program ./examples/sierra/zklend_fuzzing.json --entry-point zklend::fuzzing::Fuzzing::__wrapper__fuzz_scaled_down_amount --seed 1739662178
34 | ```
35 |
36 | #### Property testing
37 |
38 | You can define functions that will be fuzzed automatically by prefixing their name with `fuzz_` :
39 |
40 | ```rs
41 | #[starknet::contract]
42 | mod Echo {
43 | #[storage]
44 | struct Storage {
45 | balance: felt252,
46 | }
47 |
48 | #[constructor]
49 | fn constructor(ref self: ContractState, initial_balance: felt252) {
50 | self.balance.write(initial_balance);
51 | }
52 |
53 | #[external(v0)]
54 | fn fuzz_test(ref self: ContractState, value: felt252) -> felt252 {
55 | assert(value != 2, 'fail');
56 | value
57 | }
58 |
59 | #[external(v0)]
60 | fn fuzz_test2(ref self: ContractState, value: u32) -> u32 {
61 | assert(value != 3, 'fail');
62 | value
63 | }
64 | }
65 | ```
66 |
67 | Then run the `cairo-fuzzer` with the `--proptesting` flag :
68 |
69 | ```sh
70 | cargo run -- --program-path examples/cairo/proptesting.cairo --proptesting
71 | ```
72 |
73 |
74 |
75 |
76 |
77 |
78 | ### Roadmap
79 |
80 | - [x] Implement the Cairo Native runner
81 | - [x] Implement the fuzzer based on Cairo Native runner
82 | - [x] Import existing mutator from the cairo-fuzzer
83 | - [x] Property testing
84 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/doc/cairo_fuzzer_demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FuzzingLabs/cairo-fuzzer/9b063a9e99bb0aff44f8444557682f7ff6fd4708/cairo-native-fuzzer/doc/cairo_fuzzer_demo.png
--------------------------------------------------------------------------------
/cairo-native-fuzzer/doc/property_testing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FuzzingLabs/cairo-fuzzer/9b063a9e99bb0aff44f8444557682f7ff6fd4708/cairo-native-fuzzer/doc/property_testing.png
--------------------------------------------------------------------------------
/cairo-native-fuzzer/examples/cairo/echo.cairo:
--------------------------------------------------------------------------------
1 |
2 | #[starknet::contract]
3 | mod Echo {
4 | use integer::u8_try_as_non_zero;
5 |
6 | #[storage]
7 | struct Storage {}
8 |
9 | #[external(v0)]
10 | fn echo_felt(ref self: ContractState, value: felt252) -> felt252 {
11 | assert(value != 2, 'fail');
12 | value
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/examples/cairo/fuzzinglabs.cairo:
--------------------------------------------------------------------------------
1 | #[starknet::contract]
2 | mod test_contract {
3 | #[storage]
4 | struct Storage {
5 | bal:u8
6 | }
7 |
8 | #[external(v0)]
9 | fn Fuzz_symbolic_execution(
10 | ref self: ContractState,
11 | f: felt252,
12 | u: felt252,
13 | z: u32,
14 | z2: u32,
15 | i: u64,
16 | n: u128,
17 | g: u128,
18 | l: u128,
19 | a: felt252,
20 | b: felt252,
21 | s: u8,
22 | ) {
23 | if (f == 'f') {
24 | if (u == 'u') {
25 | if (z == 'z') {
26 | if (z2 == 'z') {
27 | if (i == 'i') {
28 | if (n == 'n') {
29 | if (g == 'g') {
30 | if (l == 'l') {
31 | if (a == 'a') {
32 | if (b == 'b') {
33 | if (s == 's') {
34 | assert(1==0 , '!(f & t)');
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }
42 | }
43 | }
44 | }
45 | }
46 | return ();
47 | }
48 | }
--------------------------------------------------------------------------------
/cairo-native-fuzzer/examples/cairo/proptesting.cairo:
--------------------------------------------------------------------------------
1 | #[starknet::contract]
2 | mod Echo {
3 | #[storage]
4 | struct Storage {
5 | balance: felt252,
6 | }
7 |
8 | #[constructor]
9 | fn constructor(ref self: ContractState, initial_balance: felt252) {
10 | //panic_with_felt252('panic');
11 | self.balance.write(initial_balance);
12 | }
13 |
14 | #[external(v0)]
15 | fn fuzz_test(ref self: ContractState, value: felt252) -> felt252 {
16 | assert(value != 2, 'fail');
17 | value
18 | }
19 |
20 | #[external(v0)]
21 | fn fuzz_test2(ref self: ContractState, value: felt252) -> felt252 {
22 | assert(value != 3, 'fail');
23 | value
24 | }
25 |
26 | #[external(v0)]
27 | fn fuzz_i128(ref self: ContractState, value: i128) -> i128 {
28 | assert(value != 2, 'fail');
29 | value
30 | }
31 |
32 | #[external(v0)]
33 | fn fuzz_i64(ref self: ContractState, value: i64) -> i64 {
34 | assert(value != 2, 'fail');
35 | value
36 | }
37 |
38 | #[external(v0)]
39 | fn fuzz_i32(ref self: ContractState, value: i32) -> i32 {
40 | assert(value != 2, 'fail');
41 | value
42 | }
43 |
44 | #[external(v0)]
45 | fn fuzz_i16(ref self: ContractState, value: i16) -> i16 {
46 | assert(value != 2, 'fail');
47 | value
48 | }
49 |
50 | #[external(v0)]
51 | fn fuzz_i8(ref self: ContractState, value: i8) -> i8 {
52 | assert(value != 2, 'fail');
53 | value
54 | }
55 |
56 | #[external(v0)]
57 | fn fuzz_u256(ref self: ContractState, value: u256) -> u256 {
58 | assert(value != 2, 'fail');
59 | value
60 | }
61 |
62 | #[external(v0)]
63 | fn fuzz_u128(ref self: ContractState, value: u128) -> u128 {
64 | assert(value != 2, 'fail');
65 | value
66 | }
67 |
68 | #[external(v0)]
69 | fn fuzz_u64(ref self: ContractState, value: u64) -> u64 {
70 | assert(value != 2, 'fail');
71 | value
72 | }
73 |
74 | #[external(v0)]
75 | fn fuzz_u32(ref self: ContractState, value: u32) -> u32 {
76 | assert(value != 2, 'fail');
77 | value
78 | }
79 |
80 | #[external(v0)]
81 | fn fuzz_u16(ref self: ContractState, value: u16) -> u16 {
82 | assert(value != 2, 'fail');
83 | value
84 | }
85 |
86 | #[external(v0)]
87 | fn fuzz_u8(ref self: ContractState, value: u8) -> u8 {
88 | assert(value != 2, 'fail');
89 | value
90 | }
91 | }
--------------------------------------------------------------------------------
/cairo-native-fuzzer/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ###
4 | ### This script automates the setup of the Cairo 2 compiler and Scarb on Linux and macOS systems.
5 | ### It downloads the appropriate Cairo release, decompresses it, installs Scarb, and sets up
6 | ### the necessary environment variables for macOS. Additionally, it creates a symbolic link
7 | ### to the Cairo core library.
8 | ###
9 |
10 | set -e
11 |
12 | UNAME=$(uname)
13 | CAIRO_2_VERSION=2.9.1
14 | SCARB_VERSION=2.9.1
15 |
16 | # Decompress the Cairo tarball
17 | function decompress_cairo {
18 | local source=$1
19 | local target=$2
20 | rm -rf "$target"
21 | tar -xzvf "$source"
22 | mv cairo/ "$target"
23 | }
24 |
25 | # Download the Cairo tarball
26 | function download_cairo {
27 | local version=$1
28 | local os=$2
29 | local url=""
30 |
31 | if [ "$os" == "macos" ]; then
32 | url="https://github.com/starkware-libs/cairo/releases/download/v${version}/release-aarch64-apple-darwin.tar"
33 | else
34 | url="https://github.com/starkware-libs/cairo/releases/download/v${version}/release-x86_64-unknown-linux-musl.tar.gz"
35 | fi
36 |
37 | curl -L -o "cairo-${version}-${os}.tar" "$url"
38 | }
39 |
40 | # Install Scarb
41 | function install_scarb {
42 | curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- --no-modify-path --version "$SCARB_VERSION"
43 | }
44 |
45 | # Build the Cairo 2 compiler
46 | function build_cairo_2_compiler {
47 | local os=$1
48 | local cairo_dir="cairo2"
49 |
50 | if [ "$os" == "macos" ]; then
51 | cairo_dir="cairo2-macos"
52 | fi
53 |
54 | download_cairo "$CAIRO_2_VERSION" "$os"
55 | decompress_cairo "cairo-${CAIRO_2_VERSION}-${os}.tar" "$cairo_dir"
56 | }
57 |
58 | # Install dependencies for macOS
59 | function deps_macos {
60 | build_cairo_2_compiler "macos"
61 | install_scarb
62 | brew install llvm@19 --quiet
63 | echo "You can execute the env-macos.sh script to setup the needed env variables."
64 | }
65 |
66 | # Install dependencies for Linux
67 | function deps_linux {
68 | build_cairo_2_compiler "linux"
69 | install_scarb
70 | }
71 |
72 | # Determine the OS and call the appropriate function
73 | function main {
74 | if [ "$UNAME" == "Linux" ]; then
75 | deps_linux
76 | elif [ "$UNAME" == "Darwin" ]; then
77 | deps_macos
78 | else
79 | echo "Unsupported operating system: $UNAME"
80 | exit 1
81 | fi
82 |
83 | rm -rf corelib
84 | ln -s cairo2/corelib corelib
85 | }
86 |
87 | main
88 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/custom_rand/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod rng;
2 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/custom_rand/rng.rs:
--------------------------------------------------------------------------------
1 | use std::cell::Cell;
2 | use std::ops::RangeInclusive;
3 |
4 | /// Random number generator implementation using xorshift64
5 | /// We use an xorshift rng during mutations for better performances
6 | #[derive(Clone)]
7 | pub struct Rng {
8 | /// Internal xorshift seed
9 | seed: Cell,
10 | }
11 |
12 | impl Rng {
13 | /// Creates a RNG with a fixed `seed` value
14 | pub fn seeded(seed: u64) -> Self {
15 | Rng {
16 | seed: Cell::new(seed),
17 | }
18 | }
19 |
20 | /// Get a random 64-bit number using xorshift
21 | pub fn rand(&self) -> u64 {
22 | let mut seed = self.seed.get();
23 | seed ^= seed << 13;
24 | seed ^= seed >> 17;
25 | seed ^= seed << 43;
26 | self.seed.set(seed);
27 | seed
28 | }
29 |
30 | /// Get a random usize number using xorshift
31 | pub fn rand_usize(&self) -> usize {
32 | self.rand() as usize
33 | }
34 |
35 | /// Generate a random number in the range [start, end]
36 | pub fn gen_range(&self, range: RangeInclusive) -> usize {
37 | let start = *range.start();
38 | let end = *range.end();
39 | assert!(end >= start, "end must be greater than or equal to start");
40 | start + self.rand_usize() % (end - start + 1)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/fuzzer/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod fuzzer;
2 | pub mod statistics;
3 | pub mod utils;
4 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/fuzzer/statistics.rs:
--------------------------------------------------------------------------------
1 | use std::time::Instant;
2 |
3 | /// Cairo Fuzzer statistics
4 | pub struct FuzzerStats {
5 | // Total fuzzer executions
6 | pub total_executions: usize,
7 | // Start time of the fuzzer
8 | pub start_time: Instant,
9 | // Total number of crashes
10 | pub crashes: usize,
11 | }
12 |
13 | impl Default for FuzzerStats {
14 | fn default() -> Self {
15 | Self {
16 | // Init the fuzzer statistics
17 | total_executions: 0,
18 | crashes: 0,
19 | start_time: Instant::now(),
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/fuzzer/utils.rs:
--------------------------------------------------------------------------------
1 | use regex::Regex;
2 | use std::sync::Arc;
3 |
4 | use cairo_lang_sierra::ids::FunctionId;
5 | use cairo_lang_sierra::program::Program;
6 | use colored::*;
7 |
8 | use crate::mutator::argument_type::{map_argument_type, ArgumentType};
9 | use crate::utils::get_cairo_native_version;
10 | use crate::utils::get_function_by_id;
11 |
12 | // Initialization message printed at fuzzer launch
13 | const INIT_MESSAGE_FORMAT: &str = "
14 | =============================================================================================================================================================
15 | ╔═╗ ┌─┐ ┬ ┬─┐ ┌───┐ ╔═╗ ┬ ┬ ┌─┐ ┌─┐ ┌─┐ ┬─┐ | Seed -- {}
16 | ║ ├─┤ │ ├┬┘ │2.0│───╠╣ │ │ ┌─┘ ┌─┘ ├┤ ├┬┘ | cairo-native version -- {}
17 | ╚═╝ ┴ ┴ ┴ ┴└─ └───┘ ╚ └─┘ └─┘ └─┘ └─┘ ┴└─ |
18 | =============================================================================================================================================================
19 | ";
20 |
21 | /// Print the initialization message
22 | pub fn print_init_message(seed: u64) {
23 | let version = get_cairo_native_version();
24 |
25 | // Replace the first occurrence of {} with the seed value
26 | let re = Regex::new(r"\{\}").unwrap();
27 | let message = re.replace(INIT_MESSAGE_FORMAT, |_: ®ex::Captures| seed.to_string());
28 |
29 | // Replace the next occurrence of {} with the version string
30 | let message = re.replace(&message, |_: ®ex::Captures| version.to_string());
31 |
32 | println!("{}", message);
33 | }
34 |
35 | /// Returns a vector of the function parameter types
36 | ///
37 | /// For example, given a function with the prototype:
38 | /// ```
39 | /// myfunction(a: felt252, b: felt252) -> felt252
40 | /// ```
41 | /// This function will return:
42 | /// ```
43 | /// [Felt, Felt]
44 | /// ```
45 | pub fn get_function_argument_types(
46 | sierra_program: &Option>,
47 | entry_point_id: &Option,
48 | ) -> Vec {
49 | // Get the function from the Sierra program using the entry point id
50 | let func = match (sierra_program, entry_point_id) {
51 | (Some(program), Some(entry_point_id)) => get_function_by_id(program, entry_point_id),
52 | _ => None,
53 | };
54 |
55 | // Iterate through entry point arguments and map their types to a type supported by the fuzzer
56 | if let Some(func) = func {
57 | let argument_types: Vec = func
58 | .signature
59 | .param_types
60 | .iter()
61 | .filter_map(|param_type| {
62 | if let Some(debug_name) = ¶m_type.debug_name {
63 | // Map param_type to an `ArgumentType`
64 | // For now we only handle felt252
65 | return map_argument_type(debug_name);
66 | }
67 | None
68 | })
69 | .collect();
70 | argument_types
71 | } else {
72 | Vec::new()
73 | }
74 | }
75 |
76 | /// Print the contract functions prototypes
77 | pub fn print_contract_functions(sierra_program: &Option>) {
78 | println!("Contract functions :\n");
79 |
80 | if let Some(program) = sierra_program {
81 | for function in &program.funcs {
82 | // Use function.id.debug_name if available, otherwise use function.id.id
83 | let function_name = function
84 | .id
85 | .debug_name
86 | .clone()
87 | .unwrap_or_else(|| function.id.id.to_string().into());
88 |
89 | let signature = &function.signature;
90 |
91 | // Collect parameter types
92 | let param_types: Vec = signature
93 | .param_types
94 | .iter()
95 | .map(|param| {
96 | param
97 | .debug_name
98 | .as_ref()
99 | .expect("Parameter name not found")
100 | .to_string()
101 | })
102 | .collect();
103 |
104 | // Collect return types
105 | let ret_types: Vec = signature
106 | .ret_types
107 | .iter()
108 | .map(|ret_type| {
109 | ret_type
110 | .debug_name
111 | .as_ref()
112 | .expect("Return type name not found")
113 | .to_string()
114 | })
115 | .collect();
116 |
117 | // Format the prototype
118 | let prototype = format!(
119 | "{} ({}) -> ({})",
120 | function_name.bold().white(),
121 | param_types.join(", ").green(),
122 | ret_types.join(", ").cyan()
123 | );
124 |
125 | // Print the contract functions
126 | println!("- {}", prototype);
127 | }
128 | }
129 | }
130 |
131 | /// Find the entry point id
132 | pub fn find_entry_point_id(sierra_program: &Option>, entry_point: &str) -> FunctionId {
133 | let sierra_program = sierra_program
134 | .as_ref()
135 | .expect("Sierra program not available");
136 | cairo_native::utils::find_function_id(sierra_program, entry_point)
137 | .expect(&format!("Entry point '{}' not found", entry_point))
138 | .clone()
139 | }
140 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/main.rs:
--------------------------------------------------------------------------------
1 | mod custom_rand;
2 | mod fuzzer;
3 | mod mutator;
4 | mod runner;
5 | mod utils;
6 |
7 | use clap::Parser;
8 | use std::path::PathBuf;
9 | use std::time::{SystemTime, UNIX_EPOCH};
10 |
11 | use crate::fuzzer::fuzzer::Fuzzer;
12 |
13 | /// Command-line arguments for the fuzzer
14 | #[derive(Parser, Debug)]
15 | #[command(version, about, long_about = None)]
16 | struct Args {
17 | /// Path to the Cairo program
18 | #[arg(short, long)]
19 | program_path: Option,
20 |
21 | /// Path to the Sierra program
22 | #[arg(long)]
23 | sierra_program: Option,
24 |
25 | /// Entry point of the Sierra program
26 | #[arg(short, long)]
27 | entry_point: Option,
28 |
29 | /// Analyze the program and print function prototypes
30 | #[arg(short, long)]
31 | analyze: bool,
32 |
33 | /// Number of iterations to use for fuzzing
34 | #[arg(short, long)]
35 | iter: Option,
36 |
37 | /// Enable property-based testing
38 | #[arg(long)]
39 | proptesting: bool,
40 |
41 | /// Seed for the random number generator
42 | #[arg(short, long)]
43 | seed: Option,
44 | }
45 |
46 | fn main() {
47 | let args = Args::parse();
48 |
49 | // Initialize the logger
50 | colog::init();
51 |
52 | // Determine the seed value
53 | let seed = args.seed.unwrap_or_else(|| {
54 | // Use the current time as default seed if the --seed parameter is not specified
55 | let start = SystemTime::now();
56 | start
57 | .duration_since(UNIX_EPOCH)
58 | .expect("Failed to get the current time")
59 | .as_secs()
60 | });
61 |
62 | // Set the default value for iter based on proptesting flag
63 | let iter = if args.proptesting {
64 | args.iter.unwrap_or(10000)
65 | } else {
66 | args.iter.unwrap_or(-1)
67 | };
68 |
69 | // Check if --entry-point parameter is required
70 | if !(args.proptesting || args.analyze) && args.entry_point.is_none() {
71 | eprintln!("Error: --entry-point is required if --proptesting is not set");
72 | return;
73 | }
74 |
75 | // Check if --analyze requires either --program-path or --sierra-program
76 | if args.analyze && args.program_path.is_none() && args.sierra_program.is_none() {
77 | eprintln!("Error: --analyze requires either --program-path or --sierra-program");
78 | return;
79 | }
80 |
81 | // Initialize the fuzzer based on the provided program path
82 | let mut fuzzer = if let Some(sierra_program) = args.sierra_program {
83 | Fuzzer::new_sierra(sierra_program, args.entry_point)
84 | } else if let Some(program_path) = args.program_path {
85 | Fuzzer::new(program_path, args.entry_point)
86 | } else {
87 | eprintln!("Error: Either --program-path or --sierra-program must be specified");
88 | return;
89 | };
90 |
91 | match fuzzer.init(seed) {
92 | Ok(()) => {
93 | // Print the contract functions
94 | if args.analyze {
95 | fuzzer.print_functions_prototypes();
96 | }
97 | // Run the fuzzer
98 | else {
99 | if args.proptesting {
100 | match fuzzer.fuzz_proptesting(iter) {
101 | Ok(()) => println!("Property-based testing completed successfully."),
102 | Err(e) => eprintln!("Error during property-based testing: {}", e),
103 | }
104 | } else {
105 | match fuzzer.fuzz(iter) {
106 | Ok(()) => println!("Fuzzing completed successfully."),
107 | Err(e) => eprintln!("Error during fuzzing: {}", e),
108 | }
109 | }
110 | }
111 | }
112 | Err(e) => eprintln!("Error during initialization: {}", e),
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/mutator/argument_type.rs:
--------------------------------------------------------------------------------
1 | /// Enum representing the types of arguments that can be passed to a function
2 | #[derive(Debug, Clone)]
3 | pub enum ArgumentType {
4 | Felt,
5 | FeltArray, // TODO: Add support for other types
6 | }
7 |
8 | /// Helper function to map argument types based on their debug names
9 | /// This function takes a debug name string and returns the corresponding `ArgumentType`
10 | pub fn map_argument_type(debug_name: &str) -> Option {
11 | match debug_name {
12 | "felt252" => Some(ArgumentType::Felt),
13 | // We treat felt252 arrays as a single felt for now
14 | "core::array::Span::" => Some(ArgumentType::FeltArray),
15 | // TODO: Add support for other types
16 | _ => None,
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/mutator/basic_mutator.rs:
--------------------------------------------------------------------------------
1 | use starknet_types_core::felt::Felt;
2 |
3 | use crate::custom_rand::rng::Rng;
4 | use crate::mutator::magic_values::MAGIC_VALUES;
5 |
6 | /// This mutator only handles felt252
7 | /// TODO: Handle more types
8 | #[derive(Clone)]
9 | pub struct Mutator {
10 | rng: Rng,
11 | max_input_size: usize,
12 | }
13 |
14 | impl Mutator {
15 | /// Creates a new `Mutator` with the given seed
16 | pub fn new(seed: u64) -> Self {
17 | Self {
18 | rng: Rng::seeded(seed),
19 | max_input_size: 252,
20 | }
21 | }
22 |
23 | pub fn mutate(&mut self, felt: Felt) -> Felt {
24 | // Perform a random mutation
25 | let mutation_type = self.rng.gen_range(0..=15); // Increase range to accommodate more strategies
26 | match mutation_type {
27 | 0 => self.add_small_random_value(felt),
28 | 1 => self.subtract_small_random_value(felt),
29 | 2 => self.flip_random_bit(felt),
30 | 3 => self.inc_byte(felt),
31 | 4 => self.dec_byte(felt),
32 | // 5 => self.neg_byte(felt),
33 | 6 => self.add_sub(felt),
34 | 7 => self.swap(felt),
35 | 8 => self.copy(felt),
36 | 9 => self.inter_splice(felt),
37 | 10 => self.magic_overwrite(felt),
38 | 11 => self.magic_insert(felt),
39 | 12 => self.random_overwrite(felt),
40 | 13 => self.random_insert(felt),
41 | 14 => self.byte_repeat_overwrite(felt),
42 | 15 => self.byte_repeat_insert(felt),
43 | // Fallback to the original value if something goes wrong
44 | _ => felt,
45 | }
46 | }
47 |
48 | fn add_small_random_value(&mut self, felt: Felt) -> Felt {
49 | // Random value between 1 and 9
50 | let small_value = self.rng.gen_range(1..=9);
51 | felt + Felt::from(small_value)
52 | }
53 |
54 | fn subtract_small_random_value(&mut self, felt: Felt) -> Felt {
55 | // Random value between 1 and 9
56 | let small_value = self.rng.gen_range(1..=9);
57 |
58 | // Check for underflow before performing the subtraction
59 | if felt < Felt::from(small_value) {
60 | Felt::from(0)
61 | } else {
62 | felt - Felt::from(small_value)
63 | }
64 | }
65 |
66 | fn flip_random_bit(&mut self, felt: Felt) -> Felt {
67 | // Determine the actual bit length of the Felt value
68 | let felt_bytes = felt.to_bytes_be();
69 | let mut bit_length = 0;
70 |
71 | for byte in felt_bytes.iter().rev() {
72 | if *byte != 0 {
73 | bit_length =
74 | (felt_bytes.len() - felt_bytes.iter().rev().position(|&b| b != 0).unwrap()) * 8;
75 | for i in (0..8).rev() {
76 | if byte & (1 << i) != 0 {
77 | bit_length += i + 1;
78 | break;
79 | }
80 | }
81 | break;
82 | }
83 | }
84 |
85 | if bit_length == 0 {
86 | // If the Felt value is zero, return the original value
87 | return felt;
88 | }
89 |
90 | // Random bit index within the actual bit length
91 | let bit_index = self.rng.gen_range(0..=bit_length - 1);
92 | let byte_index = bit_index / 8;
93 | let bit_position = bit_index % 8;
94 |
95 | // Ensure the byte index is within the valid range
96 | if byte_index >= felt_bytes.len() {
97 | return felt;
98 | }
99 |
100 | // Flip the bit at the calculated position
101 | let mut felt_bytes = felt.to_bytes_be();
102 | felt_bytes[byte_index] ^= 1 << bit_position;
103 |
104 | Felt::from_bytes_be(&felt_bytes)
105 | }
106 |
107 | fn inc_byte(&mut self, felt: Felt) -> Felt {
108 | felt + Felt::from(1)
109 | }
110 |
111 | fn dec_byte(&mut self, felt: Felt) -> Felt {
112 | // Check for underflow before performing the subtraction
113 | if felt <= Felt::from(0) {
114 | Felt::from(0)
115 | } else {
116 | felt - Felt::from(1)
117 | }
118 | }
119 |
120 | fn add_sub(&mut self, felt: Felt) -> Felt {
121 | // Add or subtract a random amount with a random endianness from a random size `u8` through `u64`
122 | let delta = self.rng.gen_range(0..=200) as i64 - 100; // Example range
123 | let new_felt = felt + Felt::from(delta);
124 |
125 | // Clamp the value to a reasonable range
126 | if new_felt > Felt::from(u64::MAX) {
127 | Felt::from(u64::MAX)
128 | } else if new_felt < Felt::from(0) {
129 | Felt::from(0)
130 | } else {
131 | new_felt
132 | }
133 | }
134 |
135 | fn swap(&mut self, felt: Felt) -> Felt {
136 | // Swap two ranges in an input buffer
137 | let mut felt_bytes = felt.to_bytes_be();
138 | let len = felt_bytes.len();
139 | let src = self.rng.gen_range(0..=len - 1);
140 | let dst = self.rng.gen_range(0..=len - 1);
141 | let swap_len = self.rng.gen_range(1..=len.min(len - src.max(dst)));
142 |
143 | for i in 0..swap_len {
144 | felt_bytes.swap(src + i, dst + i);
145 | }
146 |
147 | Felt::from_bytes_be(&felt_bytes)
148 | }
149 |
150 | fn copy(&mut self, felt: Felt) -> Felt {
151 | // Copy bytes from one location in the input and overwrite them at another
152 | let mut felt_bytes = felt.to_bytes_be();
153 | let len = felt_bytes.len();
154 | let src = self.rng.gen_range(0..=len - 1);
155 | let dst = self.rng.gen_range(0..=len - 1);
156 | let copy_len = self.rng.gen_range(1..=len.min(len - src.max(dst)));
157 |
158 | for i in 0..copy_len {
159 | felt_bytes[dst + i] = felt_bytes[src + i];
160 | }
161 |
162 | Felt::from_bytes_be(&felt_bytes)
163 | }
164 |
165 | fn inter_splice(&mut self, felt: Felt) -> Felt {
166 | // Take one location of the input and splice it into another
167 | let felt_bytes = felt.to_bytes_be();
168 | let len = felt_bytes.len();
169 | let src = self.rng.gen_range(0..=len - 1);
170 | let dst = self.rng.gen_range(0..=len - 1);
171 | let splice_len = self.rng.gen_range(1..=len.min(len - src.max(dst)));
172 |
173 | let mut new_bytes = Vec::new();
174 | new_bytes.extend_from_slice(&felt_bytes[..dst]);
175 | new_bytes.extend_from_slice(&felt_bytes[src..src + splice_len]);
176 | new_bytes.extend_from_slice(&felt_bytes[dst..]);
177 |
178 | // Ensure the length is exactly 32 bytes
179 | if new_bytes.len() > 32 {
180 | new_bytes.truncate(32);
181 | } else if new_bytes.len() < 32 {
182 | new_bytes.resize(32, 0);
183 | }
184 |
185 | let mut array = [0u8; 32];
186 | array.copy_from_slice(&new_bytes);
187 |
188 | Felt::from_bytes_be(&array)
189 | }
190 |
191 | fn magic_overwrite(&mut self, felt: Felt) -> Felt {
192 | // Pick a random magic value
193 | let magic_value = &MAGIC_VALUES[self.rng.gen_range(0..=MAGIC_VALUES.len() - 1)];
194 | let mut felt_bytes = felt.to_bytes_be();
195 |
196 | // Overwrite the bytes in the input with the magic value
197 | let len = magic_value.len().min(felt_bytes.len());
198 | felt_bytes[..len].copy_from_slice(&magic_value[..len]);
199 |
200 | Felt::from_bytes_be(&felt_bytes)
201 | }
202 |
203 | fn magic_insert(&mut self, felt: Felt) -> Felt {
204 | // Pick a random magic value
205 | let magic_value = &MAGIC_VALUES[self.rng.gen_range(0..=MAGIC_VALUES.len() - 1)];
206 | let felt_bytes = felt.to_bytes_be();
207 |
208 | // Insert the magic value at a random offset
209 | let offset = self.rng.gen_range(0..=felt_bytes.len());
210 | let mut new_bytes = Vec::new();
211 | new_bytes.extend_from_slice(&felt_bytes[..offset]);
212 | new_bytes.extend_from_slice(magic_value);
213 | new_bytes.extend_from_slice(&felt_bytes[offset..]);
214 |
215 | // Ensure the length is exactly 32 bytes
216 | if new_bytes.len() > 32 {
217 | new_bytes.truncate(32);
218 | } else if new_bytes.len() < 32 {
219 | new_bytes.resize(32, 0);
220 | }
221 |
222 | let mut array = [0u8; 32];
223 | array.copy_from_slice(&new_bytes);
224 |
225 | Felt::from_bytes_be(&array)
226 | }
227 |
228 | fn random_overwrite(&mut self, felt: Felt) -> Felt {
229 | // Overwrite a random offset of the input with random bytes
230 | let mut felt_bytes = felt.to_bytes_be();
231 | let offset = self.rng.gen_range(0..=felt_bytes.len() - 1);
232 | let amount = self.rng.gen_range(1..=felt_bytes.len() - offset);
233 |
234 | for i in offset..offset + amount {
235 | felt_bytes[i] = self.rng.rand_usize() as u8;
236 | }
237 |
238 | Felt::from_bytes_be(&felt_bytes)
239 | }
240 |
241 | fn random_insert(&mut self, felt: Felt) -> Felt {
242 | // Insert random bytes into a random offset in the input
243 | let felt_bytes = felt.to_bytes_be();
244 | let offset = self.rng.gen_range(0..=felt_bytes.len());
245 | let amount = self
246 | .rng
247 | .gen_range(0..=self.max_input_size - felt_bytes.len());
248 |
249 | let mut new_bytes = Vec::new();
250 | new_bytes.extend_from_slice(&felt_bytes[..offset]);
251 | new_bytes.extend(std::iter::repeat(self.rng.rand_usize() as u8).take(amount));
252 | new_bytes.extend_from_slice(&felt_bytes[offset..]);
253 |
254 | // Ensure the length is exactly 32 bytes
255 | if new_bytes.len() > 32 {
256 | new_bytes.truncate(32);
257 | } else if new_bytes.len() < 32 {
258 | new_bytes.resize(32, 0);
259 | }
260 |
261 | let mut array = [0u8; 32];
262 | array.copy_from_slice(&new_bytes);
263 |
264 | Felt::from_bytes_be(&array)
265 | }
266 |
267 | fn byte_repeat_overwrite(&mut self, felt: Felt) -> Felt {
268 | // Find a byte and repeat it multiple times by overwriting the data after it
269 | let mut felt_bytes = felt.to_bytes_be();
270 | let offset = self.rng.gen_range(0..=felt_bytes.len() - 1);
271 | let amount = self.rng.gen_range(1..=felt_bytes.len() - offset);
272 |
273 | let val = felt_bytes[offset];
274 | for i in offset + 1..offset + amount {
275 | felt_bytes[i] = val;
276 | }
277 |
278 | Felt::from_bytes_be(&felt_bytes)
279 | }
280 |
281 | fn byte_repeat_insert(&mut self, felt: Felt) -> Felt {
282 | // Find a byte and repeat it multiple times by splicing a random amount of the byte in
283 | let felt_bytes = felt.to_bytes_be();
284 | let offset = self.rng.gen_range(0..=felt_bytes.len() - 1);
285 | let amount = self
286 | .rng
287 | .gen_range(0..=self.max_input_size - felt_bytes.len());
288 |
289 | let val = felt_bytes[offset];
290 | let mut new_bytes = Vec::new();
291 | new_bytes.extend_from_slice(&felt_bytes[..offset]);
292 | new_bytes.extend(std::iter::repeat(val).take(amount));
293 | new_bytes.extend_from_slice(&felt_bytes[offset..]);
294 |
295 | // Ensure the length is exactly 32 bytes
296 | if new_bytes.len() > 32 {
297 | new_bytes.truncate(32);
298 | } else if new_bytes.len() < 32 {
299 | new_bytes.resize(32, 0);
300 | }
301 |
302 | let mut array = [0u8; 32];
303 | array.copy_from_slice(&new_bytes);
304 |
305 | Felt::from_bytes_be(&array)
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/mutator/magic_values.rs:
--------------------------------------------------------------------------------
1 | //! A file containing a bunch of magic values, from honggfuzz
2 | /*
3 | *
4 | * Authors:
5 | * Robert Swiecki
6 | * Brandon Falk
7 | *
8 | * Copyright 2010-2018 by Google Inc. All Rights Reserved.
9 | * Copyright 2020 by Brandon Falk
10 | *
11 | * Licensed under the Apache License, Version 2.0 (the "License"); you may
12 | * not use this file except in compliance with the License. You may obtain
13 | * a copy of the License at
14 | *
15 | * http://www.apache.org/licenses/LICENSE-2.0
16 | *
17 | * Unless required by applicable law or agreed to in writing, software
18 | * distributed under the License is distributed on an "AS IS" BASIS,
19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
20 | * implied. See the License for the specific language governing
21 | * permissions and limitations under the License.
22 | *
23 | */
24 |
25 | /// Magic values of various sizes and endiannesses
26 | pub const MAGIC_VALUES: &[&[u8]] = &[
27 | b"\x00",
28 | b"\x01",
29 | b"\x02",
30 | b"\x03",
31 | b"\x04",
32 | b"\x05",
33 | b"\x06",
34 | b"\x07",
35 | b"\x08",
36 | b"\x09",
37 | b"\x0a",
38 | b"\x0b",
39 | b"\x0c",
40 | b"\x0d",
41 | b"\x0e",
42 | b"\x0f",
43 | b"\x10",
44 | b"\x20",
45 | b"\x40",
46 | b"\x7e",
47 | b"\x7f",
48 | b"\x80",
49 | b"\x81",
50 | b"\xc0",
51 | b"\xfe",
52 | b"\xff",
53 | b"\x00\x00",
54 | b"\x01\x01",
55 | b"\x80\x80",
56 | b"\xff\xff",
57 | b"\x00\x01",
58 | b"\x00\x02",
59 | b"\x00\x03",
60 | b"\x00\x04",
61 | b"\x00\x05",
62 | b"\x00\x06",
63 | b"\x00\x07",
64 | b"\x00\x08",
65 | b"\x00\x09",
66 | b"\x00\x0a",
67 | b"\x00\x0b",
68 | b"\x00\x0c",
69 | b"\x00\x0d",
70 | b"\x00\x0e",
71 | b"\x00\x0f",
72 | b"\x00\x10",
73 | b"\x00\x20",
74 | b"\x00\x40",
75 | b"\x00\x7e",
76 | b"\x00\x7f",
77 | b"\x00\x80",
78 | b"\x00\x81",
79 | b"\x00\xc0",
80 | b"\x00\xfe",
81 | b"\x00\xff",
82 | b"\x7e\xff",
83 | b"\x7f\xff",
84 | b"\x80\x00",
85 | b"\x80\x01",
86 | b"\xff\xfe",
87 | b"\x00\x00",
88 | b"\x01\x00",
89 | b"\x02\x00",
90 | b"\x03\x00",
91 | b"\x04\x00",
92 | b"\x05\x00",
93 | b"\x06\x00",
94 | b"\x07\x00",
95 | b"\x08\x00",
96 | b"\x09\x00",
97 | b"\x0a\x00",
98 | b"\x0b\x00",
99 | b"\x0c\x00",
100 | b"\x0d\x00",
101 | b"\x0e\x00",
102 | b"\x0f\x00",
103 | b"\x10\x00",
104 | b"\x20\x00",
105 | b"\x40\x00",
106 | b"\x7e\x00",
107 | b"\x7f\x00",
108 | b"\x80\x00",
109 | b"\x81\x00",
110 | b"\xc0\x00",
111 | b"\xfe\x00",
112 | b"\xff\x00",
113 | b"\xff\x7e",
114 | b"\xff\x7f",
115 | b"\x00\x80",
116 | b"\x01\x80",
117 | b"\xfe\xff",
118 | b"\x00\x00\x00\x00",
119 | b"\x01\x01\x01\x01",
120 | b"\x80\x80\x80\x80",
121 | b"\xff\xff\xff\xff",
122 | b"\x00\x00\x00\x01",
123 | b"\x00\x00\x00\x02",
124 | b"\x00\x00\x00\x03",
125 | b"\x00\x00\x00\x04",
126 | b"\x00\x00\x00\x05",
127 | b"\x00\x00\x00\x06",
128 | b"\x00\x00\x00\x07",
129 | b"\x00\x00\x00\x08",
130 | b"\x00\x00\x00\x09",
131 | b"\x00\x00\x00\x0a",
132 | b"\x00\x00\x00\x0b",
133 | b"\x00\x00\x00\x0c",
134 | b"\x00\x00\x00\x0d",
135 | b"\x00\x00\x00\x0e",
136 | b"\x00\x00\x00\x0f",
137 | b"\x00\x00\x00\x10",
138 | b"\x00\x00\x00\x20",
139 | b"\x00\x00\x00\x40",
140 | b"\x00\x00\x00\x7e",
141 | b"\x00\x00\x00\x7f",
142 | b"\x00\x00\x00\x80",
143 | b"\x00\x00\x00\x81",
144 | b"\x00\x00\x00\xc0",
145 | b"\x00\x00\x00\xfe",
146 | b"\x00\x00\x00\xff",
147 | b"\x7e\xff\xff\xff",
148 | b"\x7f\xff\xff\xff",
149 | b"\x80\x00\x00\x00",
150 | b"\x80\x00\x00\x01",
151 | b"\xff\xff\xff\xfe",
152 | b"\x00\x00\x00\x00",
153 | b"\x01\x00\x00\x00",
154 | b"\x02\x00\x00\x00",
155 | b"\x03\x00\x00\x00",
156 | b"\x04\x00\x00\x00",
157 | b"\x05\x00\x00\x00",
158 | b"\x06\x00\x00\x00",
159 | b"\x07\x00\x00\x00",
160 | b"\x08\x00\x00\x00",
161 | b"\x09\x00\x00\x00",
162 | b"\x0a\x00\x00\x00",
163 | b"\x0b\x00\x00\x00",
164 | b"\x0c\x00\x00\x00",
165 | b"\x0d\x00\x00\x00",
166 | b"\x0e\x00\x00\x00",
167 | b"\x0f\x00\x00\x00",
168 | b"\x10\x00\x00\x00",
169 | b"\x20\x00\x00\x00",
170 | b"\x40\x00\x00\x00",
171 | b"\x7e\x00\x00\x00",
172 | b"\x7f\x00\x00\x00",
173 | b"\x80\x00\x00\x00",
174 | b"\x81\x00\x00\x00",
175 | b"\xc0\x00\x00\x00",
176 | b"\xfe\x00\x00\x00",
177 | b"\xff\x00\x00\x00",
178 | b"\xff\xff\xff\x7e",
179 | b"\xff\xff\xff\x7f",
180 | b"\x00\x00\x00\x80",
181 | b"\x01\x00\x00\x80",
182 | b"\xfe\xff\xff\xff",
183 | b"\x00\x00\x00\x00\x00\x00\x00\x00",
184 | b"\x01\x01\x01\x01\x01\x01\x01\x01",
185 | b"\x80\x80\x80\x80\x80\x80\x80\x80",
186 | b"\xff\xff\xff\xff\xff\xff\xff\xff",
187 | b"\x00\x00\x00\x00\x00\x00\x00\x01",
188 | b"\x00\x00\x00\x00\x00\x00\x00\x02",
189 | b"\x00\x00\x00\x00\x00\x00\x00\x03",
190 | b"\x00\x00\x00\x00\x00\x00\x00\x04",
191 | b"\x00\x00\x00\x00\x00\x00\x00\x05",
192 | b"\x00\x00\x00\x00\x00\x00\x00\x06",
193 | b"\x00\x00\x00\x00\x00\x00\x00\x07",
194 | b"\x00\x00\x00\x00\x00\x00\x00\x08",
195 | b"\x00\x00\x00\x00\x00\x00\x00\x09",
196 | b"\x00\x00\x00\x00\x00\x00\x00\x0a",
197 | b"\x00\x00\x00\x00\x00\x00\x00\x0b",
198 | b"\x00\x00\x00\x00\x00\x00\x00\x0c",
199 | b"\x00\x00\x00\x00\x00\x00\x00\x0d",
200 | b"\x00\x00\x00\x00\x00\x00\x00\x0e",
201 | b"\x00\x00\x00\x00\x00\x00\x00\x0f",
202 | b"\x00\x00\x00\x00\x00\x00\x00\x10",
203 | b"\x00\x00\x00\x00\x00\x00\x00\x20",
204 | b"\x00\x00\x00\x00\x00\x00\x00\x40",
205 | b"\x00\x00\x00\x00\x00\x00\x00\x7e",
206 | b"\x00\x00\x00\x00\x00\x00\x00\x7f",
207 | b"\x00\x00\x00\x00\x00\x00\x00\x80",
208 | b"\x00\x00\x00\x00\x00\x00\x00\x81",
209 | b"\x00\x00\x00\x00\x00\x00\x00\xc0",
210 | b"\x00\x00\x00\x00\x00\x00\x00\xfe",
211 | b"\x00\x00\x00\x00\x00\x00\x00\xff",
212 | b"\x7e\xff\xff\xff\xff\xff\xff\xff",
213 | b"\x7f\xff\xff\xff\xff\xff\xff\xff",
214 | b"\x80\x00\x00\x00\x00\x00\x00\x00",
215 | b"\x80\x00\x00\x00\x00\x00\x00\x01",
216 | b"\xff\xff\xff\xff\xff\xff\xff\xfe",
217 | b"\x00\x00\x00\x00\x00\x00\x00\x00",
218 | b"\x01\x00\x00\x00\x00\x00\x00\x00",
219 | b"\x02\x00\x00\x00\x00\x00\x00\x00",
220 | b"\x03\x00\x00\x00\x00\x00\x00\x00",
221 | b"\x04\x00\x00\x00\x00\x00\x00\x00",
222 | b"\x05\x00\x00\x00\x00\x00\x00\x00",
223 | b"\x06\x00\x00\x00\x00\x00\x00\x00",
224 | b"\x07\x00\x00\x00\x00\x00\x00\x00",
225 | b"\x08\x00\x00\x00\x00\x00\x00\x00",
226 | b"\x09\x00\x00\x00\x00\x00\x00\x00",
227 | b"\x0a\x00\x00\x00\x00\x00\x00\x00",
228 | b"\x0b\x00\x00\x00\x00\x00\x00\x00",
229 | b"\x0c\x00\x00\x00\x00\x00\x00\x00",
230 | b"\x0d\x00\x00\x00\x00\x00\x00\x00",
231 | b"\x0e\x00\x00\x00\x00\x00\x00\x00",
232 | b"\x0f\x00\x00\x00\x00\x00\x00\x00",
233 | b"\x10\x00\x00\x00\x00\x00\x00\x00",
234 | b"\x20\x00\x00\x00\x00\x00\x00\x00",
235 | b"\x40\x00\x00\x00\x00\x00\x00\x00",
236 | b"\x7e\x00\x00\x00\x00\x00\x00\x00",
237 | b"\x7f\x00\x00\x00\x00\x00\x00\x00",
238 | b"\x80\x00\x00\x00\x00\x00\x00\x00",
239 | b"\x81\x00\x00\x00\x00\x00\x00\x00",
240 | b"\xc0\x00\x00\x00\x00\x00\x00\x00",
241 | b"\xfe\x00\x00\x00\x00\x00\x00\x00",
242 | b"\xff\x00\x00\x00\x00\x00\x00\x00",
243 | b"\xff\xff\xff\xff\xff\xff\xff\x7e",
244 | b"\xff\xff\xff\xff\xff\xff\xff\x7f",
245 | b"\x00\x00\x00\x00\x00\x00\x00\x80",
246 | b"\x01\x00\x00\x00\x00\x00\x00\x80",
247 | b"\xfe\xff\xff\xff\xff\xff\xff\xff",
248 | ];
249 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/mutator/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod argument_type;
2 | pub mod basic_mutator;
3 | pub mod magic_values;
4 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/runner/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod runner;
2 | pub mod syscall_handler;
3 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/runner/runner.rs:
--------------------------------------------------------------------------------
1 | use cairo_lang_sierra::ids::FunctionId;
2 | use cairo_lang_sierra::program::Program;
3 | use cairo_native::context::NativeContext;
4 | use cairo_native::execution_result::ContractExecutionResult;
5 | use cairo_native::executor::JitNativeExecutor;
6 | use cairo_native::module::NativeModule;
7 | use starknet_types_core::felt::Felt;
8 |
9 | use crate::runner::syscall_handler::SyscallHandler;
10 |
11 | // Create a JIT Native Executor
12 | pub fn create_executor<'a>(native_program: NativeModule<'a>) -> JitNativeExecutor<'a> {
13 | JitNativeExecutor::from_native_module(native_program, Default::default())
14 | .expect("Failed to create JIT native executor from the provided native module")
15 | }
16 |
17 | /// Compile a Sierra program into a MLIR module
18 | pub fn compile_sierra_program<'a>(
19 | native_context: &'a NativeContext,
20 | sierra_program: &'a Program,
21 | ) -> Result, String> {
22 | native_context
23 | .compile(sierra_program, false, Some(Default::default()))
24 | .map_err(|e| e.to_string())
25 | }
26 |
27 | /// Execute a program with arbitraty entrypoint & parameters
28 | pub fn run_program(
29 | executor: &JitNativeExecutor,
30 | entry_point_id: &FunctionId,
31 | params: &Vec,
32 | ) -> Result {
33 | executor
34 | .invoke_contract_dynamic(entry_point_id, params, Some(u64::MAX), SyscallHandler)
35 | .map_err(|e| e.to_string())
36 | }
37 |
--------------------------------------------------------------------------------
/cairo-native-fuzzer/src/runner/syscall_handler.rs:
--------------------------------------------------------------------------------
1 | // Source : https://github.com/lambdaclass/cairo_native/blob/2bad480b4f59cd047626b9b5697eb90fa723ef07/examples/erc20.rs
2 |
3 | use cairo_native::starknet::BlockInfo;
4 | use cairo_native::starknet::ExecutionInfo;
5 | use cairo_native::starknet::ExecutionInfoV2;
6 | use cairo_native::starknet::ResourceBounds;
7 | use cairo_native::starknet::Secp256k1Point;
8 | use cairo_native::starknet::Secp256r1Point;
9 | use cairo_native::starknet::StarknetSyscallHandler;
10 | use cairo_native::starknet::SyscallResult;
11 | use cairo_native::starknet::TxInfo;
12 | use cairo_native::starknet::TxV2Info;
13 | use cairo_native::starknet::U256;
14 | use starknet_types_core::felt::Felt;
15 |
16 | #[derive(Debug)]
17 | pub struct SyscallHandler;
18 |
19 | impl StarknetSyscallHandler for SyscallHandler {
20 | fn get_block_hash(&mut self, block_number: u64, _gas: &mut u64) -> SyscallResult {
21 | println!("Called `get_block_hash({block_number})` from MLIR.");
22 | Ok(Felt::from_bytes_be_slice(b"get_block_hash ok"))
23 | }
24 |
25 | fn get_execution_info(
26 | &mut self,
27 | _gas: &mut u64,
28 | ) -> SyscallResult {
29 | println!("Called `get_execution_info()` from MLIR.");
30 | Ok(ExecutionInfo {
31 | block_info: BlockInfo {
32 | block_number: 1234,
33 | block_timestamp: 2345,
34 | sequencer_address: 3456.into(),
35 | },
36 | tx_info: TxInfo {
37 | version: 4567.into(),
38 | account_contract_address: 5678.into(),
39 | max_fee: 6789,
40 | signature: vec![1248.into(), 2486.into()],
41 | transaction_hash: 9876.into(),
42 | chain_id: 8765.into(),
43 | nonce: 7654.into(),
44 | },
45 | caller_address: 6543.into(),
46 | contract_address: 5432.into(),
47 | entry_point_selector: 4321.into(),
48 | })
49 | }
50 |
51 | fn get_execution_info_v2(
52 | &mut self,
53 | _remaining_gas: &mut u64,
54 | ) -> SyscallResult {
55 | println!("Called `get_execution_info_v2()` from MLIR.");
56 | Ok(ExecutionInfoV2 {
57 | block_info: BlockInfo {
58 | block_number: 1234,
59 | block_timestamp: 2345,
60 | sequencer_address: 3456.into(),
61 | },
62 | tx_info: TxV2Info {
63 | version: 1.into(),
64 | account_contract_address: 1.into(),
65 | max_fee: 0,
66 | signature: vec![1.into()],
67 | transaction_hash: 1.into(),
68 | chain_id: 1.into(),
69 | nonce: 1.into(),
70 | tip: 1,
71 | paymaster_data: vec![1.into()],
72 | nonce_data_availability_mode: 0,
73 | fee_data_availability_mode: 0,
74 | account_deployment_data: vec![1.into()],
75 | resource_bounds: vec![ResourceBounds {
76 | resource: 2.into(),
77 | max_amount: 10,
78 | max_price_per_unit: 20,
79 | }],
80 | },
81 | caller_address: 6543.into(),
82 | contract_address: 5432.into(),
83 | entry_point_selector: 4321.into(),
84 | })
85 | }
86 |
87 | fn deploy(
88 | &mut self,
89 | class_hash: Felt,
90 | contract_address_salt: Felt,
91 | calldata: &[Felt],
92 | deploy_from_zero: bool,
93 | _gas: &mut u64,
94 | ) -> SyscallResult<(Felt, Vec)> {
95 | println!("Called `deploy({class_hash}, {contract_address_salt}, {calldata:?}, {deploy_from_zero})` from MLIR.");
96 | Ok((
97 | class_hash + contract_address_salt,
98 | calldata.iter().map(|x| x + Felt::ONE).collect(),
99 | ))
100 | }
101 |
102 | fn replace_class(&mut self, class_hash: Felt, _gas: &mut u64) -> SyscallResult<()> {
103 | println!("Called `replace_class({class_hash})` from MLIR.");
104 | Ok(())
105 | }
106 |
107 | fn library_call(
108 | &mut self,
109 | class_hash: Felt,
110 | function_selector: Felt,
111 | calldata: &[Felt],
112 | _gas: &mut u64,
113 | ) -> SyscallResult> {
114 | println!(
115 | "Called `library_call({class_hash}, {function_selector}, {calldata:?})` from MLIR."
116 | );
117 | Ok(calldata.iter().map(|x| x * Felt::from(3)).collect())
118 | }
119 |
120 | fn call_contract(
121 | &mut self,
122 | address: Felt,
123 | entry_point_selector: Felt,
124 | calldata: &[Felt],
125 | _gas: &mut u64,
126 | ) -> SyscallResult> {
127 | println!(
128 | "Called `call_contract({address}, {entry_point_selector}, {calldata:?})` from MLIR."
129 | );
130 | Ok(calldata.iter().map(|x| x * Felt::from(3)).collect())
131 | }
132 |
133 | fn storage_read(
134 | &mut self,
135 | address_domain: u32,
136 | address: Felt,
137 | _gas: &mut u64,
138 | ) -> SyscallResult {
139 | println!("Called `storage_read({address_domain}, {address})` from MLIR.");
140 | Ok(address * Felt::from(3))
141 | }
142 |
143 | fn storage_write(
144 | &mut self,
145 | address_domain: u32,
146 | address: Felt,
147 | value: Felt,
148 | _gas: &mut u64,
149 | ) -> SyscallResult<()> {
150 | println!("Called `storage_write({address_domain}, {address}, {value})` from MLIR.");
151 | Ok(())
152 | }
153 |
154 | fn emit_event(&mut self, keys: &[Felt], data: &[Felt], _gas: &mut u64) -> SyscallResult<()> {
155 | println!("Called `emit_event({keys:?}, {data:?})` from MLIR.");
156 | Ok(())
157 | }
158 |
159 | fn send_message_to_l1(
160 | &mut self,
161 | to_address: Felt,
162 | payload: &[Felt],
163 | _gas: &mut u64,
164 | ) -> SyscallResult<()> {
165 | println!("Called `send_message_to_l1({to_address}, {payload:?})` from MLIR.");
166 | Ok(())
167 | }
168 |
169 | fn keccak(
170 | &mut self,
171 | input: &[u64],
172 | _gas: &mut u64,
173 | ) -> SyscallResult {
174 | println!("Called `keccak({input:?})` from MLIR.");
175 | Ok(U256 {
176 | hi: 0,
177 | lo: 1234567890,
178 | })
179 | }
180 |
181 | fn secp256k1_new(
182 | &mut self,
183 | _x: U256,
184 | _y: U256,
185 | _remaining_gas: &mut u64,
186 | ) -> SyscallResult