Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiling raw sierra #20

Merged
merged 14 commits into from
Feb 9, 2024
366 changes: 206 additions & 160 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
[package]
name = "universal-sierra-compiler"
description = "Universal-Sierra-Compiler is the tool for Sierra compilation. It compiles any ever-existing Sierra version to CASM."
homepage = "https://github.com/software-mansion/universal-sierra-compiler"
documentation = "https://github.com/software-mansion/universal-sierra-compiler/blob/master/README.md"
readme = "README.md"
repository = "https://github.com/software-mansion/universal-sierra-compiler"
version = "1.0.0"
edition = "2021"
license = "MIT"


[dependencies]
cairo-lang-starknet-sierra-0_1_0 = {package = "cairo-lang-starknet", git = "https://github.com/starkware-libs/cairo.git", tag = "v1.0.0-alpha.6"}
cairo-lang-starknet-sierra-1_0_0 = { package = "cairo-lang-starknet", git = "https://github.com/starkware-libs/cairo", tag = "v1.0.0-rc0" }
cairo-lang-starknet = "=2.4.0"
cairo-lang-starknet = "2.5.3"
cairo-lang-sierra-to-casm = "2.5.3"
cairo-lang-sierra = "2.5.3"
serde_json = "1.0.108"
serde = "1.0.193"
clap = "4.4.11"
Expand All @@ -20,6 +28,8 @@ tempfile = "3.8.1"
indoc = "2.0.4"
fs_extra = "1.3.0"
test-case = "3.3.1"
num-bigint = "0.4.4"
cairo-lang-casm = { version = "2.5.3", features = ["serde"] }

[[bin]]
name = "universal-sierra-compiler"
Expand Down
65 changes: 65 additions & 0 deletions src/commands/compile_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use anyhow::{Context, Result};
use cairo_lang_starknet::casm_contract_class::CasmContractClass;
use cairo_lang_starknet::contract_class::ContractClass;
use cairo_lang_starknet_sierra_0_1_0::casm_contract_class::CasmContractClass as CasmContractClassSierraV0;
use cairo_lang_starknet_sierra_0_1_0::contract_class::ContractClass as ContractClassSierraV0;
use cairo_lang_starknet_sierra_1_0_0::casm_contract_class::CasmContractClass as CasmContractClassSierraV1;
use cairo_lang_starknet_sierra_1_0_0::contract_class::ContractClass as ContractClassSierraV1;
use clap::Args;
use serde_json::Value;
use std::path::PathBuf;

#[derive(Args)]
pub struct CompileContract {
/// Path to the sierra json file
#[arg(short, long)]
pub sierra_input_path: PathBuf,

/// Path to where casm json file will be saved
#[arg(short, long)]
pub casm_output_path: Option<PathBuf>,
}

/// `sierra_json` should be a json containing `sierra_program` and `entry_points_by_type`
pub fn compile(mut sierra_json: Value) -> Result<Value> {
sierra_json["abi"] = Value::Null;
sierra_json["sierra_program_debug_info"] = Value::Null;
sierra_json["contract_class_version"] = Value::String(String::new());

macro_rules! compile_contract {
($sierra_type:ty, $casm_type:ty) => {{
let sierra_class = serde_json::from_value::<$sierra_type>(sierra_json.clone()).unwrap();
let casm_class = <$casm_type>::from_contract_class(sierra_class, true).unwrap();
return Ok(serde_json::to_value(&casm_class)?);
}};
}

let sierra_version = parse_sierra_version(&sierra_json)?;
match sierra_version.as_slice() {
[1, 2..=4, ..] => compile_contract!(ContractClass, CasmContractClass),
[1, 0..=1, 0] => compile_contract!(ContractClassSierraV1, CasmContractClassSierraV1),
[0, ..] => compile_contract!(ContractClassSierraV0, CasmContractClassSierraV0),
_ => {
anyhow::bail!(
"Unable to compile Sierra to Casm. No matching ContractClass or CasmContractClass found for version "
.to_string() + &sierra_version.iter().map(|&num| num.to_string()).collect::<Vec<String>>().join("."),
)
}
}
}

/// Extracts sierra version from the program
/// It will not be possible to convert sierra 0.1.0 version because it keeps its version only in the first felt252
/// (as a shortstring) while other versions keep it on the first 3 (major, minor, patch)
/// That's why it fallbacks to 0 when converting from Value to u8
fn parse_sierra_version(sierra_json: &Value) -> Result<Vec<u8>> {
let parsed_values: Vec<u8> = sierra_json["sierra_program"]
.as_array()
.context("Unable to read sierra_program. Make sure it is an array of felts")?
.iter()
.take(3)
.map(|x| u8::from_str_radix(&x.as_str().unwrap()[2..], 16).unwrap_or_default())
.collect();

Ok(parsed_values)
}
58 changes: 58 additions & 0 deletions src/commands/compile_raw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anyhow::{Context, Result};
use cairo_lang_sierra::program::Program;
use cairo_lang_sierra_to_casm::metadata::{calc_metadata, MetadataComputationConfig};
use clap::Args;
use serde::Deserialize;
use serde_json::{json, Value};
use std::path::PathBuf;

#[derive(Args)]
pub struct CompileRaw {
/// Path to the sierra json file
#[arg(short, long)]
pub sierra_input_path: PathBuf,
war-in marked this conversation as resolved.
Show resolved Hide resolved

/// Path to where casm json file will be saved
#[arg(short, long)]
war-in marked this conversation as resolved.
Show resolved Hide resolved
pub cairo_program_output_path: Option<PathBuf>,
war-in marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Deserialize)]
pub struct SierraArtifact {
sierra_program: Program,
}

// `sierra_artifact` should be a json containing `sierra_program`
pub fn compile(sierra_artifact: Value) -> Result<Value> {
let sierra_artifact: SierraArtifact = serde_json::from_value(sierra_artifact)
.context("Unable to deserialize Sierra. Make sure it is in a correct format")?;
war-in marked this conversation as resolved.
Show resolved Hide resolved
let metadata_config = MetadataComputationConfig::default();
let metadata = calc_metadata(&sierra_artifact.sierra_program, metadata_config)?;

let cairo_program = cairo_lang_sierra_to_casm::compiler::compile(
&sierra_artifact.sierra_program,
&metadata,
true,
)?;
let assembled_cairo_program = cairo_program.assemble();

let debug_info: Vec<(usize, usize)> = cairo_program
.debug_info
.sierra_statement_info
.iter()
.map(|statement_debug_info| {
(
statement_debug_info.code_offset,
statement_debug_info.instruction_idx,
)
})
.collect();
war-in marked this conversation as resolved.
Show resolved Hide resolved

Ok(json!({
"assembled_cairo_program": {
"bytecode": serde_json::to_value(assembled_cairo_program.bytecode)?,
"hints": serde_json::to_value(assembled_cairo_program.hints)?
},
"debug_info": serde_json::to_value(debug_info)?
}))
}
2 changes: 2 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod compile_contract;
pub mod compile_raw;
54 changes: 1 addition & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1 @@
use anyhow::{Context, Result};
use serde_json::Value;

use cairo_lang_starknet::casm_contract_class::CasmContractClass;
use cairo_lang_starknet::contract_class::ContractClass;
use cairo_lang_starknet_sierra_0_1_0::casm_contract_class::CasmContractClass as CasmContractClassSierraV0;
use cairo_lang_starknet_sierra_0_1_0::contract_class::ContractClass as ContractClassSierraV0;
use cairo_lang_starknet_sierra_1_0_0::casm_contract_class::CasmContractClass as CasmContractClassSierraV1;
use cairo_lang_starknet_sierra_1_0_0::contract_class::ContractClass as ContractClassSierraV1;

/// `sierra_json` should be a json containing `sierra_program` and `entry_points_by_type`
pub fn compile(mut sierra_json: Value) -> Result<Value> {
sierra_json["abi"] = Value::Null;
sierra_json["sierra_program_debug_info"] = Value::Null;
sierra_json["contract_class_version"] = Value::String(String::new());

macro_rules! compile_contract {
($sierra_type:ty, $casm_type:ty) => {{
let sierra_class = serde_json::from_value::<$sierra_type>(sierra_json.clone()).unwrap();
let casm_class = <$casm_type>::from_contract_class(sierra_class, true).unwrap();
return Ok(serde_json::to_value(&casm_class)?);
}};
}

let sierra_version = parse_sierra_version(&sierra_json)?;
match sierra_version.as_slice() {
[1, 2..=4, ..] => compile_contract!(ContractClass, CasmContractClass),
[1, 0..=1, 0] => compile_contract!(ContractClassSierraV1, CasmContractClassSierraV1),
[0, ..] => compile_contract!(ContractClassSierraV0, CasmContractClassSierraV0),
_ => {
anyhow::bail!(
"Unable to compile Sierra to Casm. No matching ContractClass or CasmContractClass found for version "
.to_string() + &sierra_version.iter().map(|&num| num.to_string()).collect::<Vec<String>>().join("."),
)
}
}
}

/// Extracts sierra version from the program
/// It will not be possible to convert sierra 0.1.0 version because it keeps its version only in the first felt252
/// (as a shortstring) while other versions keep it on the first 3 (major, minor, patch)
/// That's why it fallbacks to 0 when converting from Value to u8
fn parse_sierra_version(sierra_json: &Value) -> Result<Vec<u8>> {
let parsed_values: Vec<u8> = sierra_json["sierra_program"]
.as_array()
.context("Unable to read sierra_program. Make sure it is an array of felts")?
.iter()
.take(3)
.map(|x| u8::from_str_radix(&x.as_str().unwrap()[2..], 16).unwrap_or_default())
.collect();

Ok(parsed_values)
}
pub mod commands;
79 changes: 53 additions & 26 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
use anyhow::{Context, Error, Result};
use clap::Parser;
use clap::{Parser, Subcommand};
use console::style;
use serde_json::to_writer;
use std::fs::File;
use std::path::PathBuf;
use universal_sierra_compiler::compile;
use universal_sierra_compiler::commands;
use universal_sierra_compiler::commands::compile_contract::CompileContract;
use universal_sierra_compiler::commands::compile_raw::CompileRaw;

#[derive(Parser, Debug)]
#[derive(Parser)]
#[command(version)]
struct Args {
/// Path to the sierra json file
#[arg(short, long)]
sierra_input_path: PathBuf,

/// Path to where casm json file will be saved
#[arg(short, long)]
casm_output_path: Option<PathBuf>,
struct Cli {
#[command(subcommand)]
command: Commands,
}

#[derive(Subcommand)]
enum Commands {
CompileContract(CompileContract),
CompileRaw(CompileRaw),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be CompileProgram, I would consider bytecode or textual sierra a raw sierra

}

fn print_error_message(error: &Error) {
Expand All @@ -24,26 +26,51 @@ fn print_error_message(error: &Error) {
}

fn main_execution() -> Result<bool> {
let args = Args::parse();
let cli = Cli::parse();

let sierra_file =
File::open(args.sierra_input_path).context("Unable to open sierra json file")?;
let sierra_json =
serde_json::from_reader(sierra_file).context("Unable to read sierra json file")?;
match cli.command {
war-in marked this conversation as resolved.
Show resolved Hide resolved
Commands::CompileContract(compile_contract) => {
let sierra_file = File::open(compile_contract.sierra_input_path)
.context("Unable to open sierra json file")?;
let sierra_json =
serde_json::from_reader(sierra_file).context("Unable to read sierra json file")?;

let casm_json = compile(sierra_json)?;
let casm_json = commands::compile_contract::compile(sierra_json)?;

match args.casm_output_path {
Some(output_path) => {
let casm_file =
File::create(output_path).context("Unable to open/create casm json file")?;
match compile_contract.casm_output_path {
Some(output_path) => {
let casm_file = File::create(output_path)
.context("Unable to open/create casm json file")?;

to_writer(casm_file, &casm_json).context("Unable to save casm json file")?;
to_writer(casm_file, &casm_json).context("Unable to save casm json file")?;
}
None => {
println!("{}", serde_json::to_string(&casm_json)?);
}
};
}
None => {
println!("{}", serde_json::to_string(&casm_json)?);
Commands::CompileRaw(compile_raw) => {
let sierra_file = File::open(compile_raw.sierra_input_path)
.context("Unable to open sierra artifact json file")?;
let sierra_json = serde_json::from_reader(sierra_file)
.context("Unable to read sierra artifact json file")?;

let cairo_program_json = commands::compile_raw::compile(sierra_json)?;

match compile_raw.cairo_program_output_path {
Some(output_path) => {
let casm_file = File::create(output_path)
.context("Unable to open/create cairo program json file")?;

to_writer(casm_file, &cairo_program_json)
.context("Unable to save cairo program json file")?;
}
None => {
println!("{}", serde_json::to_string(&cairo_program_json)?);
}
};
}
};
}

Ok(true)
}
Expand Down
1 change: 1 addition & 0 deletions tests/data/sierra_raw/sierra_1_4_0.json

Large diffs are not rendered by default.

Loading
Loading