Skip to content

Commit

Permalink
Cairo bindings (#1316)
Browse files Browse the repository at this point in the history
  • Loading branch information
karol-bisztyga authored Feb 13, 2023
1 parent cb109e8 commit 62ffecc
Show file tree
Hide file tree
Showing 17 changed files with 1,929 additions and 1,471 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/builds_on_mac.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ jobs:
poetry export -o requirements.txt
pip install -r requirements.txt
poetry install
- name: Install cairo bindings
run: |
poetry run ./scripts/install_cairo_bindings.sh
- name: Build binary
run: |
poetry run poe build
14 changes: 14 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
python -m pip install --upgrade pip
pip install poetry
poetry install
- name: Install cairo bindings
run: |
poetry run ./scripts/install_cairo_bindings.sh
- name: Populate caches
uses: actions/cache@v3
with:
Expand Down Expand Up @@ -54,6 +57,9 @@ jobs:
path: |
~/.cache/pypoetry
key: poetry-${{ hashFiles('poetry.lock') }}
- name: Install cairo bindings
run: |
poetry run ./scripts/install_cairo_bindings.sh
- name: Check types
run: |
Expand Down Expand Up @@ -81,12 +87,16 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install poetry
shell: bash
- name: Restore caches
uses: actions/cache@v3
with:
path: |
~/.cache/pypoetry
key: poetry-${{ hashFiles('poetry.lock') }}
- name: Install cairo bindings
run: |
poetry run ./scripts/install_cairo_bindings.sh
- name: Patch git config
run: |
Expand Down Expand Up @@ -120,6 +130,10 @@ jobs:
path: |
~/.cache/pypoetry
key: poetry-${{ hashFiles('poetry.lock') }}
- name: Install cairo bindings
run: |
poetry run ./scripts/install_cairo_bindings.sh
shell: bash

- name: Build
run: |
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ curl -L https://raw.githubusercontent.com/software-mansion/protostar/master/inst
1. Upgrade pip: `pip install --upgrade pip`
1. Install project dependencies: `poetry install`
- MacBook M1/M2: `CFLAGS=-I/opt/homebrew/opt/gmp/include LDFLAGS=-L/opt/homebrew/opt/gmp/lib poetry install`
1. Install bindings for the rust tools used by protostar:
- [install rust](https://www.rust-lang.org/tools/install)
- poetry run poe install_cairo_bindings
1. Patch the git's config by always allowing file transport: `git config --global protocol.file.allow always` (needed for some tests to pass)
1. Verify the setup by running tests: `poe test`
1. Build Protostar: `poe build`
Expand Down
2,927 changes: 1,477 additions & 1,450 deletions poetry.lock

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions protostar/cairo/cairo_bindings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from pathlib import Path
from typing import Optional

import cairo_python_bindings


def call_cairo_to_sierra_compiler(
input_path: Path, output_path: Optional[Path] = None
) -> Optional[str]:
return cairo_python_bindings.call_cairo_to_sierra_compiler( # pyright: ignore
str(input_path), str(output_path) if output_path else None
)


def call_sierra_to_casm_compiler(
input_path: Path, output_path: Optional[Path] = None
) -> Optional[str]:
return cairo_python_bindings.call_sierra_to_casm_compiler( # pyright: ignore
str(input_path), str(output_path) if output_path else None
)


def call_cairo_to_casm_compiler(
input_path: Path, output_path: Optional[Path] = None
) -> Optional[str]:
return cairo_python_bindings.call_cairo_to_casm_compiler( # pyright: ignore
str(input_path), str(output_path) if output_path else None
)


def call_starknet_contract_compiler(
input_path: Path, output_path: Optional[Path] = None
) -> Optional[str]:
return cairo_python_bindings.call_starknet_contract_compiler( # pyright: ignore
str(input_path), str(output_path) if output_path else None
)


def call_test_collector(
input_path: Path, output_path: Optional[Path] = None
) -> tuple[Optional[str], list[str]]:
return cairo_python_bindings.call_test_collector( # pyright: ignore
str(input_path), str(output_path) if output_path else None
)


def call_protostar_sierra_to_casm(
named_tests: list[str], input_path: Path, output_path: Optional[Path] = None
) -> Optional[str]:
return cairo_python_bindings.call_protostar_sierra_to_casm( # pyright: ignore
named_tests, str(input_path), str(output_path) if output_path else None
)
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ tomli-w = "^1.0.0"
tqdm = "^4.64.1"
typing-extensions = "^4.3.0"
tomlkit = "^0.11.6"
maturin = "^0.14.13"

[tool.poetry.dev-dependencies]
GitPython = "^3.1.29"
Expand Down Expand Up @@ -72,6 +73,8 @@ lint = "./scripts/run_lint.sh"
format_check_selected = "black --check"
lint_selected = "pylint"

install_cairo_bindings = "./scripts/install_cairo_bindings.sh"

local_static_check = [
"format",
"lint",
Expand Down
30 changes: 30 additions & 0 deletions scripts/install_cairo_bindings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

set -e

# clean up
if [ "$1" == "--cleanup" ]; then
poetry env info -p | xargs rm -rf
if [[ $(uname -m) == 'arm64' ]]; then
CFLAGS=-I/opt/homebrew/opt/gmp/include LDFLAGS=-L/opt/homebrew/opt/gmp/lib poetry install
else
poetry install
fi
fi

function install() {
pushd "${1}"
git clone https://github.com/software-mansion-labs/cairo.git
pushd cairo
# currrent master works ok, in case it doesn't, uncomment the line below
# git checkout 5608ce7e052df79da11485689cb5f1459d3e5d18 # working commit
pushd crates/cairo-lang-python-bindings
rustup override set nightly
maturin develop --release || return 1;
popd # cairo
popd # cairo/crates/cairo_python_bindings
popd # "${1}"
}

DIR=$(mktemp -d)
install $DIR && echo "DONE" || echo "installation failed"
21 changes: 2 additions & 19 deletions tests/integration/cairo1/parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,15 @@

@pytest.fixture(name="test_suite_json")
def test_suite_json_fixture(datadir: Path) -> str:
"""
Cairo source code of the tested fixture
-----------------------------------------
fn test_cheatcode_caller() {
roll(1, 2)
}
fn test_cheatcode_caller_twice() {
roll(1, 2);
roll(1, 2)
}
fn test_cheatcode_caller_three() {
roll(1, 2);
roll(1, 2);
roll(1, 2)
}
-----------------------------------------
"""
# Cairo source code of the tested fixture - ../cairo_compiler/contracts/roll_test.cairo
with open(datadir / "compiled_test_suite.json", "r") as file:
return file.read()


def test_parse(mocker: MockerFixture, test_suite_json: str):
test_suite = parse_test_suite(Path("test_source.cairo"), test_suite_json)
cheat_mock = mocker.MagicMock()
cheat_mock.return_value = 0
for case in test_suite.test_cases:
runner = CairoFunctionRunner(program=test_suite.program, layout="all")
runner.run_from_entrypoint(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
[
138,
[
"\nmemory[ap + 0] = 0; \nroll(address=[fp + -4], caller_address=[fp + -3])\n"
"\nmemory[ap + 0] = roll(address=[fp + -4], caller_address=[fp + -3]); \n\n"
]
],
[
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[contract]
mod HelloStarknet {
struct Storage { balance: felt, }

// Increases the balance by the given amount.
#[external]
fn increase_balance(amount: felt) {
balance::write(balance::read() + amount);
}

// Returns the current balance.
#[view]
fn get_balance() -> felt {
balance::read();
0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#[abi]
trait IAnotherContract {
fn foo(); }


#[contract]
mod TestContract {
struct Storage { my_storage_var: felt, }

fn internal_func() -> felt {
1
}

#[external]
fn test(ref arg: felt, arg1: felt, arg2: felt) -> felt {
let x = my_storage_var::read();
my_storage_var::write(x + 1);
x + internal_func()
}

#[external]
fn empty() {
}
}
58 changes: 58 additions & 0 deletions tests/integration/cairo_compiler/contracts/enum_contract.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
enum MyEnumShort {
a: felt,
b: felt
}
enum MyEnumLong {
a: felt,
b: felt,
c: felt
}
enum MyEnumGeneric<S, T> {
a: T,
b: S,
c: T
}

impl MyEnumGenericDrop of Drop::<MyEnumGeneric::<(), felt>>;

fn main() -> felt {
let es0 = MyEnumShort::a(10);
match_short(es0);
let es1 = MyEnumShort::b(11);
match_short(es1);
let el0 = MyEnumLong::a(20);
match_long(el0);
let el1 = MyEnumLong::b(21);
match_long(el1);
let el2 = MyEnumLong::c(22);
match_long(el2);
let eg1: MyEnumGeneric::<(), felt> = MyEnumGeneric::<(), felt>::a(30);
let eg2: MyEnumGeneric::<(), felt> = MyEnumGeneric::<(), felt>::b(());
let eg3: MyEnumGeneric::<(), felt> = MyEnumGeneric::<(), felt>::c(32);
300
}

fn match_short(e: MyEnumShort) -> felt {
match e {
MyEnumShort::a(x) => {
x
},
MyEnumShort::b(x) => {
x
},
}
}

fn match_long(e: MyEnumLong) -> felt {
match e {
MyEnumLong::a(x) => {
x
},
MyEnumLong::b(x) => {
x
},
MyEnumLong::c(x) => {
x
},
}
}
15 changes: 15 additions & 0 deletions tests/integration/cairo_compiler/contracts/roll_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#[test]
fn test_cheatcode_caller() {
roll(1, 2)
}
#[test]
fn test_cheatcode_caller_twice() {
roll(1, 2);
roll(1, 2)
}
#[test]
fn test_cheatcode_caller_three() {
roll(1, 2);
roll(1, 2);
roll(1, 2)
}
61 changes: 61 additions & 0 deletions tests/integration/cairo_compiler/prepare_files_fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import Callable, NamedTuple
from pathlib import Path
from enum import Enum

from tests.integration._conftest import ProtostarFixture

TEST_CONTRACTS_PATH = Path(__file__).parent / "contracts"


class RequestedFile(str, Enum):
input_enum_contract_cairo = "enum_contract.cairo"
input_basic_starknet_contract_cairo = "basic_starknet_contract.cairo"
input_basic_starknet_test_cairo = "basic_starknet_test.cairo"
input_roll_test_cairo = "roll_test.cairo"
output_sierra = "output.sierra"
output_casm = "output.casm"


class PreparedFile(NamedTuple):
path: Path
contents: str


class PrepareFilesFixture:
def __init__(self, protostar: ProtostarFixture):
self.protostar = protostar

def prepare_files(
self, requested_files: list[RequestedFile]
) -> dict[str, PreparedFile]:
files: dict[str, PreparedFile] = {}
for file_item in requested_files:
file_path = Path(TEST_CONTRACTS_PATH / file_item.value)
contents = file_path.read_text() if file_path.exists() else ""
file_with_ext = ".".join(file_item.name.rsplit("_", 1))
files[file_item.name] = PreparedFile(
path=Path(f"./src/{file_with_ext}"), contents=contents
)

self.protostar.create_files(
{str(path): contents for path, contents in files.values()}
)
files = {
label: PreparedFile(
path=Path(self.protostar.project_root_path / path), contents=contents
)
for label, (path, contents) in files.items()
}
for path, _ in files.values():
assert path.exists()
return files


def check_compiler_function(
compiler_function_to_test: Callable, input_path: Path, output_path: Path
):
compiler_function_to_test(input_path, output_path)
assert output_path.exists() and output_path.stat().st_size
contents = compiler_function_to_test(input_path)
assert contents
assert contents == "".join(output_path.read_text())
Loading

0 comments on commit 62ffecc

Please sign in to comment.