Skip to content
This repository has been archived by the owner on Dec 1, 2023. It is now read-only.

Add cairo1 compilers #348

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include LICENSE
recursive-include src/nile/artifacts/ *
recursive-include src/nile/base_project/ *
recursive-include src/nile/core/cairo1/compilers/ *
10 changes: 10 additions & 0 deletions src/nile/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import asyncclick as click

from nile.common import is_alias
from nile.core.cairo1.compile import compile as compile_1_command
from nile.core.call_or_invoke import call_or_invoke as call_or_invoke_command
from nile.core.clean import clean as clean_command
from nile.core.compile import compile as compile_command
Expand Down Expand Up @@ -350,6 +351,15 @@ def compile(
)


@cli.command()
@click.argument("contracts", nargs=-1)
@click.option("--directory")
@enable_stack_trace
def compile_1(ctx, contracts, directory):
"""Compile cairo1 contracts."""
compile_1_command(contracts, directory)


@cli.command()
@enable_stack_trace
def clean(ctx):
Expand Down
2 changes: 1 addition & 1 deletion src/nile/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get_all_contracts(ext=None, directory=None):
ext = ".cairo"

files = list()
for (dirpath, _, filenames) in os.walk(
for dirpath, _, filenames in os.walk(
directory if directory else CONTRACTS_DIRECTORY
):
files += [
Expand Down
7 changes: 7 additions & 0 deletions src/nile/core/cairo1/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Cairo1 common."""
Copy link
Contributor

Choose a reason for hiding this comment

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

i think we can remove this file and just replace variables in good ol' common/ if we drop Cairo0 support


from pathlib import Path

COMPILERS_BIN_PATH = Path(__file__).parent / "./compilers/src/bin"
BUILD_DIRECTORY = "artifacts/cairo1"
ABIS_DIRECTORY = f"{BUILD_DIRECTORY}/abis"
94 changes: 94 additions & 0 deletions src/nile/core/cairo1/compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Command to compile cairo1 files."""
import json
import logging
import os
import subprocess

from nile.common import CONTRACTS_DIRECTORY, get_all_contracts
from nile.core.cairo1.common import ABIS_DIRECTORY, BUILD_DIRECTORY, COMPILERS_BIN_PATH


def compile(
contracts,
directory=None,
):
"""Compile command."""
contracts_directory = directory if directory else CONTRACTS_DIRECTORY

if not os.path.exists(ABIS_DIRECTORY):
logging.info(f"📁 Creating {ABIS_DIRECTORY} to store compilation artifacts")
os.makedirs(ABIS_DIRECTORY, exist_ok=True)

all_contracts = contracts

if len(contracts) == 0:
logging.info(
f"🤖 Compiling all Cairo contracts in the {contracts_directory} directory"
)
all_contracts = get_all_contracts(directory=contracts_directory)

results = []

for contract in all_contracts:
status_code, sierra_file, filename = _compile_to_sierra(
contract, contracts_directory
)
results.append(status_code)
if status_code == 0:
_extract_abi(sierra_file, filename)
_compile_to_casm(sierra_file, filename)

failed_contracts = [c for (c, r) in zip(all_contracts, results) if r != 0]
failures = len(failed_contracts)

if failures == 0:
logging.info("✅ Done")
else:
exp = f"{failures} contract"
if failures > 1:
exp += "s" # pluralize
logging.info(f"🛑 Failed to compile the following {exp}:")
for contract in failed_contracts:
logging.info(f" {contract}")


def _compile_to_sierra(
path,
directory=None,
):
"""Compile from Cairo1 to Sierra."""
base = os.path.basename(path)
filename = os.path.splitext(base)[0]
logging.info(f"🔨 Compiling {path}")

sierra_file = f"{BUILD_DIRECTORY}/{filename}.sierra"

cmd = f"""
{COMPILERS_BIN_PATH}/starknet-compile {path} \
{sierra_file}
"""

process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
process.communicate()

return process.returncode, sierra_file, filename


def _compile_to_casm(sierra_file, filename):
"""Compile from Sierra to Casm."""
cmd = f"""
{COMPILERS_BIN_PATH}/starknet-sierra-compile {sierra_file} \
{BUILD_DIRECTORY}/{filename}.casm
"""

process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE)
process.communicate()
return process.returncode


def _extract_abi(sierra_file, filename):
with open(sierra_file, "r") as f:
data = json.load(f)

with open(f"{ABIS_DIRECTORY}/{filename}.json", "w") as f:
json.dump(data["abi"], f, indent=2)
51 changes: 51 additions & 0 deletions src/nile/core/cairo1/compilers/corelib/array.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
extern type Array<T>;
extern fn array_new<T>() -> Array::<T> nopanic;
extern fn array_append<T>(ref arr: Array::<T>, value: T) nopanic;
extern fn array_pop_front<T>(ref arr: Array::<T>) -> Option::<T> nopanic;
#[panic_with('Array out of bounds', array_at)]
extern fn array_get<T>(
ref arr: Array::<T>, index: usize
) -> Option::<T> implicits(RangeCheck) nopanic;
extern fn array_len<T>(ref arr: Array::<T>) -> usize nopanic;

trait ArrayTrait<T> {
fn new() -> Array::<T>;
fn append(ref self: Array::<T>, value: T);
fn pop_front(ref self: Array::<T>) -> Option::<T>;
fn get(ref self: Array::<T>, index: usize) -> Option::<T>;
fn at(ref self: Array::<T>, index: usize) -> T;
fn len(ref self: Array::<T>) -> usize;
fn is_empty(ref self: Array::<T>) -> bool;
}
impl ArrayImpl<T> of ArrayTrait::<T> {
#[inline(always)]
fn new() -> Array::<T> {
array_new()
}
#[inline(always)]
fn append(ref self: Array::<T>, value: T) {
array_append(ref self, value)
}
#[inline(always)]
fn pop_front(ref self: Array::<T>) -> Option::<T> {
array_pop_front(ref self)
}
#[inline(always)]
fn get(ref self: Array::<T>, index: usize) -> Option::<T> {
array_get(ref self, index)
}
fn at(ref self: Array::<T>, index: usize) -> T {
array_at(ref self, index)
}
#[inline(always)]
fn len(ref self: Array::<T>) -> usize {
array_len(ref self)
}
#[inline(always)]
fn is_empty(ref self: Array::<T>) -> bool {
self.len() == 0_usize
}
}

// Impls for common generic types
impl ArrayFeltDrop of Drop::<Array::<felt>>;
6 changes: 6 additions & 0 deletions src/nile/core/cairo1/compilers/corelib/box.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
extern type Box<T>;
impl BoxFeltCopy of Copy::<Box::<felt>>;
impl BoxFeltDrop of Drop::<Box::<felt>>;

extern fn into_box<T>(value: T) -> Box::<T> nopanic;
extern fn unbox<T>(box: Box::<T>) -> T nopanic;
2 changes: 2 additions & 0 deletions src/nile/core/cairo1/compilers/corelib/cairo_project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[crate_roots]
core = "."
9 changes: 9 additions & 0 deletions src/nile/core/cairo1/compilers/corelib/debug.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use array::ArrayTrait;

extern fn print(message: Array::<felt>) nopanic;

fn print_felt(message: felt) {
let mut arr = ArrayTrait::new();
arr.append(message);
print(arr);
}
32 changes: 32 additions & 0 deletions src/nile/core/cairo1/compilers/corelib/dict.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
extern type DictManager;
extern type DictFeltTo<T>;
extern type SquashedDictFeltTo<T>;
impl SquashedDictFeltToFeltDrop of Drop::<SquashedDictFeltTo::<felt>>;

extern fn dict_felt_to_new<T>() -> DictFeltTo::<T> implicits(DictManager) nopanic;
extern fn dict_felt_to_write<T>(ref dict: DictFeltTo::<T>, key: felt, value: T) nopanic;
extern fn dict_felt_to_read<T>(ref dict: DictFeltTo::<T>, key: felt) -> T nopanic;
extern fn dict_felt_to_squash<T>(
dict: DictFeltTo::<T>
) -> SquashedDictFeltTo::<T> implicits(RangeCheck, GasBuiltin, DictManager) nopanic;

trait DictFeltToTrait<T> {
fn new() -> DictFeltTo::<T>;
fn insert(ref self: DictFeltTo::<T>, key: felt, value: T);
fn get(ref self: DictFeltTo::<T>, key: felt) -> T;
fn squash(self: DictFeltTo::<T>) -> SquashedDictFeltTo::<T>;
}
impl DictFeltToImpl<T> of DictFeltToTrait::<T> {
fn new() -> DictFeltTo::<T> {
dict_felt_to_new()
}
fn insert(ref self: DictFeltTo::<T>, key: felt, value: T) {
dict_felt_to_write(ref self, key, value)
}
fn get(ref self: DictFeltTo::<T>, key: felt) -> T {
dict_felt_to_read(ref self, key)
}
fn squash(self: DictFeltTo::<T>) -> SquashedDictFeltTo::<T> {
dict_felt_to_squash(self)
}
}
145 changes: 145 additions & 0 deletions src/nile/core/cairo1/compilers/corelib/ec.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
mod StarkCurve {
/// The STARK Curve is defined by the equation `y^2 = x^3 + ALPHA*x + BETA`.
const ALPHA: felt = 1;
/// The STARK Curve is defined by the equation `y^2 = x^3 + ALPHA*x + BETA`.
const BETA: felt = 0x6f21413efbe40de150e596d72f7a8c5609ad26c15c915c1f4cdfcb99cee9e89;
/// The order (number of points) of the STARK Curve.
const ORDER: felt = 0x800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f;
/// The x coordinate of the generator point used in the ECDSA signature.
const GEN_X: felt = 0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca;
/// The y coordinate of the generator point used in the ECDSA signature.
const GEN_Y: felt = 0x5668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f;
}

extern type EcOp;
#[derive(Copy, Drop)]
extern type EcPoint;
type NonZeroEcPoint = NonZero::<EcPoint>;

impl NonZeroEcPointCopy of Copy::<NonZeroEcPoint>;
impl OptionNonZeroEcPointCopy of Copy::<Option::<NonZeroEcPoint>>;
impl NonZeroEcPointDrop of Drop::<NonZeroEcPoint>;

/// Returns the zero point of the curve ("the point at infinity").
extern fn ec_point_zero() -> EcPoint nopanic;
/// Constructs a non-zero point from its (x, y) coordinates.
///
/// * `ec_point_try_new_nz` returns `None` if the point (x, y) is not on the curve.
/// * `ec_point_new_nz` panics in that case.
#[panic_with('not on EC', ec_point_new_nz)]
extern fn ec_point_try_new_nz(x: felt, y: felt) -> Option::<NonZeroEcPoint> nopanic;

#[inline(always)]
fn ec_point_try_new(x: felt, y: felt) -> Option::<EcPoint> {
match ec_point_try_new_nz(:x, :y) {
Option::Some(pt) => Option::Some(unwrap_nz(pt)),
Option::None(()) => Option::None(()),
}
}

fn ec_point_new(x: felt, y: felt) -> EcPoint {
unwrap_nz(ec_point_new_nz(:x, :y))
}

extern fn ec_point_from_x_nz(x: felt) -> Option::<NonZeroEcPoint> nopanic;

#[inline(always)]
fn ec_point_from_x(x: felt) -> Option::<EcPoint> {
match ec_point_from_x_nz(:x) {
Option::Some(pt) => Option::Some(unwrap_nz(pt)),
Option::None(()) => Option::None(()),
}
}

extern fn ec_point_unwrap(p: NonZeroEcPoint) -> (felt, felt) nopanic;
/// Computes the negation of an elliptic curve point (-p).
extern fn ec_neg(p: EcPoint) -> EcPoint nopanic;
/// Checks whether the given `EcPoint` is the zero point.
extern fn ec_point_is_zero(p: EcPoint) -> IsZeroResult::<EcPoint> nopanic;

/// Converts `p` to `NonZeroEcPoint`. Panics if `p` is the zero point.
fn ec_point_non_zero(p: EcPoint) -> NonZeroEcPoint {
match ec_point_is_zero(p) {
IsZeroResult::Zero(()) => {
let mut data = array_new();
array_append(ref data, 'Zero point');
panic(data)
},
IsZeroResult::NonZero(p_nz) => p_nz,
}
}

// EC state.

// TODO(lior): Allow explicit clone() for EcState, since we don't allow implicit dup (Copy).
#[derive(Drop)]
extern type EcState;

/// Initializes an EC computation with the zero point.
extern fn ec_state_init() -> EcState nopanic;
/// Adds a point to the computation.
extern fn ec_state_add(ref s: EcState, p: NonZeroEcPoint) nopanic;
/// Finalizes the EC computation and returns the result (returns `None` if the result is the
/// zero point).
extern fn ec_state_try_finalize_nz(s: EcState) -> Option::<NonZeroEcPoint> nopanic;
/// Adds the product p * m to the state.
extern fn ec_state_add_mul(ref s: EcState, m: felt, p: NonZeroEcPoint) implicits(EcOp) nopanic;

/// Finalizes the EC computation and returns the result.
#[inline(always)]
fn ec_state_finalize(s: EcState) -> EcPoint nopanic {
match ec_state_try_finalize_nz(s) {
Option::Some(pt) => unwrap_nz(pt),
Option::None(()) => ec_point_zero(),
}
}

/// Computes the product of an EC point `p` by the given scalar `m`.
fn ec_mul(p: EcPoint, m: felt) -> EcPoint {
match ec_point_is_zero(p) {
IsZeroResult::Zero(()) => p,
IsZeroResult::NonZero(p_nz) => {
let mut state = ec_state_init();
ec_state_add_mul(ref state, m, p_nz);
ec_state_finalize(state)
}
}
}

impl EcPointAdd of Add::<EcPoint> {
/// Computes the sum of two points on the curve.
// TODO(lior): Implement using a libfunc to make it more efficient.
fn add(p: EcPoint, q: EcPoint) -> EcPoint {
let p_nz = match ec_point_is_zero(p) {
IsZeroResult::Zero(()) => {
return q;
},
IsZeroResult::NonZero(pt) => pt,
};
let q_nz = match ec_point_is_zero(q) {
IsZeroResult::Zero(()) => {
return p;
},
IsZeroResult::NonZero(pt) => pt,
};
let mut state = ec_state_init();
ec_state_add(ref state, p_nz);
ec_state_add(ref state, q_nz);
ec_state_finalize(state)
}
}

impl EcPointSub of Sub::<EcPoint> {
/// Computes the difference between two points on the curve.
fn sub(p: EcPoint, q: EcPoint) -> EcPoint {
match ec_point_is_zero(q) {
IsZeroResult::Zero(()) => {
// p - 0 = p.
return p;
},
IsZeroResult::NonZero(_) => {},
};
// p - q = p + (-q).
p + ec_neg(q)
}
}
Loading