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
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
[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-sierra-to-casm = "2.6.0-rc.0"
cairo-lang-sierra = "2.6.0-rc.0"
cairo-lang-starknet-classes = "2.6.0-rc.0"
serde_json = "1.0.108"
serde = "1.0.193"
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.6.0-rc.0", features = ["serde"] }

[[bin]]
name = "universal-sierra-compiler"
Expand Down
71 changes: 71 additions & 0 deletions src/commands/compile_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use anyhow::{Context, Result};
use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
use cairo_lang_starknet_classes::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, which should have
/// `sierra_program` and `entry_points_by_type` fields
#[arg(short, long)]
pub sierra_path: PathBuf,

/// Path to where casm json file will be saved.
/// It will be serialized cairo_lang_starknet::casm_contract_class::CasmContractClass
#[arg(short, long)]
pub output_path: Option<PathBuf>,
}

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..=5, ..] => {
let sierra_class: ContractClass = serde_json::from_value(sierra_json.clone()).unwrap();
let casm_class =
CasmContractClass::from_contract_class(sierra_class, true, usize::MAX).unwrap();
Ok(serde_json::to_value(casm_class)?)
}
[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::compiler::{CairoProgramDebugInfo, SierraToCasmConfig};
use cairo_lang_sierra_to_casm::metadata::{calc_metadata, MetadataComputationConfig};
use clap::Args;
use serde_json::{json, Value};
use std::path::PathBuf;

#[derive(Args)]
pub struct CompileRaw {
/// Path to the sierra program json file, which should have
/// `type_declarations`, `libfunc_declarations`, `statements` and `funcs` fields
#[arg(short, long)]
pub sierra_path: PathBuf,

/// Path to where compilation result json file will be saved.
/// It will consist of `assembled_cairo_program` and `debug_info` fields
#[arg(short, long)]
war-in marked this conversation as resolved.
Show resolved Hide resolved
pub output_path: Option<PathBuf>,
}

pub fn compile(sierra_program: Value) -> Result<Value> {
let sierra_program: Program = serde_json::from_value(sierra_program)
.context("Unable to deserialize Sierra program. Make sure it is in a correct format")?;
let metadata_config = MetadataComputationConfig::default();
let metadata = calc_metadata(&sierra_program, metadata_config)?;

let cairo_program = cairo_lang_sierra_to_casm::compiler::compile(
&sierra_program,
&metadata,
SierraToCasmConfig {
gas_usage_check: true,
max_bytecode_size: usize::MAX,
},
)?;
let assembled_cairo_program = cairo_program.assemble();

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(serialize_cairo_program_debug_info(&cairo_program.debug_info))?
}))
}

fn serialize_cairo_program_debug_info(debug_info: &CairoProgramDebugInfo) -> Vec<(usize, usize)> {
debug_info
.sierra_statement_info
.iter()
.map(|statement_debug_info| {
(
statement_debug_info.code_offset,
statement_debug_info.instruction_idx,
)
})
.collect()
}
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;
59 changes: 1 addition & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1 @@
use anyhow::{Context, Result};
use serde_json::Value;

use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass;
use cairo_lang_starknet_classes::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..=5, ..] => {
let sierra_class: ContractClass = serde_json::from_value(sierra_json.clone()).unwrap();
let casm_class =
CasmContractClass::from_contract_class(sierra_class, true, usize::MAX).unwrap();
Ok(serde_json::to_value(casm_class)?)
}
[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;
72 changes: 49 additions & 23 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,76 @@
use anyhow::{Context, Error, Result};
use clap::Parser;
use clap::{Parser, Subcommand};
use console::style;
use serde_json::to_writer;
use serde_json::{to_writer, Value};
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 {
// Compile sierra of the contract
CompileContract(CompileContract),

// Compile sierra program (cairo_lang_sierra::program::Program)
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) {
let error_tag = style("ERROR").red();
println!("[{error_tag}] {error}");
}

fn main_execution() -> Result<bool> {
let args = Args::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")?;
fn read_json(file_path: PathBuf) -> Result<Value> {
let sierra_file = File::open(file_path).context("Unable to open json file")?;

let casm_json = compile(sierra_json)?;
serde_json::from_reader(sierra_file).context("Unable to read json file")
}

match args.casm_output_path {
fn output_casm(output_json: &Value, output_file_path: Option<PathBuf>) -> Result<()> {
match output_file_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, &output_json).context("Unable to save casm json file")?;
}
None => {
println!("{}", serde_json::to_string(&casm_json)?);
println!("{}", serde_json::to_string(&output_json)?);
}
};

Ok(())
}

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

match cli.command {
war-in marked this conversation as resolved.
Show resolved Hide resolved
Commands::CompileContract(compile_contract) => {
let sierra_json = read_json(compile_contract.sierra_path)?;

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

output_casm(&casm_json, compile_contract.output_path)?;
}
Commands::CompileRaw(compile_raw) => {
let sierra_json = read_json(compile_raw.sierra_path)?;

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

output_casm(&cairo_program_json, compile_raw.output_path)?;
}
}

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.

1 change: 1 addition & 0 deletions tests/data/sierra_raw/sierra_1_5_0.json

Large diffs are not rendered by default.

Loading
Loading