Skip to content

Commit

Permalink
Merge pull request #26 from FuzzingLabs/feat/starknet-foundry-integra…
Browse files Browse the repository at this point in the history
…tion

Scarb projects support
  • Loading branch information
Rog3rSm1th authored Jan 9, 2025
2 parents fc18a24 + fea7769 commit da9b5c3
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 17 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ clap = { version = "4.0.0-rc.1", features = [ "derive" ] }
serde = "1.0.209"
serde_json = "1.0.116"
tokio = "1.37.0"
cairo-lang-sierra = "~2.8.4"
cairo-lang-starknet-classes = "~2.8.4"
cairo-lang-sierra = "~2.9.2"
cairo-lang-starknet-classes = "~2.9.2"
sierra-analyzer-lib = { path = "./lib" }

29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Sierra Analyzer is a security toolkit designed for analyzing Sierra files. It in
- [Use the symbolic execution to generate unit tests](#use-the-symbolic-execution-to-generate-unit-tests)
- [Improve the decompiler output using LLMs](#print-the-contracts-callgraph)
- [Use it as a library](#print-the-contracts-callgraph)
- [Use with a Scarb project](#use-it-with-a-scarb-project)


### Project structure
Expand Down Expand Up @@ -58,10 +59,10 @@ Contracts can be fetched directly from Starknet (Mainnet & Sepolia) by specifyin

```
# Fetch & decompile a contract from starknet mainnet
cargo run -- --remote 0x07c43d18d37d66d7855dab8f21ebf9d554dd213c6307aacecaf2d595a53b3bbb
cargo run -- --remote 0x035ae0fe6ca00fcc8020a6c64503f38bfaf3481ae9a6c8b7daec2f899df735fa
# Fetch & decompile a contract from Sepolia network
cargo run -- --network sepolia --remote 0x068377a89d64c0b16dc97c66933777bf4e9b050652c4fde2c59c8c4d755a163b
cargo run -- --remote 0x01437be408319cdb7524b3e3c52c0e9d80070d8cb85f363d42a7c3c2df5b66b2 --network sepolia -d
```

### Print the contract's Control-Flow Graph
Expand Down Expand Up @@ -141,6 +142,30 @@ The tests generator can also be used [with the library](https://github.com/Fuzzi

It is also possible to use the `sierra-analyzer-lib` library to decompile serialised or unserialised Sierra files.

### Use it with a Scarb project

First you need to build the project using Scarb :

```sh
scarb build
```

Then you can run the sierra-decompiler using the `--scarb` flag :

```sh
// Run the decompiler
sierra-decompiler --scarb

// Run the analyzer
sierra-decompiler --scarb -a

// Generate the control-flow graph
sierra-decompiler --scarb --cfg

// Generate the callgraph
sierra-decompiler --scarb --callgraph
```

### Features

- [x] Decompiler
Expand Down
94 changes: 88 additions & 6 deletions bin/sierra-decompiler/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::fs;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process::exit;

use clap::Parser;
use serde_json;
Expand Down Expand Up @@ -67,15 +68,19 @@ struct Args {
/// Network type (Mainnet & Sepolia are supported)
#[clap(long, default_value = "mainnet")]
network: String,

/// Run sierra-analyzer in a repo that uses Scarb
#[clap(long)]
scarb: bool,
}

#[tokio::main]
async fn main() {
let args = Args::parse();

// Ensure either remote or Sierra file is provided
if args.remote.is_empty() && args.sierra_file.is_none() {
eprintln!("Error: Either remote or Sierra file must be provided");
// Ensure either remote, Sierra file, or scarb is provided
if args.remote.is_empty() && args.sierra_file.is_none() && !args.scarb {
eprintln!("Error: Either remote, Sierra file, or --scarb flag must be provided");
return;
}

Expand Down Expand Up @@ -120,9 +125,11 @@ async fn main() {
}
}

/// Load the Sierra program from either a remote source or a local file
/// Load the Sierra program from either a remote source, a local file, or scarb
async fn load_program(args: &Args) -> Result<SierraProgram, String> {
if !args.remote.is_empty() {
if args.scarb {
load_scarb_program().await
} else if !args.remote.is_empty() {
load_remote_program(args).await
} else {
load_local_program(args)
Expand Down Expand Up @@ -196,10 +203,85 @@ fn load_local_program(args: &Args) -> Result<SierraProgram, String> {
Ok(program)
}

/// Load the Sierra program from the /target directory
async fn load_scarb_program() -> Result<SierraProgram, String> {
let target_dir = Path::new("./target/dev/");

// Read the directory contents
let entries =
fs::read_dir(target_dir).map_err(|e| format!("Failed to read directory: {}", e))?;

// Find the file that ends with "contract_class.json"
let contract_class_file = entries
.filter_map(|entry| {
let entry = entry.ok()?;
let path = entry.path();
if path.is_file()
&& path
.file_name()
.and_then(|name| name.to_str())
.map_or(false, |name| name.ends_with("contract_class.json"))
{
Some(path)
} else {
None
}
})
.next();

// Check if the file was found
let contract_class_file = if let Some(file) = contract_class_file {
file
} else {
eprintln!("You need to run scarb build before running the sierra-analyzer");
exit(1);
};

// Open the file
let mut file =
File::open(&contract_class_file).map_err(|e| format!("Failed to open file: {}", e))?;

// Read the file content into a string
let mut content = String::new();
file.read_to_string(&mut content)
.map_err(|e| format!("Failed to read file: {}", e))?;

// Deserialize the JSON content into a ContractClass
let contract_class: Result<ContractClass, _> = serde_json::from_str(&content);

let program_string = match contract_class {
Ok(ref prog) => {
// Extract the Sierra program from the ContractClass
match prog.extract_sierra_program() {
Ok(prog_sierra) => prog_sierra.to_string(),
Err(e) => {
eprintln!("Error extracting Sierra program: {}", e);
content.clone()
}
}
}
Err(ref _e) => content.clone(),
};

// Initialize a new SierraProgram with the deserialized Sierra program content
let mut program = SierraProgram::new(program_string);

// Set the program ABI if deserialization was successful
if let Ok(ref contract_class) = contract_class {
let abi = contract_class.abi.clone();
program.set_abi(abi.unwrap());
}

Ok(program)
}

/// Get the file stem based on the remote address or the Sierra file
fn get_file_stem(args: &Args) -> String {
if !args.remote.is_empty() {
args.remote.clone()
} else if args.scarb {
// TODO : modify with the program name
"sierra_program".to_string()
} else {
args.sierra_file
.as_ref()
Expand Down
8 changes: 4 additions & 4 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cairo-lang-sierra = "~2.8.4"
cairo-lang-starknet-classes = "~2.8.4"
cairo-lang-starknet = "~2.8.4"
cairo-lang-sierra = "~2.9.2"
cairo-lang-starknet-classes = "~2.9.2"
cairo-lang-starknet = "~2.9.2"
colored = "2.1.0"
graphviz-rust = "0.9.0"
hex = "0.4.3"
Expand All @@ -24,4 +24,4 @@ z3 = "0.12.1"

[dev-dependencies]
serde_json = "1.0.116"
cairo-lang-starknet-classes = "~2.8.4"
cairo-lang-starknet-classes = "~2.9.2"
2 changes: 1 addition & 1 deletion lib/src/decompiler/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ impl<'a> Function<'a> {
.expect("Library function not found in the registry");

if let CoreConcreteLibfunc::FunctionCall(f_called) = lib_func {
let function_name = f_called.function.id.debug_name.as_ref().unwrap();
let function_name = parse_element_name!(f_called.function.id);

for function in functions {
let current_function_name =
Expand Down
4 changes: 2 additions & 2 deletions lib/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ pub struct NetworkConfig;
impl NetworkConfig {
/// URL for the mainnet API
pub const MAINNET_API_URL: &'static str =
"https://starknet-mainnet.public.blastapi.io/rpc/v0_6";
"https://starknet-mainnet.public.blastapi.io/rpc/v0_7";

/// URL for the Sepolia API
pub const SEPOLIA_API_URL: &'static str =
"https://starknet-sepolia.public.blastapi.io/rpc/v0_6";
"https://starknet-sepolia.public.blastapi.io/rpc/v0_7";
}

/// Struct representing an RPC client
Expand Down

0 comments on commit da9b5c3

Please sign in to comment.