Skip to content

Commit

Permalink
Compiling raw sierra (#20)
Browse files Browse the repository at this point in the history
* WIP: Compiling raw sierra

* update the interface

* remove outdated todo

* wip

* update compilation

* add sierra v1.5.0 raw compilation test

* change sierra v1.5.0 raw file

* unify duplicated code

* review suggestions

* remove unnecessary SierraArtifact, apply review suggestions

* aftermerge cleaning

* add command description

---------

Co-authored-by: war-in <marcin.warchol20@gmail.com>
  • Loading branch information
Arcticae and war-in authored Feb 9, 2024
1 parent 415bfc4 commit 59a7b1a
Show file tree
Hide file tree
Showing 23 changed files with 378 additions and 121 deletions.
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)]
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),
}

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 {
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
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
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

0 comments on commit 59a7b1a

Please sign in to comment.