├── .github
├── .gitkeep
└── workflows
│ ├── deployment.yml
│ ├── formatting.yml
│ ├── linting.yml
│ └── test.yml
├── .gitignore
├── .patches
├── builtins
│ └── bitwise.cairo.patch
├── hints
│ ├── hints00.cairo.patch
│ └── hints01.cairo.patch
├── implicit_arguments
│ ├── implicit_arguments01.cairo.patch
│ ├── implicit_arguments02.cairo.patch
│ └── implicit_arguments03.cairo.patch
├── operations
│ ├── operations00.cairo.patch
│ ├── operations01.cairo.patch
│ ├── operations02.cairo.patch
│ └── operations03.cairo.patch
├── recursions
│ ├── array01.cairo.patch
│ ├── array02.cairo.patch
│ ├── array03.cairo.patch
│ ├── array04.cairo.patch
│ ├── collatz_sequence.cairo.patch
│ ├── recursion01.cairo.patch
│ └── struct01.cairo.patch
├── registers
│ ├── registers00.cairo.patch
│ ├── registers01.cairo.patch
│ ├── registers02.cairo.patch
│ ├── registers03.cairo.patch
│ └── registers04.cairo.patch
├── revoked_references
│ └── revoked_references01.cairo.patch
├── storage
│ ├── storage01.cairo.patch
│ ├── storage02.cairo.patch
│ └── storage03.cairo.patch
├── strings
│ ├── strings00.cairo.patch
│ └── strings01.cairo.patch
├── syntax
│ ├── syntax01.cairo.patch
│ ├── syntax02.cairo.patch
│ ├── syntax03.cairo.patch
│ ├── syntax04.cairo.patch
│ └── syntax05.cairo.patch
└── tricks
│ ├── assert_bool.cairo.patch
│ ├── inline_if.cairo.patch
│ └── no_conditionals.cairo.patch
├── .pylintrc
├── CONTRIBUTING.md
├── README.md
├── deploy.py
├── exercises
├── builtins
│ ├── README.md
│ └── bitwise.cairo
├── hints
│ ├── README.md
│ ├── hints00.cairo
│ └── hints01.cairo
├── implicit_arguments
│ ├── README.md
│ ├── implicit_arguments01.cairo
│ ├── implicit_arguments02.cairo
│ └── implicit_arguments03.cairo
├── operations
│ ├── README.md
│ ├── operations00.cairo
│ ├── operations01.cairo
│ ├── operations02.cairo
│ └── operations03.cairo
├── recursions
│ ├── README.md
│ ├── array01.cairo
│ ├── array02.cairo
│ ├── array03.cairo
│ ├── array04.cairo
│ ├── collatz_sequence.cairo
│ ├── recursion01.cairo
│ └── struct01.cairo
├── registers
│ ├── registers00.cairo
│ ├── registers01.cairo
│ ├── registers02.cairo
│ ├── registers03.cairo
│ └── registers04.cairo
├── revoked_references
│ └── revoked_references01.cairo
├── storage
│ ├── storage01.cairo
│ ├── storage02.cairo
│ └── storage03.cairo
├── strings
│ ├── README.md
│ ├── strings00.cairo
│ └── strings01.cairo
├── syntax
│ ├── README.md
│ ├── syntax01.cairo
│ ├── syntax02.cairo
│ ├── syntax03.cairo
│ ├── syntax04.cairo
│ └── syntax05.cairo
└── tricks
│ ├── assert_bool.cairo
│ ├── inline_if.cairo
│ └── no_conditionals.cairo
├── install.sh
├── lib
└── .gitkeep
├── poetry.lock
├── protostar.toml
├── pyproject.toml
├── pytest.ini
├── resources
└── assets
│ └── warning.png
├── scripts
├── add_new_exercise.sh
└── patch_and_test_exercises.sh
├── src
├── __init__.py
├── cli.py
├── config.py
├── console.py
├── database.py
├── exercises
│ ├── __init__.py
│ ├── checker.py
│ ├── checker_test.py
│ ├── list_test.py
│ ├── model.py
│ ├── model_test.py
│ ├── seeker.py
│ └── seeker_test.py
├── file_watcher
│ ├── dummy_file.cairo
│ ├── watcher.py
│ └── watcher_test.py
├── prompt.py
├── repository
│ ├── __init__.py
│ ├── state_checker.py
│ └── state_checker_test.py
├── runner.py
├── runner_test.py
├── solutions
│ ├── __init__.py
│ ├── factory.py
│ ├── factory_test.py
│ └── repository.py
├── user
│ ├── __init__.py
│ ├── access_token.py
│ ├── login.py
│ └── login_test.py
└── utils
│ ├── __init__.py
│ ├── debounce.py
│ ├── debounce_test.py
│ ├── version_manager.py
│ └── version_manager_test.py
├── starklings-backend
├── .env.DEFAULT
├── README.md
├── app.py
├── config.py
├── db.py
├── requirements.txt
├── starklings_backend
│ ├── __init__.py
│ ├── exercise.py
│ ├── models
│ │ ├── shared.py
│ │ └── user.py
│ ├── routes.py
│ └── utils.py
└── tests
│ └── test_app.py
├── starklings.py
├── starklings.spec
└── tests
├── exercises
└── test_end_of_exercise_messages
│ ├── with_next_exercises
│ ├── syntax01.cairo
│ └── syntax02.cairo
│ └── without_next_exercises
│ ├── syntax01.cairo
│ └── syntax02.cairo
├── test.cairo
├── test_failure.cairo
├── test_invalid.cairo
└── test_missing.cairo
/.github/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/.github/.gitkeep
--------------------------------------------------------------------------------
/.github/workflows/deployment.yml:
--------------------------------------------------------------------------------
1 | name: Deployment
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | build:
10 | runs-on: ${{ matrix.os }}
11 | strategy:
12 | matrix:
13 | os: [ubuntu-18.04, macOS-11] # TODO: Fix windows gmp.h not found issue, and add a windows binary
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Set up Python 3.8.12
17 | uses: actions/setup-python@v2
18 | with:
19 | python-version: 3.8.12
20 | - name: Install dependencies
21 | run: |
22 | python -m pip install --upgrade pip
23 | pip install poetry
24 | poetry install
25 | - name: Build binary
26 | run: |
27 | poetry run poe build
28 | - name: Run a smoke test with --help
29 | run: |
30 | ./dist/starklings/starklings --help
31 | - name: Pack binary into a tarball
32 | run: tar -czvf starklings.tar.gz ./dist/starklings
33 | - name: Upload the tarball
34 | uses: actions/upload-artifact@v2
35 | with:
36 | name: starklings-${{ runner.os }}
37 | path: starklings.tar.gz
38 | publish:
39 | runs-on: ubuntu-latest
40 | needs: [build]
41 | steps:
42 | - uses: actions/checkout@v2
43 | - uses: actions/download-artifact@v2
44 | with:
45 | path: dist
46 |
47 | - name: Upload macOS binary to release
48 | uses: svenstaro/upload-release-action@v2
49 | with:
50 | repo_token: ${{ secrets.GITHUB_TOKEN }}
51 | file: dist/starklings-macOS/starklings.tar.gz
52 | asset_name: starklings-macOS.tar.gz
53 | tag: ${{ github.ref }}
54 | overwrite: true
55 |
56 | - name: Upload Linux binary to release
57 | uses: svenstaro/upload-release-action@v2
58 | with:
59 | repo_token: ${{ secrets.GITHUB_TOKEN }}
60 | file: dist/starklings-Linux/starklings.tar.gz
61 | asset_name: starklings-Linux.tar.gz
62 | tag: ${{ github.ref }}
63 | overwrite: true
64 | release:
65 | runs-on: ubuntu-latest
66 | needs: [build]
67 | steps:
68 | - uses: actions/checkout@v2
69 | - name: Initialize mandatory git config
70 | run: |
71 | git config user.name "GitHub Actions"
72 | git config user.email noreply@github.com
73 | - name: Checkout stable branch
74 | run: |
75 | git fetch origin
76 | git checkout stable
77 | - name: Reset on main
78 | run: |
79 | git reset --hard origin/main
80 | git push --force origin stable
81 |
--------------------------------------------------------------------------------
/.github/workflows/formatting.yml:
--------------------------------------------------------------------------------
1 | name: Formatting
2 |
3 | on:
4 | pull_request: ~
5 |
6 | jobs:
7 | cairo:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-python@v3
12 | with:
13 | python-version: "3.8"
14 | - name: Install dependencies
15 | run:
16 | | # Install latest version from https://pypi.org/simple/cairo-lang/ (as done in nile)
17 | pip install https://files.pythonhosted.org/packages/c0/f6/c850604895a2ce5ff3ef77cdb470b6b0ef50889645748a748e18a1c2269e/cairo-lang-0.8.1.post1.zip#sha256=b3c1a23078ba4e0c8ec45d2cd2ba4873ad70b6723dfba70f70c978c9723ff6eb
18 | - name: Check files formatting
19 | run: cairo-format -c exercises/**/*.cairo
20 | python:
21 | runs-on: ubuntu-latest
22 | steps:
23 | - uses: actions/checkout@v3
24 | - uses: actions/setup-python@v3
25 | with:
26 | python-version: "3.8"
27 | - name: Install dependencies
28 | run: |
29 | pip install black
30 | - name: Check files formatting
31 | run: black --check src tests
32 |
--------------------------------------------------------------------------------
/.github/workflows/linting.yml:
--------------------------------------------------------------------------------
1 | name: Linting
2 |
3 | on:
4 | pull_request: ~
5 |
6 | jobs:
7 | python:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-python@v3
12 | with:
13 | python-version: "3.8"
14 | - name: Install dependencies
15 | run: |
16 | python -m pip install --upgrade pip
17 | pip install poetry
18 | poetry install
19 | - name: Check files linting
20 | run: poetry run pylint src
21 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | pull_request: ~
5 |
6 | jobs:
7 | unit-test:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - name: Set up Python 3.8.12
12 | uses: actions/setup-python@v2
13 | with:
14 | python-version: 3.8.12
15 | - name: Install dependencies
16 | run: |
17 | python -m pip install --upgrade pip
18 | pip install poetry
19 | poetry install
20 | - name: Build binary
21 | run: |
22 | poetry run poe build
23 | - name: Run tests
24 | run: |
25 | poetry run pytest src
26 | exercises-test:
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v2
30 | - name: Set up Python 3.8.12
31 | uses: actions/setup-python@v2
32 | with:
33 | python-version: 3.8.12
34 | - name: Install protostar
35 | run: |
36 | curl -L https://raw.githubusercontent.com/software-mansion/protostar/master/install.sh | bash
37 | - name: Run exercise tests
38 | run: |
39 | chmod +x ./scripts/patch_and_test_exercises.sh
40 | ./scripts/patch_and_test_exercises.sh
41 | shell: bash
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .vscode
3 |
4 | # Python
5 | __pycache__
6 | env
7 | *.py[cod]
8 |
9 | # Protostar
10 | build
11 |
12 | # Pyinstaller
13 | dist
14 |
15 | .solutions
16 | starklings.db
17 | starklings-backend/.env
--------------------------------------------------------------------------------
/.patches/builtins/bitwise.cairo.patch:
--------------------------------------------------------------------------------
1 | --- a/exercises/builtins/bitwise.cairo 2022-06-15 15:00:45.028637685 +0200
2 | +++ b/exercises/builtins/bitwise.cairo 2022-06-15 15:00:24.931890268 +0200
3 | @@ -22 +22,3 @@
4 | - # FILL ME
5 | + let (pow2n) = pow(2, n)
6 | + let (and_val) = bitwise_and(value, pow2n)
7 | + let (res) = is_not_zero(and_val)
8 | @@ -29 +31,2 @@
9 | - # FILL ME
10 | + let (pow2n) = pow(2, n)
11 | + let (res) = bitwise_or(value, pow2n)
12 | @@ -36 +39,2 @@
13 | - # FILL ME
14 | + let (pow2n) = pow(2, n)
15 | + let (res) = bitwise_xor(value, pow2n)
16 | @@ -55 +59,2 @@
17 | - # Assert op is correct
18 | + # Product can only be zero if op in ['get', 'set', 'toggle']
19 | + assert 0 = (op - 'get') * (op - 'set') * (op - 'toggle')
20 | @@ -58 +64,2 @@
21 | - # Assert n is within bounds
22 | + assert_nn(n)
23 | + assert_le(n, 250)
24 | @@ -61,2 +68,18 @@
25 | - # Compute the operation
26 | - # Don't forget to advance bitwise_ptr
27 | + let (pow2n) = pow(2, n)
28 | + assert bitwise_ptr.x = value
29 | + assert bitwise_ptr.y = pow2n
30 | +
31 | + local res
32 | + if op == 'get':
33 | + let (tmp) = is_not_zero(bitwise_ptr.x_and_y)
34 | + assert res = tmp
35 | + else:
36 | + if op == 'set':
37 | + assert res = bitwise_ptr.x_or_y
38 | + else:
39 | + # op == 'toggle'
40 | + assert res = bitwise_ptr.x_xor_y
41 | + end
42 | + end
43 | +
44 | + let bitwise_ptr = bitwise_ptr + BitwiseBuiltin.SIZE
45 |
--------------------------------------------------------------------------------
/.patches/hints/hints00.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/hints/hints00.cairo b/exercises/hints/hints00.cairo
2 | index 24b17e1..554e059 100644
3 | --- a/exercises/hints/hints00.cairo
4 | +++ b/exercises/hints/hints00.cairo
5 | @@ -16,0 +17 @@ func basic_hint() -> (value : felt):
6 | + %{ ids.res = 42 %}
7 |
--------------------------------------------------------------------------------
/.patches/hints/hints01.cairo.patch:
--------------------------------------------------------------------------------
1 | --- a/exercises/hints/hints01.cairo 2022-06-01 10:52:51.816970359 +0200
2 | +++ b/exercises/hints/hints01.cairo 2022-06-01 11:10:07.339328562 +0200
3 | @@ -18,3 +18,3 @@
4 | - # TODO: Compute the quotient and remainder inside the hint
5 | - print(ids.quotient)
6 | - print(ids.remainder)
7 | + q, r = divmod(ids.x, ids.n)
8 | + ids.quotient = q
9 | + ids.remainder = r
10 | @@ -22,3 +22,2 @@
11 | - # TODO: verify the result is correct
12 | -
13 | - return (0)
14 | + assert x = quotient * n + remainder
15 | + return (remainder)
16 |
--------------------------------------------------------------------------------
/.patches/implicit_arguments/implicit_arguments01.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/implicit_arguments/implicit_arguments01.cairo b/exercises/implicit_arguments/implicit_arguments01.cairo
2 | index ca97179..bb45805 100644
3 | --- a/exercises/implicit_arguments/implicit_arguments01.cairo
4 | +++ b/exercises/implicit_arguments/implicit_arguments01.cairo
5 | @@ -10 +10 @@
6 | -func implicit_sum() -> (result : felt):
7 | +func implicit_sum{a, b}() -> (result : felt):
8 |
--------------------------------------------------------------------------------
/.patches/implicit_arguments/implicit_arguments02.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/implicit_arguments/implicit_arguments02.cairo b/exercises/implicit_arguments/implicit_arguments02.cairo
2 | index c2390e7..5f64a2f 100644
3 | --- a/exercises/implicit_arguments/implicit_arguments02.cairo
4 | +++ b/exercises/implicit_arguments/implicit_arguments02.cairo
5 | @@ -20 +20 @@ end
6 | -func child_function_1() -> (result : felt):
7 | +func child_function_1{a}() -> (result : felt):
8 | @@ -25 +25 @@ end
9 | -func child_function_2() -> (result : felt):
10 | +func child_function_2{b}() -> (result : felt):
11 |
--------------------------------------------------------------------------------
/.patches/implicit_arguments/implicit_arguments03.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/implicit_arguments/implicit_arguments03.cairo b/exercises/implicit_arguments/implicit_arguments03.cairo
2 | index 93629ad..173a4f3 100644
3 | --- a/exercises/implicit_arguments/implicit_arguments03.cairo
4 | +++ b/exercises/implicit_arguments/implicit_arguments03.cairo
5 | @@ -13,0 +14 @@ func black_box{secret : felt}() -> ():
6 | + let secret = 'very secret!'
7 |
--------------------------------------------------------------------------------
/.patches/operations/operations00.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/operations/operations00.cairo 2022-08-15 14:01:18.147439197 +0200
2 | +++ ./.solutions/operations00.cairo 2022-08-15 14:02:33.348133149 +0200
3 | @@ -11 +11 @@
4 | - # FILL ME
5 | + let res = (x - 1) * (x + 2) / (x - 2)
6 |
--------------------------------------------------------------------------------
/.patches/operations/operations01.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/operations/operations01.cairo 2022-08-15 14:19:41.493661039 +0200
2 | +++ ./.solutions/operations01.cairo 2022-08-15 13:31:21.670475341 +0200
3 | @@ -13 +13 @@
4 | - # FILL ME
5 | + let x = 2 ** 251 + 17 * 2 ** 192
6 | @@ -23 +23 @@
7 | - # FILL ME
8 | + const y = 2 ** 250 + 17 * 2 ** 191
9 |
--------------------------------------------------------------------------------
/.patches/operations/operations02.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/operations/operations02.cairo 2022-08-15 14:09:44.344120823 +0200
2 | +++ ./.solutions/operations02.cairo 2022-08-15 14:09:18.319879674 +0200
3 | @@ -14 +14 @@
4 | - # TO FILL
5 | + let x = 89 # works for all prime numbers in range [50 ; 100]
6 |
--------------------------------------------------------------------------------
/.patches/operations/operations03.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/operations/operations03.cairo 2022-08-15 14:02:02.971852750 +0200
2 | +++ ./.solutions/operations03.cairo 2022-08-15 14:09:15.559854101 +0200
3 | @@ -15 +15 @@
4 | - %{ ids.x = -1 %} # Change only this line to make the test pass
5 | + %{ ids.x = 2 ** 251 + 17 * 2 ** 192 + 1 - 17 %}
6 |
--------------------------------------------------------------------------------
/.patches/recursions/array01.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/recursions/array01.cairo b/exercises/recursions/array01.cairo
2 | index 44cb04f..9f0d822 100644
3 | --- a/exercises/recursions/array01.cairo
4 | +++ b/exercises/recursions/array01.cairo
5 | @@ -12 +12,8 @@ func contains(needle : felt, haystack : felt*, haystack_len : felt) -> (result :
6 | - return (0)
7 | + if haystack_len == 0:
8 | + return (0)
9 | + end
10 | + if needle == haystack[0]:
11 | + return (1)
12 | + end
13 | + let (next) = contains(needle, haystack + 1, haystack_len - 1)
14 | + return (next)
15 |
--------------------------------------------------------------------------------
/.patches/recursions/array02.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/recursions/array02.cairo b/exercises/recursions/array02.cairo
2 | index de33f4a..7ff6098 100644
3 | --- a/exercises/recursions/array02.cairo
4 | +++ b/exercises/recursions/array02.cairo
5 | @@ -14 +14 @@ from starkware.cairo.common.alloc import alloc
6 | -func square(array : felt*, array_len : felt):
7 | +func square(array : felt*, array_len : felt, squared_array : felt*):
8 | @@ -20 +20 @@ func square(array : felt*, array_len : felt):
9 | - assert [array] = squared_item
10 | + assert [squared_array] = squared_item
11 | @@ -22 +22 @@ func square(array : felt*, array_len : felt):
12 | - return square(array + 1, array_len - 1)
13 | + return square(array + 1, array_len - 1, squared_array + 1)
14 | @@ -36 +36,2 @@ func test_square{syscall_ptr : felt*}():
15 | - square(array, 4)
16 | + let (squared_array : felt*) = alloc()
17 | + square(array, 4, squared_array)
18 | @@ -38,4 +39,4 @@ func test_square{syscall_ptr : felt*}():
19 | - assert [array] = 1
20 | - assert [array + 1] = 4
21 | - assert [array + 2] = 9
22 | - assert [array + 3] = 16
23 | + assert [squared_array] = 1
24 | + assert [squared_array + 1] = 4
25 | + assert [squared_array + 2] = 9
26 | + assert [squared_array + 3] = 16
27 |
--------------------------------------------------------------------------------
/.patches/recursions/array03.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/recursions/array03.cairo 2022-05-25 16:16:23.785739305 +0200
2 | +++ array03.cairo 2022-05-25 16:16:25.372412454 +0200
3 | @@ -25,2 +25,2 @@ func is_increasing{range_check_ptr : fel
4 | - let curr_value = 0
5 | - let next_value = 0
6 | + let curr_value = [array]
7 | + let next_value = [array + 1]
8 | @@ -43,0 +44,10 @@ func is_decreasing{range_check_ptr : fel
9 | + if array_len == 0:
10 | + return (1)
11 | + end
12 | +
13 | + if array_len == 1:
14 | + return (1)
15 | + end
16 | +
17 | + let curr_value = array[array_len - 1]
18 | + let next_value = array[array_len - 2]
19 | @@ -49 +59 @@ func is_decreasing{range_check_ptr : fel
20 | - return is_decreasing(array, array_len)
21 | + return is_decreasing(array, array_len - 1)
22 | @@ -60,0 +71,6 @@ func reverse(array : felt*, rev_array :
23 | + if array_len == 0:
24 | + return ()
25 | + end
26 | +
27 | + assert [rev_array] = array[array_len - 1]
28 | + reverse(array, rev_array + 1, array_len - 1)
29 |
--------------------------------------------------------------------------------
/.patches/recursions/array04.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/recursions/array04.cairo 2022-05-30 19:30:16.000000000 -0400
2 | +++ /tmp/array04.cairo 2022-05-30 19:29:01.000000000 -0400
3 | @@ -14 +14,14 @@
4 | - # FILL ME
5 | + if len_points == 0:
6 | + return (0)
7 | + end
8 | + if points[0].x != 0:
9 | + return contains_origin(len_points-1, points + Point.SIZE)
10 | + end
11 | + if points[0].y != 0:
12 | + return contains_origin(len_points-1, points + Point.SIZE)
13 | + end
14 | + if points[0].z != 0:
15 | + return contains_origin(len_points-1, points + Point.SIZE)
16 | + end
17 | +
18 | + return (1)
19 |
--------------------------------------------------------------------------------
/.patches/recursions/collatz_sequence.cairo.patch:
--------------------------------------------------------------------------------
1 | --- collatz_sequence.cairo 2022-05-31 11:36:29.555657000 +0400
2 | +++ collatz_sequence_solution.cairo 2022-05-31 11:29:43.390728000 +0400
3 | @@ -18,0 +19,15 @@
4 | + if step == 0:
5 | + return (result=seed)
6 | + end
7 | +
8 | + if seed == 1:
9 | + return (result=1)
10 | + end
11 | +
12 | + let (even_or_odd) = bitwise_and(seed, 1)
13 | +
14 | + if even_or_odd == 0:
15 | + let number : felt = collatz(seed=seed / 2, step=step - 1)
16 | + else:
17 | + let number : felt = collatz(seed=seed * 3 + 1, step=step - 1)
18 | + end
19 |
--------------------------------------------------------------------------------
/.patches/recursions/recursion01.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/recursions/recursion01.cairo b/exercises/recursions/recursion01.cairo
2 | index 811b7a9..4ef0c2f 100644
3 | --- a/exercises/recursions/recursion01.cairo
4 | +++ b/exercises/recursions/recursion01.cairo
5 | @@ -13 +13,10 @@ func fibonacci(n : felt) -> (result : felt):
6 | - return (0)
7 | + alloc_locals
8 | + if n == 0:
9 | + return (0)
10 | + end
11 | + if n == 1:
12 | + return (1)
13 | + end
14 | + let (local n_1) = fibonacci(n - 1)
15 | + let (local n_2) = fibonacci(n - 2)
16 | + return (n_1 + n_2)
17 |
--------------------------------------------------------------------------------
/.patches/recursions/struct01.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/recursions/struct01.cairo 2022-05-31 13:55:44.000000000 +0200
2 | +++ exercises/recursions/struct01.sol.cairo 2022-05-31 13:53:49.000000000 +0200
3 | @@ -30 +30,7 @@
4 | - # FILL ME
5 | + if struct_size == 0:
6 | + return (res=0)
7 | + end
8 | +
9 | + let square = [struct_value] * [struct_value]
10 | + let (rest) = squared_magnitude(struct_value + 1, struct_size - 1)
11 | + return (res=square + rest)
12 |
--------------------------------------------------------------------------------
/.patches/registers/registers00.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/registers/registers00.cairo 2022-05-18 12:53:46.000000000 -0400
2 | +++ .tmp_solution/registers00.cairo 2022-05-18 11:38:32.000000000 -0400
3 | @@ -12,3 +12,2 @@
4 | -func ret_42() -> (r : felt):
5 | - # [ap] = 42; ap++
6 | - # ret
7 | +func ret_42() -> (r: felt):
8 | + return (42)
9 | @@ -20,2 +19,4 @@
10 | -func ret_0_and_1() -> (zero : felt, one : felt):
11 | - # return (0, 1)
12 | +func ret_0_and_1() -> (zero: felt, one: felt):
13 | + [ap] = 0; ap++
14 | + [ap] = 1; ap++
15 | + ret
16 | @@ -43 +44 @@
17 | -end
18 | +end
19 | \ No newline at end of file
20 |
--------------------------------------------------------------------------------
/.patches/registers/registers01.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/registers/registers01.cairo 2022-05-18 12:53:45.000000000 -0400
2 | +++ .tmp_solution/registers01.cairo 2022-05-18 11:39:34.000000000 -0400
3 | @@ -11,3 +11,3 @@
4 | -func assert_is_42(n : felt):
5 | - # [ap - 3] = 42
6 | - # ret
7 | +func assert_is_42(n: felt):
8 | + assert n = 42
9 | + ret
10 | @@ -19,2 +19,3 @@
11 | -func sum(a : felt, b : felt) -> (s : felt):
12 | - # return (a + b)
13 | +func sum(a: felt, b: felt) -> (s: felt):
14 | + [ap] = [ap -3] + [ap -4]; ap++
15 | + ret
16 | @@ -45 +46 @@
17 | -end
18 | +end
19 | \ No newline at end of file
20 |
--------------------------------------------------------------------------------
/.patches/registers/registers02.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/registers/registers02.cairo 2022-06-11 22:20:43.000000000 -0400
2 | +++ ./registers02.cairo 2022-06-11 22:18:21.000000000 -0400
3 | @@ -17,3 +17,3 @@
4 | - # [ap] = 42; ap++
5 | - # [ap - 1] = 42
6 | - # [ap - 1] = 21
7 | + tempvar x = 42
8 | + assert x = 42
9 | + assert x = 21
10 | @@ -27 +27 @@
11 | - # assert number = 42
12 | + [fp -3] = 42
13 | @@ -37,0 +38 @@
14 | + assert [p_number] = 42
15 | @@ -45,0 +47 @@
16 | + assert 42 = [p_number]
17 |
--------------------------------------------------------------------------------
/.patches/registers/registers03.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/registers/registers03.cairo 2022-05-18 12:53:44.000000000 -0400
2 | +++ .tmp_solution/registers03.cairo 2022-05-18 11:40:22.000000000 -0400
3 | @@ -3,0 +4 @@
4 | +
5 | @@ -9,6 +10,2 @@
6 | -func sum_array(array_len : felt, array : felt*) -> (sum : felt):
7 | - # [ap] = [fp - 4]; ap++
8 | - # [ap] = [fp - 3]; ap++
9 | - # [ap] = 0; ap++
10 | - # call rec_sum_array
11 | - # ret
12 | +func sum_array(array_len: felt, array: felt*) -> (sum: felt):
13 | + return rec_sum_array(array_len, array, 0)
14 | @@ -17,16 +14,6 @@
15 | -func rec_sum_array(array_len : felt, array : felt*, sum : felt) -> (sum : felt):
16 | - # jmp continue if [fp - 5] != 0
17 | -
18 | - # stop:
19 | - # [ap] = [fp - 3]; ap++
20 | - # jmp done
21 | -
22 | - # continue:
23 | - # [ap] = [[fp - 4]]; ap++
24 | - # [ap] = [fp - 5] - 1; ap++
25 | - # [ap] = [fp - 4] + 1; ap++
26 | - # [ap] = [ap - 3] + [fp - 3]; ap++
27 | - # call rec_sum_array
28 | -
29 | - # done:
30 | - # ret
31 | +func rec_sum_array(array_len: felt, array: felt*, sum: felt) -> (sum: felt):
32 | + if array_len == 0:
33 | + return (sum)
34 | + end
35 | +
36 | + return rec_sum_array(array_len - 1, array + 1, sum + array[0])
37 | @@ -39,7 +26,31 @@
38 | -func max{range_check_ptr}(a : felt, b : felt) -> (max : felt):
39 | - # let (res) = is_le(a, b)
40 | - # if res == 1:
41 | - # return (b)
42 | - # else:
43 | - # return (a)
44 | - # end
45 | +func max{range_check_ptr}(a: felt, b: felt) -> (max: felt):
46 | + # Push arguments to the stack
47 | + [ap] = [fp - 5]; ap++ # range_check_ptr
48 | + [ap] = [fp - 4]; ap++ # a
49 | + [ap] = [fp - 3]; ap++ # b
50 | +
51 | + # This call will return two values
52 | + # 1) the updated range_check_ptr
53 | + # 2) 0 or 1 depending on which of a and b is greater
54 | + call is_le
55 | +
56 | + # Push return values to the stack
57 | + # There is two of them to push: range_check_ptr and max
58 | +
59 | + # Push the first one, the updated range_check_ptr, onto the stack
60 | + [ap] = [ap - 2]; ap++
61 | +
62 | + # Conditional jump
63 | + # The following blocks are an assembly level equivalent of the if/else pattern
64 | + jmp b_is_more if [ap - 2] !=0 # here [ap-2] is the second value returned by is_le, our boolean
65 | +
66 | + # Push either a or b to the stack
67 | + a_is_more:
68 | + [ap] = [fp - 4]; ap++
69 | + jmp done
70 | +
71 | + b_is_more:
72 | + [ap] = [fp -3]; ap++
73 | +
74 | + done:
75 | + ret
76 | @@ -79 +90 @@
77 | -
78 | +
79 |
--------------------------------------------------------------------------------
/.patches/registers/registers04.cairo.patch:
--------------------------------------------------------------------------------
1 | --- ./exercises/registers/registers04.cairo 2022-08-07 17:55:58.875865309 +0200
2 | +++ ./.solutions/registers04.cairo 2022-08-07 17:55:48.967775665 +0200
3 | @@ -12,0 +13 @@
4 | + let res = 67 + x * (45 + x * (23 + x))
5 | @@ -19,0 +21,6 @@
6 | + [ap] = x; ap++
7 | + [ap] = [ap - 1] + 23; ap++
8 | + [ap] = [ap - 1] * [ap - 2]; ap++
9 | + [ap] = [ap - 1] + 45; ap++
10 | + [ap] = [ap - 1] * [ap - 4]; ap++
11 | + [ap] = [ap - 1] + 67; ap++
12 |
--------------------------------------------------------------------------------
/.patches/revoked_references/revoked_references01.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/revoked_references/revoked_references01.cairo b/exercises/revoked_references/revoked_references01.cairo
2 | index 2c0fe0f..23884e6 100644
3 | --- a/exercises/revoked_references/revoked_references01.cairo
4 | +++ b/exercises/revoked_references/revoked_references01.cairo
5 | @@ -22,0 +23 @@ func bar{hash_ptr : HashBuiltin*}():
6 | + alloc_locals
7 | @@ -26,0 +28 @@ func bar{hash_ptr : HashBuiltin*}():
8 | + local hash_ptr : HashBuiltin* = hash_ptr
9 |
--------------------------------------------------------------------------------
/.patches/storage/storage01.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/storage/storage01.cairo 2022-05-31 15:29:04.000000000 -0400
2 | +++ /tmp/storage/storage01.cairo 2022-05-31 15:27:25.000000000 -0400
3 | @@ -7,2 +7,3 @@
4 | -# TODO
5 | -# Create a storage named `bool` storing a single felt
6 | +@storage_var
7 | +func bool() -> (bool: felt):
8 | +end
9 |
--------------------------------------------------------------------------------
/.patches/storage/storage02.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/storage/storage02.cairo 2022-06-07 10:47:20.000000000 -0400
2 | +++ ./tmp.cairo 2022-06-07 10:44:57.000000000 -0400
3 | @@ -13,4 +13,11 @@
4 | -# TODO
5 | -# Create a storage named wallet, mapping a felt to another
6 | -# Create a storage named height_map, mapping two felts to another
7 | -# Create a storage named id, mapping a felt to an Id
8 | +@storage_var
9 | +func wallet(address: felt) -> (amount: felt):
10 | +end
11 | +
12 | +@storage_var
13 | +func height_map(x: felt, y: felt) -> (z: felt):
14 | +end
15 | +
16 | +@storage_var
17 | +func id(account: felt) -> (id: Id):
18 | +end
19 |
--------------------------------------------------------------------------------
/.patches/storage/storage03.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/storage/storage03.cairo 2022-05-31 15:33:08.000000000 -0400
2 | +++ /tmp/storage/storage03.cairo 2022-05-31 15:27:25.000000000 -0400
3 | @@ -13,2 +13,9 @@
4 | - # TODO
5 | - # Implement toggle external
6 | + let (b) = bool.read()
7 | +
8 | + if b == 0:
9 | + bool.write(1)
10 | + else:
11 | + bool.write(0)
12 | + end
13 | +
14 | + return ()
15 | @@ -19,2 +26 @@
16 | - # TODO
17 | - # Implement view_bool
18 | + return bool.read()
19 |
--------------------------------------------------------------------------------
/.patches/strings/strings00.cairo.patch:
--------------------------------------------------------------------------------
1 | --- exercises/strings/strings00.cairo 2022-05-20 18:37:19.510948914 +0200
2 | +++ .solutions/strings00.cairo 2022-05-20 18:37:13.110928901 +0200
3 | @@ -12,7 +12,9 @@
4 | # TODO: Fix the say_hello function by returning the appropriate short strings
5 |
6 | func say_hello() -> (hello_string : felt, hello_felt : felt, hello_hex : felt):
7 | - # FILL ME
8 | + let hello_string = 'Hello Starklings'
9 | + let hello_felt = '#L2-2022 #L3-2023'
10 | + let hello_hex = 'buidl buidl buidl'
11 | return (hello_string, hello_felt, hello_hex)
12 | end
13 |
14 |
--------------------------------------------------------------------------------
/.patches/strings/strings01.cairo.patch:
--------------------------------------------------------------------------------
1 | --- a/exercises/strings/strings01.cairo 2022-05-24 16:32:08.050760341 +0200
2 | +++ b/exercises/strings/strings01.cairo 2022-05-24 15:41:30.545171320 +0200
3 | @@ -11 +11,3 @@
4 | - let key = 0
5 | +
6 | + # let key = 34642338947246174141841020580919298249066932857229157879292816894
7 | + let key = 'Twinkle Twinkle Little Star' - 'Another One Bites The Dust'
8 | @@ -19 +21,2 @@
9 | - let ciphertext = 0
10 | + # let ciphertext = 3042032262124705672547337926850636172917305208722716617195516092604696795127
11 | + let ciphertext = ('Magic Starknet Money' - 0xc0de) / 1337
12 |
--------------------------------------------------------------------------------
/.patches/syntax/syntax01.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/syntax/syntax01.cairo b/exercises/syntax/syntax01.cairo
2 | index f5b07a3..2e4537d 100644
3 | --- a/exercises/syntax/syntax01.cairo
4 | +++ b/exercises/syntax/syntax01.cairo
5 | @@ -0,0 +1 @@
6 | +%lang starknet
7 |
--------------------------------------------------------------------------------
/.patches/syntax/syntax02.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/syntax/syntax02.cairo b/exercises/syntax/syntax02.cairo
2 | index e210f76..a65e2a6 100644
3 | --- a/exercises/syntax/syntax02.cairo
4 | +++ b/exercises/syntax/syntax02.cairo
5 | @@ -8,0 +9 @@
6 | +from starkware.cairo.common.cairo_builtins import HashBuiltin
7 |
--------------------------------------------------------------------------------
/.patches/syntax/syntax03.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/syntax/syntax03.cairo b/exercises/syntax/syntax03.cairo
2 | index c07022c..6576af4 100644
3 | --- a/exercises/syntax/syntax03.cairo
4 | +++ b/exercises/syntax/syntax03.cairo
5 | @@ -9 +9 @@
6 | -func takes_two_arguments_and_returns_one() -> ():
7 | +func takes_two_arguments_and_returns_one(a : felt, b : felt) -> (result : felt):
8 |
--------------------------------------------------------------------------------
/.patches/syntax/syntax04.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/syntax/syntax04.cairo b/exercises/syntax/syntax04.cairo
2 | index 60e60bd..9e9e3d3 100644
3 | --- a/exercises/syntax/syntax04.cairo
4 | +++ b/exercises/syntax/syntax04.cairo
5 | @@ -19 +19 @@ func test_hello{syscall_ptr : felt*}():
6 | - let (result) = returns_something()
7 | + let (result) = my_namespace.returns_something()
8 |
--------------------------------------------------------------------------------
/.patches/syntax/syntax05.cairo.patch:
--------------------------------------------------------------------------------
1 | diff --git a/exercises/syntax/syntax05.cairo b/exercises/syntax/syntax05.cairo
2 | index bb65c7b..1dc484c 100644
3 | --- a/exercises/syntax/syntax05.cairo
4 | +++ b/exercises/syntax/syntax05.cairo
5 | @@ -7,0 +8,4 @@
6 | +struct Currency:
7 | + member name : felt
8 | + member decimals : felt
9 | +end
10 |
--------------------------------------------------------------------------------
/.patches/tricks/assert_bool.cairo.patch:
--------------------------------------------------------------------------------
1 | --- a/exercises/tricks/assert_bool.cairo 2022-06-02 15:40:52.048123812 +0200
2 | +++ b/exercises/tricks/assert_bool.cairo 2022-06-02 15:41:00.474738153 +0200
3 | @@ -10 +10 @@
4 | - # FILL ME
5 | + assert 0 = (x - 1) * (y - 1)
6 | @@ -15 +15,2 @@
7 | - # FILL ME
8 | + assert 2 = x + y
9 | + # assert 1 = x * y # works too
10 | @@ -20 +21 @@
11 | - # FILL ME
12 | + assert 0 = x + y
13 | @@ -25 +26 @@
14 | - # FILL ME
15 | + assert 1 = x + y
16 |
--------------------------------------------------------------------------------
/.patches/tricks/inline_if.cairo.patch:
--------------------------------------------------------------------------------
1 | --- a/exercises/tricks/inline_if.cairo 2022-06-15 17:34:56.044139899 +0200
2 | +++ b/exercises/tricks/inline_if.cairo 2022-06-15 17:34:47.677432930 +0200
3 | @@ -15 +15,4 @@
4 | - # FILL ME
5 | + let is_boolean_helper = (cond - TRUE) * (cond - FALSE)
6 | + assert 0 = is_boolean_helper
7 | +
8 | + let res = cond * val_true + (1 - cond) * val_false
9 |
--------------------------------------------------------------------------------
/.patches/tricks/no_conditionals.cairo.patch:
--------------------------------------------------------------------------------
1 | --- a/exercises/tricks/no_conditionals.cairo 2022-06-02 14:49:04.224066616 +0200
2 | +++ a/exercises/tricks/no_conditionals.cairo 2022-06-02 14:46:49.463087238 +0200
3 | @@ -28,2 +28,2 @@
4 | - # FILL ME
5 | - return (res)
6 | + let (not_zero) = is_not_zero((x - 1) * x)
7 | + return (res=1 - not_zero)
8 | @@ -37 +37,3 @@
9 | - # FILL ME
10 | + let tmp = (x - 1337) * (x - 69420) * (x - 42)
11 | + let (s) = is_not_zero(tmp)
12 | + let res = s * 'meh' + (1 - s) * 'cool'
13 |
--------------------------------------------------------------------------------
/.pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | disable=missing-function-docstring,
4 | missing-module-docstring,
5 | missing-class-docstring,
6 | too-few-public-methods,
7 | line-too-long # not an issue to disable this rule since black automatically wraps the overflowing lines
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Starklings contributing guide
2 |
3 | Thank you for investing your time in contributing to our project!
4 |
5 | In this guide you will find the necessary instructions to get up and running with developing Starklings new features or fixing some bugs.
6 |
7 | ---
8 |
9 | ## Setting up a development environment
10 |
11 | ### Requirements
12 |
13 | - [Python >=3.8 <3.9](https://www.python.org/downloads/)
14 |
15 | ### Setting up environment
16 |
17 | 1. Install Python version management tool: [pyenv](https://github.com/pyenv/pyenv) or [asdf](https://github.com/asdf-vm/asdf)
18 | 2. Install `Python 3.8` using the Python version management tool and activate that version
19 | 3. Clone this repository
20 | 4. Verify the active Python version: `python -V`
21 | 5. Create Python virtual environment in the project directory: `python -m venv env`
22 | 6. Activate environment: `source env/bin/activate`
23 | 7. Upgrade pip: `pip install --upgrade pip`
24 | 8. Install [Poetry](https://python-poetry.org/) — a dependency manager: `pip install poetry`
25 | 9. Install project dependencies: `poetry install`
26 |
27 | > Troubleshooting: if you run on a Mac M1, you might encounter the following error: `fatal error: 'gmp.h' file not found`
28 | > See https://github.com/OpenZeppelin/nile/issues/22 for detailed solutions.
29 |
30 | ## How can I contribute?
31 |
32 | ### Report a bug or suggest an enhancement
33 |
34 | If you spot a problem or have an idea for a cool feature, first make sure there is no related [existing issue](https://github.com/onlydustxyz/starklings/issues "Starklings GitHub Issues").
35 | If not, you can open a [new Issue](https://github.com/onlydustxyz/starklings/issues/new/choose "Open a new Issue") and add a description of your suggestions.
36 |
37 | ### Submitting changes
38 |
39 | Please send a GitHub Pull Request with a list of what your changes accomplish.
40 | Make sure to follow the guidelines below.
41 |
42 | ### Development guidelines
43 |
44 | See [StarkNet development guidelines](https://github.com/onlydustxyz/development-guidelines/blob/main/starknet/README.md).
45 |
46 | ## Where can I ask a question?
47 |
48 | If you have a question, please ask it in [Discord](https://discord.gg/kutmDrKv "OnlyDust Discord").
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Starklings
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
An interactive tutorial to get you up and running with Starknet
17 |
18 |
19 |
20 |

21 |

22 |

23 |
24 |
This repository is no more maintained and has been moved to :
25 |
starklings-cairo1
26 |
27 |

28 |

29 |

30 |
31 |
32 | ---
33 |
34 | ## Installation
35 |
36 | Clone the repository to your local machine:
37 |
38 | ```shell
39 | git clone --branch stable --single-branch https://github.com/onlydustxyz/starklings.git
40 | ```
41 |
42 | Then install the tool, run:
43 |
44 | ```shell
45 | curl -L https://raw.githubusercontent.com/onlydustxyz/starklings/master/install.sh | bash
46 | ```
47 |
48 | ## Usage
49 |
50 | Run the tool in watch mode in the repository and follow the instructions:
51 |
52 | ```shell
53 | cd PATH_TO_THE_CLONED_REPO
54 | starklings --watch
55 | ```
56 |
57 | To verify an exercise without the watch mode run:
58 |
59 | ```shell
60 | starklings --verify relative_path_to_the_exercise_file
61 | ```
62 |
63 | To display one solution to a given exercise run:
64 |
65 | ```shell
66 | starklings --solution relative_path_to_the_exercise_file
67 | ```
68 |
69 | ## Update
70 |
71 | The tool is in continuous evolution. You might want to update it regularly.
72 |
73 | ```shell
74 | git pull
75 | bash install.sh
76 | ```
77 |
78 | ## Inspiration
79 |
80 | - [Protostar](https://github.com/software-mansion/protostar) for all the project tooling and setup, deployment,
81 | packaging
82 | - [Rustlings](https://github.com/rust-lang/rustlings) for the amazing pedagogy and brilliant concept of progressive and
83 | interactive tutorial
84 |
85 | ## Contributing
86 |
87 | See [CONTRIBUTING.md](CONTRIBUTING.md).
88 |
--------------------------------------------------------------------------------
/deploy.py:
--------------------------------------------------------------------------------
1 | import re
2 | import sys
3 | from pathlib import Path
4 | from typing import Optional
5 |
6 | import tomli
7 | from git.repo import Repo
8 | from packaging import version
9 |
10 | # check if the active branch is master
11 | SCRIPT_ROOT = Path(__file__).parent
12 | repo = Repo(SCRIPT_ROOT)
13 |
14 | assert str(repo.active_branch) == "main", "Checkout to main and try again."
15 | assert len(repo.index.diff(None)) == 0, "There are uncommited changes, aborting."
16 | assert not repo.head.is_detached, "You are on a detached HEAD, aborting."
17 |
18 | # get current Starklings version
19 | path = SCRIPT_ROOT / "pyproject.toml"
20 |
21 | new_starklings_version_str: Optional[str] = None
22 | with open(path, "r+", encoding="UTF-8") as file:
23 | raw_pyproject = file.read()
24 | pyproject = tomli.loads(raw_pyproject)
25 | version_str = pyproject["tool"]["poetry"]["version"]
26 | starklings_version = version.parse(version_str)
27 | print(f"Current Starklings version: {starklings_version}")
28 |
29 | # prompt new Starklings version
30 | new_starklings_version_str = input("Provide the new Starklings version: ")
31 |
32 | # validate new version
33 | match_result = re.compile(r"^\d*\.\d*\.\d*$").match(new_starklings_version_str)
34 | if match_result is None:
35 | print("Invalid syntax")
36 | sys.exit(1)
37 | new_starklings_version = version.parse(new_starklings_version_str)
38 |
39 | if new_starklings_version <= starklings_version:
40 | print(f"New version must be greater than {starklings_version}")
41 | sys.exit(1)
42 |
43 | # update version in starklings.toml
44 | file.seek(0)
45 | file.truncate()
46 | file.write(
47 | raw_pyproject.replace(
48 | f'version = "{starklings_version}"', f'version = "{new_starklings_version}"'
49 | )
50 | )
51 |
52 | assert new_starklings_version_str is not None
53 |
54 | # add commit
55 | repo.git.add("pyproject.toml")
56 | commit = repo.index.commit(f":bookmark: Starklings {new_starklings_version_str}")
57 |
58 | # add tag
59 | tag = repo.create_tag(f"v{new_starklings_version_str}", ref=commit.hexsha)
60 |
61 | # push to main
62 | origin = repo.remote(name="origin")
63 | origin.push()
64 | origin.push(tag.path)
65 |
--------------------------------------------------------------------------------
/exercises/builtins/README.md:
--------------------------------------------------------------------------------
1 | ## Builtins
2 |
3 | Builtins are low-level add-ons that you can import in your Cairo code and implement a specific set of functions in an optimized fashion.
4 | Each builtin is assigned a separate memory location, accessible through regular Cairo memory calls using implicit parameters.
5 | Some builtins have their own struct helpers you can import from `starkware.cairo.common.cairo_builtins`.
6 |
7 | To call a builtin that computes a specific function, simply assign the input using `assert`, magically read the output and finally advance the builtin pointer.
8 |
9 | Example of builtins include:
10 | - output: prints a felt in the output memory
11 | - range_check: verifies a felt value is lower than the range check bound
12 | - pedersen: compute the pedersen hash of two felt inputs (HashBuiltin*)
13 | - bitwise: computes logical bit operation on felts (BitwiseBuiltin*)
14 | - ecdsa: computes signatures on the Stark elliptic curve (SignatureBuiltin*)
15 | - ec_op: computes elliptic curve operation on the Stark curve (EcOpBuiltin*)
16 |
17 | ## Resources:
18 | - https://www.cairo-lang.org/docs/how_cairo_works/builtins.html
19 | - https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/cairo_builtins.cairo
--------------------------------------------------------------------------------
/exercises/builtins/bitwise.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.math import assert_le, assert_nn
4 | from starkware.cairo.common.math_cmp import is_not_zero
5 | from starkware.cairo.common.bitwise import bitwise_and, bitwise_xor, bitwise_or
6 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
7 | from starkware.cairo.common.pow import pow
8 |
9 | # While operations like addition, multiplication and divisions are native for felts,
10 | # bit operations are more difficult to implement for felt.
11 | # The bitwise builtin allows computing logical operations such as XOR, AND and OR on 251-bit felts.
12 |
13 | # Resources:
14 | # - https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/bitwise.cairo
15 | # - https://github.com/starkware-libs/cairo-lang/blob/master/src/starkware/cairo/common/cairo_builtins.cairo#L17
16 |
17 | # I AM NOT DONE
18 |
19 | # TODO: Use a bitwise operation to return the n-th bit of the value parameter
20 |
21 | func get_nth_bit{bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}(value, n) -> (res):
22 | # FILL ME
23 | return (res)
24 | end
25 |
26 | # TODO: Use a bitwise operation to set the n-th bit of the value parameter to 1
27 |
28 | func set_nth_bit{bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}(value, n) -> (res):
29 | # FILL ME
30 | return (res)
31 | end
32 |
33 | # TODO: Use a bitwise operation to toggle the n-th bit of the value parameter
34 |
35 | func toggle_nth_bit{bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}(value, n) -> (res):
36 | # FILL ME
37 | return (res)
38 | end
39 |
40 | # Let's write a unique function that combines all the functions above.
41 | # This function should use the bitwise_ptr explicitly.
42 | # In particular, do not use any of the functions above.
43 |
44 | # TODO: Write a function that takes as argument
45 | # - a felt `op` in ['get', 'set', 'toggle'],
46 | # - felts `value` and `n`,
47 | # and returns the result of the operation applied to `n`-th bit of value.
48 | # Make sure
49 | # - the argument `n` is within the correct bitwise bounds,
50 | # - the `op` argument is correct.
51 |
52 | func op_nth_bit{bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}(op, value, n) -> (res):
53 | alloc_locals
54 |
55 | # Assert op is correct
56 |
57 | with_attr error_message("Bad bitwise bounds"):
58 | # Assert n is within bounds
59 | end
60 |
61 | # Compute the operation
62 | # Don't forget to advance bitwise_ptr
63 |
64 | return (res)
65 | end
66 |
67 | # Do not modify the tests.
68 | @view
69 | func test_get_nth_bit{syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}():
70 | alloc_locals
71 | local value = 0xA8
72 | let (local r0) = get_nth_bit(value, 0)
73 | let (local r1) = get_nth_bit(value, 1)
74 | let (local r2) = get_nth_bit(value, 2)
75 | let (local r3) = get_nth_bit(value, 3)
76 | let (local r4) = get_nth_bit(value, 4)
77 | let (local r5) = get_nth_bit(value, 5)
78 | let (local r6) = get_nth_bit(value, 6)
79 | let (local r7) = get_nth_bit(value, 7)
80 | assert r0 * 2 ** 0 + r1 * 2 ** 1 + r2 * 2 ** 2 + r3 * 2 ** 3 + r4 * 2 ** 4 + r5 * 2 ** 5 + r6 * 2 ** 6 + r7 * 2 ** 7 = value
81 | return ()
82 | end
83 |
84 | @view
85 | func test_set_nth_bit{syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}():
86 | alloc_locals
87 | local value = 0
88 | let (local r0) = set_nth_bit(value, 0)
89 | assert 1 = r0
90 | let (local r1) = set_nth_bit(r0, 1)
91 | assert 3 = r1
92 | let (local r2) = set_nth_bit(r1, 2)
93 | assert 7 = r2
94 | let (local r3) = set_nth_bit(r2, 3)
95 | assert 15 = r3
96 | let (local r4) = set_nth_bit(r3, 4)
97 | assert 31 = r4
98 | let (local r5) = set_nth_bit(r4, 5)
99 | assert 63 = r5
100 | let (local r6) = set_nth_bit(r5, 6)
101 | assert 127 = r6
102 | let (local r7) = set_nth_bit(r6, 7)
103 | assert 255 = r7
104 | return ()
105 | end
106 |
107 | @view
108 | func test_toggle_nth_bit{
109 | syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt
110 | }():
111 | alloc_locals
112 |
113 | local value = nondet %{ int('100000011010111', 2) %}
114 | let (res) = toggle_nth_bit(value, 14)
115 | let (res) = toggle_nth_bit(res, 3)
116 | let (res) = toggle_nth_bit(res, 5)
117 | assert res = 2 ** 8 - 1
118 | return ()
119 | end
120 |
121 | @view
122 | func test_op_nth_bit{syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt}():
123 | alloc_locals
124 | local v0
125 | local v1
126 | local v2
127 | local n0
128 | local n1
129 | local n2
130 | local r0
131 | local r1
132 | local r2
133 | %{
134 | from random import randint
135 | size = 249
136 | ids.v0 = randint(0,2**size) + 2**size
137 | tmpv1 = randint(0,2**size) + 2**size
138 | ids.v2 = randint(0,2**size) + 2**size
139 | ids.n0 = randint(0,size)
140 | ids.n1 = randint(0,size)
141 | ids.n2 = randint(0,size)
142 | ids.v1 = tmpv1 ^ (1 << ids.n1) if ((tmpv1 >> ids.n1) & 1) == 1 else tmpv1
143 | ids.r0 = (ids.v0 >> ids.n0) & 1
144 | ids.r1 = ids.v1 | (1 << ids.n1)
145 | ids.r2 = ids.v2 ^ (1 << ids.n2)
146 | %}
147 | let (val0) = op_nth_bit('get', v0, n0)
148 | assert r0 = val0
149 | let (val1) = op_nth_bit('set', v1, n1)
150 | assert r1 = val1
151 | let (val2) = op_nth_bit('toggle', v2, n2)
152 | assert r2 = val2
153 |
154 | %{ expect_revert() %}
155 | let (_) = op_nth_bit('rigged', v0, n1)
156 | return ()
157 | end
158 |
159 | @view
160 | func test_bitwise_bounds_negative_ko{
161 | syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt
162 | }():
163 | alloc_locals
164 | %{ expect_revert(error_message="Bad bitwise bounds") %}
165 | let (res) = op_nth_bit('set', 1337, -42)
166 | return ()
167 | end
168 |
169 | @view
170 | func test_bitwise_bounds_too_high_ko{
171 | syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr : felt
172 | }():
173 | alloc_locals
174 | %{ expect_revert(error_message="Bad bitwise bounds") %}
175 | let (res) = op_nth_bit('set', 1337, 251)
176 | return ()
177 | end
178 |
--------------------------------------------------------------------------------
/exercises/hints/README.md:
--------------------------------------------------------------------------------
1 | ## Hints
2 |
3 | Cairo is a non-deterministic programming language,
4 | it allows external programs to interact with the execution of a Cairo program.
5 |
6 | A hint is a piece of python code that is inserted in a Cairo program.
7 | Hints are attached to a Cairo instruction by encapsulating the hint code with the syntax:
8 | ```
9 | %{
10 | # Python hint goes here
11 | %}
12 | ```
13 |
14 | For instance, to factor a number, you could let an external prover do the computation for you and verify the result afterwards.
15 | ```
16 | let n = 1337
17 | local p
18 | local q
19 | %{
20 | ids.p = 191
21 | ids.q = 7
22 | %}
23 | assert n = p * q
24 | ```
25 |
26 | Hints are not part of the Cairo bytecode, they are not proven and do not count in the total number of steps.
27 |
28 | More information on how to use hints:
29 | https://www.cairo-lang.org/docs/hello_cairo/program_input.html#hints
30 |
--------------------------------------------------------------------------------
/exercises/hints/hints00.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Hints
4 |
5 | # Hints are the Cairo way to defer part of the program execution to an external program
6 | # Memory can be accessed and assigned inside a hint by using variables identifiers.
7 | # e.g., inside a hint variable `a` is accessed through `ids.a`
8 |
9 | # I AM NOT DONE
10 |
11 | # TODO: Assign the value of `res` inside a hint.
12 |
13 | func basic_hint() -> (value : felt):
14 | alloc_locals
15 | local res
16 | # TODO: Insert hint here
17 | return (res)
18 | end
19 |
20 | # Do not change the test
21 | @external
22 | func test_basic_hint{syscall_ptr : felt*}():
23 | let (value) = basic_hint()
24 | assert 41 = value - 1
25 | return ()
26 | end
27 |
--------------------------------------------------------------------------------
/exercises/hints/hints01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Cairo hints can be useful for delegating heavy computation.
4 | # This pattern is at the very heart of Cairo: verification is much faster than computation.
5 | # However, as hints are not part of the final Cairo bytecode, a malicious program may provide wrong results.
6 | # You should always verify computations done inside hints.
7 |
8 | # I AM NOT DONE
9 |
10 | # TODO: Compute the result of "x modulo n" inside a hint using python's `divmod`
11 | # Don't forget to make sure the result is correct.
12 |
13 | func modulo(x : felt, n : felt) -> (mod : felt):
14 | alloc_locals
15 | local quotient
16 | local remainder
17 | %{
18 | # TODO: Compute the quotient and remainder inside the hint
19 | print(ids.quotient)
20 | print(ids.remainder)
21 | %}
22 | # TODO: verify the result is correct
23 |
24 | return (0)
25 | end
26 |
27 | # Do not change the test
28 | @external
29 | func test_modulo{syscall_ptr : felt*}():
30 | const NUM_TESTS = 19
31 |
32 | %{ import random %}
33 | tempvar count = NUM_TESTS
34 |
35 | loop:
36 | %{
37 | x = random.randint(2, 2**99)
38 | n = random.randint(2, 2**50)
39 | if x < n:
40 | x,n = n,x
41 | %}
42 | tempvar x = nondet %{ x %}
43 | tempvar n = nondet %{ n %}
44 | tempvar res = nondet %{ x % n %}
45 |
46 | let (mod) = modulo(x, n)
47 | assert res = mod
48 | tempvar count = count - 1
49 | jmp loop if count != 0
50 |
51 | return ()
52 | end
53 |
--------------------------------------------------------------------------------
/exercises/implicit_arguments/README.md:
--------------------------------------------------------------------------------
1 | ## Implicit arguments
2 |
3 | Cairo functions takes two types of arguments: the explicit arguments, provided between `(` and `)`, and the implicit arguments, provided between `{` and `}`.
4 |
5 | Read more about implicit arguments here https://starknet.io/docs/how_cairo_works/builtins.html#implicit-arguments.
6 |
--------------------------------------------------------------------------------
/exercises/implicit_arguments/implicit_arguments01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Functions can take implicit arguments. You might have already encountered this with
4 | # syscall_ptr: felt* for example.
5 |
6 | # I AM NOT DONE
7 |
8 | # TODO: fix the "implicit_sum" signature to make the test pass
9 |
10 | func implicit_sum() -> (result : felt):
11 | return (a + b)
12 | end
13 |
14 | # Do not change the test
15 | @external
16 | func test_sum{syscall_ptr : felt*}():
17 | let a = 3
18 | let b = 5
19 | let (sum) = implicit_sum{a=a, b=b}()
20 | assert sum = 8
21 |
22 | return ()
23 | end
24 |
--------------------------------------------------------------------------------
/exercises/implicit_arguments/implicit_arguments02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Implicit arguments are passed down to any subsequent function calls that would require them.
4 | # Make good usage of this feature to pass this exercise!
5 |
6 | # I AM NOT DONE
7 |
8 | # TODO: fix the "child_function_1" and "child_function_2" signatures to make the test pass
9 |
10 | # Do not change the function signature
11 | func parent_function{a, b}() -> (result : felt):
12 | # Do not change the function body
13 | alloc_locals
14 | let (local intermediate_result_1) = child_function_1()
15 | let (local intermediate_result_2) = child_function_2()
16 | return (intermediate_result_1 + intermediate_result_2)
17 | end
18 |
19 | func child_function_1() -> (result : felt):
20 | # Do not change the function body
21 | return (2 * a)
22 | end
23 |
24 | func child_function_2() -> (result : felt):
25 | # Do not change the function body
26 | return (b + 3)
27 | end
28 |
29 | @external
30 | func test_sum{syscall_ptr : felt*}():
31 | let a = 3
32 | let b = 5
33 | with a, b:
34 | let (result) = parent_function()
35 | assert result = 14
36 | end
37 |
38 | return ()
39 | end
40 |
--------------------------------------------------------------------------------
/exercises/implicit_arguments/implicit_arguments03.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # What is really neat with implicit arguments is that they are returned implicitly by any function using them
4 | # This is a very powerful feature of the language since it helps with readability, letting the developer omit
5 | # implicit arguments in the subsequent function calls.
6 |
7 | # I AM NOT DONE
8 |
9 | # TODO: implement the "black_box" function body to make the test pass
10 |
11 | # Do not change the function signature!
12 | func black_box{secret : felt}() -> ():
13 | # Make the magic happen here :)
14 | return ()
15 | end
16 |
17 | # Do not change the test
18 | @external
19 | func test_secret_change{syscall_ptr : felt*}():
20 | let secret = 'no so secret'
21 | with secret:
22 | black_box()
23 | assert secret = 'very secret!'
24 | end
25 |
26 | return ()
27 | end
28 |
--------------------------------------------------------------------------------
/exercises/operations/README.md:
--------------------------------------------------------------------------------
1 | # Felt operations
2 |
3 | A field element (aka felt), is a native type handling all the main number basics in Cairo.
4 |
5 | You can learn more about field elements: [https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#field-elements](https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#field-elements.
6 |
--------------------------------------------------------------------------------
/exercises/operations/operations00.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Felts supports basic math operations.
4 | # Only accepted operators (const excluded) are: +, -, * and /
5 |
6 | # I AM NOT DONE
7 |
8 | # TODO
9 | # Return the solution of (x² + x - 2) / (x - 2)
10 | func poly(x : felt) -> (res : felt):
11 | # FILL ME
12 | return (res=res) # Do not change
13 | end
14 |
15 | # Do not change the test
16 | @external
17 | func test_poly():
18 | let (res) = poly(x=1)
19 | assert res = 0
20 | let (res) = poly(x=3)
21 | assert res = 10
22 | return ()
23 | end
24 |
--------------------------------------------------------------------------------
/exercises/operations/operations01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Felts are integers defined in the range [0 ; P[, all compuations are done modulo P.
4 | # They can be unsigned integers using `let` or signed integers using `const`.
5 | # Exercice resources: https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#field-elements
6 |
7 | # I AM NOT DONE
8 |
9 | # TODO
10 | # Compute a number X which verify X + 1 < X using unsigned int
11 | @external
12 | func test_unsigned_integer():
13 | # FILL ME
14 | let z = x + 1
15 | %{ assert ids.z < ids.x, f'assert failed: {ids.z} >= {ids.x}' %}
16 | return ()
17 | end
18 |
19 | # TODO
20 | # Compute a number Y which verify Y + 1 < Y using signed int
21 | @external
22 | func test_signed_integer():
23 | # FILL ME
24 | const z = y + 1
25 | %{ assert ids.z < ids.y, f'assert failed: {ids.z} >= {ids.y}' %}
26 | return ()
27 | end
28 |
--------------------------------------------------------------------------------
/exercises/operations/operations02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.math import assert_lt_felt, assert_not_zero
4 |
5 | # Felts use prime number property to ensure (x / y) * y = x is always true.
6 | # Since floats are not supported, this can lead to get surprising results.
7 | # Exercice resources: https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#field-elements
8 |
9 | # I AM NOT DONE
10 |
11 | # TODO
12 | # Find a number X which satisfy A / X > A with X in range ]0 ; 100]
13 | func solve(a : felt) -> (x : felt):
14 | # TO FILL
15 | return (x=x)
16 | end
17 |
18 | # Do not change the test
19 | @external
20 | func test_solve{range_check_ptr}():
21 | let a = 347092984475551631116800
22 | let (x) = solve(a=a)
23 | assert_not_zero(x)
24 | assert_lt_felt(x, 101)
25 | assert_lt_felt(a, a / x)
26 | return ()
27 | end
28 |
--------------------------------------------------------------------------------
/exercises/operations/operations03.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.math import assert_lt_felt
4 |
5 | # Felts use prime number property to ensure (x / y) * y = x is always true.
6 | # Even if `let` provide an unsigned integer, it is still possible to manage negative numbers thanks to field element properties.
7 | # Exercice resources: https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#field-elements
8 |
9 | # I AM NOT DONE
10 |
11 | # TODO
12 | # Set the value of x (in the hint) to verify the test
13 | func solve() -> (x : felt):
14 | tempvar x
15 | %{ ids.x = -1 %} # Change only this line to make the test pass
16 | return (x=x)
17 | end
18 |
19 | # Do not change the test
20 | @external
21 | func test_solve{range_check_ptr}():
22 | let (x) = solve()
23 | assert x = -17
24 | return ()
25 | end
26 |
--------------------------------------------------------------------------------
/exercises/recursions/README.md:
--------------------------------------------------------------------------------
1 | # Recursions
2 |
3 | Something noticeable with Cairo is that there is no `for` or `while` loops.
4 |
5 | Loops must be implemented recursively, which is possible and simple in Cairo.
6 |
--------------------------------------------------------------------------------
/exercises/recursions/array01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Arrays can be passed as function arguments in the form of a pointer and a length.
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO: write the "contains" function body that returns 1 if the haystack contains the needle and 0 otherwise.
8 |
9 | from starkware.cairo.common.alloc import alloc
10 |
11 | func contains(needle : felt, haystack : felt*, haystack_len : felt) -> (result : felt):
12 | return (0)
13 | end
14 |
15 | # Do not change the test
16 | @external
17 | func test_contains{syscall_ptr : felt*}():
18 | let (haystack1 : felt*) = alloc()
19 | assert [haystack1] = 1
20 | assert [haystack1 + 1] = 2
21 | assert [haystack1 + 2] = 3
22 | assert [haystack1 + 3] = 4
23 |
24 | let (contains3) = contains(3, haystack1, 4)
25 | assert contains3 = 1
26 |
27 | let (haystack2 : felt*) = alloc()
28 | assert [haystack2] = 1
29 | assert [haystack2 + 1] = 2
30 | let (contains5) = contains(5, haystack2, 2)
31 | assert contains5 = 0
32 | return ()
33 | end
34 |
--------------------------------------------------------------------------------
/exercises/recursions/array02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Getting pointer as function arguments let us modify the values at the memory address of the pointer
4 | # ...or not! Cairo memory is immutable. Therefore you cannot just update a memory cell.
5 |
6 | # I AM NOT DONE
7 |
8 | # TODO: Update the square function – you can change the body and the signature –
9 | # to make it achieve the desired result: returning an array
10 | # with the squared values of the input array.
11 |
12 | from starkware.cairo.common.alloc import alloc
13 |
14 | func square(array : felt*, array_len : felt):
15 | if array_len == 0:
16 | return ()
17 | end
18 |
19 | let squared_item = array[0] * array[0]
20 | assert [array] = squared_item
21 |
22 | return square(array + 1, array_len - 1)
23 | end
24 |
25 | # You can update the test if the function signature changes.
26 | @external
27 | func test_square{syscall_ptr : felt*}():
28 | alloc_locals
29 | let (local array : felt*) = alloc()
30 |
31 | assert [array] = 1
32 | assert [array + 1] = 2
33 | assert [array + 2] = 3
34 | assert [array + 3] = 4
35 |
36 | square(array, 4)
37 |
38 | assert [array] = 1
39 | assert [array + 1] = 4
40 | assert [array + 2] = 9
41 | assert [array + 3] = 16
42 |
43 | return ()
44 | end
45 |
--------------------------------------------------------------------------------
/exercises/recursions/array03.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # There are multiple ways to scan through an array.
4 | # Using recursion, one could go forwards or backwards.
5 |
6 | # I AM NOT DONE
7 |
8 | from starkware.cairo.common.math_cmp import is_le
9 | from starkware.cairo.common.alloc import alloc
10 |
11 | # TODO
12 | # Scan through the array elements from first to last
13 | # Return 1 if elements of the array are in increasing order.
14 | # Return 0 otherwise
15 |
16 | func is_increasing{range_check_ptr : felt}(array : felt*, array_len : felt) -> (res : felt):
17 | if array_len == 0:
18 | return (1)
19 | end
20 |
21 | if array_len == 1:
22 | return (1)
23 | end
24 |
25 | let curr_value = 0
26 | let next_value = 0
27 |
28 | # Do not modify these lines
29 | let (is_sorted) = is_le(curr_value, next_value)
30 | if is_sorted == 1:
31 | return is_increasing(array + 1, array_len - 1)
32 | end
33 |
34 | return (0)
35 | end
36 |
37 | # TODO
38 | # Scan through the array elements from last to first
39 | # Return 1 if elements of the array are in decreasing order.
40 | # Return 0 otherwise
41 |
42 | func is_decreasing{range_check_ptr : felt}(array : felt*, array_len : felt) -> (res : felt):
43 | # FILL ME
44 |
45 | # Do not modify this line
46 | let (is_sorted) = is_le(curr_value, next_value)
47 |
48 | if is_sorted == 1:
49 | return is_decreasing(array, array_len)
50 | end
51 |
52 | return (0)
53 | end
54 |
55 | # TODO
56 | # Use recursion to reverse array in rev_array
57 | # Assume rev_array is already allocated
58 |
59 | func reverse(array : felt*, rev_array : felt*, array_len : felt):
60 | # FILL ME
61 | return ()
62 | end
63 |
64 | # Do not modify the test
65 | @external
66 | func test_is_sorted{syscall_ptr : felt*, range_check_ptr : felt}():
67 | alloc_locals
68 |
69 | local inc_array : felt* = new (1, 2, 3, 4)
70 | local bad_array : felt* = new (1, 2, 69, -11, 0)
71 | local dec_array : felt* = new (10, 9, 8, 7, 6, 5)
72 | let (inc0) = is_increasing(inc_array, 4)
73 | let (inc1) = is_increasing(bad_array, 5)
74 | let (inc2) = is_increasing(dec_array, 6)
75 | assert (inc0, inc1, inc2) = (1, 0, 0)
76 |
77 | let (dec0) = is_decreasing(inc_array, 4)
78 | let (dec1) = is_decreasing(bad_array, 5)
79 | let (dec2) = is_decreasing(dec_array, 6)
80 | assert (dec0, dec1, dec2) = (0, 0, 1)
81 |
82 | return ()
83 | end
84 |
85 | # Do not modify the test
86 | @external
87 | func test_reversed{syscall_ptr : felt*}():
88 | alloc_locals
89 |
90 | local in_array : felt* = new (1, 2, 3, 4, 19, 42)
91 | let (reversed_array : felt*) = alloc()
92 | reverse(in_array, reversed_array, 6)
93 | assert 42 = [reversed_array + 0]
94 | assert 19 = [reversed_array + 1]
95 | assert 4 = [reversed_array + 2]
96 | assert 3 = [reversed_array + 3]
97 | assert 2 = [reversed_array + 4]
98 | assert 1 = [reversed_array + 5]
99 |
100 | local in_array : felt* = new (31337, 1664, 911, 0, -42)
101 | let (reversed_array : felt*) = alloc()
102 | reverse(in_array, reversed_array, 5)
103 | assert -42 = [reversed_array + 0]
104 | assert 0 = [reversed_array + 1]
105 | assert 911 = [reversed_array + 2]
106 | assert 1664 = [reversed_array + 3]
107 | assert 31337 = [reversed_array + 4]
108 |
109 | return ()
110 | end
111 |
--------------------------------------------------------------------------------
/exercises/recursions/array04.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Arrays can also contain structs
4 |
5 | # I AM NOT DONE
6 |
7 | struct Point:
8 | member x : felt
9 | member y : felt
10 | member z : felt
11 | end
12 |
13 | func contains_origin{range_check_ptr : felt}(len_points : felt, points : Point*) -> (bool : felt):
14 | # FILL ME
15 | end
16 |
17 | # TESTS #
18 |
19 | from starkware.cairo.common.alloc import alloc
20 |
21 | @external
22 | func test_contrains_origin{range_check_ptr : felt}():
23 | alloc_locals
24 |
25 | let (local false_array : Point*) = alloc()
26 | assert false_array[0] = Point(1, 2, 3)
27 | assert false_array[1] = Point(2, 2, 2)
28 | assert false_array[2] = Point(42, 27, 11)
29 |
30 | let (res) = contains_origin(3, false_array)
31 | assert res = 0
32 |
33 | let (local true_array : Point*) = alloc()
34 | assert true_array[0] = Point(1, 2, 3)
35 | assert true_array[1] = Point(0, 0, 0)
36 | assert true_array[2] = Point(42, 27, 11)
37 |
38 | let (res) = contains_origin(3, true_array)
39 | assert res = 1
40 |
41 | return ()
42 | end
43 |
--------------------------------------------------------------------------------
/exercises/recursions/collatz_sequence.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 | %builtins bitwise
3 |
4 | from starkware.cairo.common.bitwise import bitwise_and
5 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
6 |
7 | # Collatz sequence are defined as follow :
8 | # If the number is even, divide it by two.
9 | # If the number is odd, triple it and add one.
10 | # If the number is one, stop the computation
11 | # https://en.wikipedia.org/wiki/Collatz_conjecture
12 |
13 | # I AM NOT DONE
14 |
15 | # TODO: write a recursive implementation of Collatz sequence that returns the nth collatz number from the seed
16 | # HELP: number % 2 == 0 => bitwise_and(number, 1) == 0
17 |
18 | func collatz{bitwise_ptr : BitwiseBuiltin*}(seed : felt, step : felt) -> (result : felt):
19 | return (result=number)
20 | end
21 |
22 | # Do not change the test
23 | @external
24 | func test_collatz{syscall_ptr : felt*, bitwise_ptr : BitwiseBuiltin*}():
25 | let (n) = collatz(seed=42, step=0)
26 | assert n = 42
27 | let (n) = collatz(seed=42, step=1)
28 | assert n = 21
29 | let (n) = collatz(seed=42, step=2)
30 | assert n = 64
31 | let (n) = collatz(seed=42, step=3)
32 | assert n = 32
33 | let (n) = collatz(seed=42, step=4)
34 | assert n = 16
35 | let (n) = collatz(seed=42, step=5)
36 | assert n = 8
37 | let (n) = collatz(seed=42, step=6)
38 | assert n = 4
39 | let (n) = collatz(seed=42, step=7)
40 | assert n = 2
41 | let (n) = collatz(seed=42, step=8)
42 | assert n = 1
43 | let (n) = collatz(seed=42, step=9)
44 | assert n = 1
45 | let (n) = collatz(seed=42, step=42)
46 | assert n = 1
47 | return ()
48 | end
49 |
--------------------------------------------------------------------------------
/exercises/recursions/recursion01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Fibonacci numbers are defined by the following recurrence:
4 | # F(0) = 0
5 | # F(1) = 1
6 | # F(n) = F(n-1) + F(n-2)
7 |
8 | # I AM NOT DONE
9 |
10 | # TODO: write a recursive implementation of fibonacci numbers that returns the nth fibonacci number
11 |
12 | func fibonacci(n : felt) -> (result : felt):
13 | return (0)
14 | end
15 |
16 | # Do not change the test
17 | @external
18 | func test_fibonacci{syscall_ptr : felt*}():
19 | let (n) = fibonacci(0)
20 | assert n = 0
21 | let (n) = fibonacci(1)
22 | assert n = 1
23 | let (n) = fibonacci(7)
24 | assert n = 13
25 | let (n) = fibonacci(10)
26 | assert n = 55
27 | return ()
28 | end
29 |
--------------------------------------------------------------------------------
/exercises/recursions/struct01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Structs are nothing more than a continuous list of felts in memory. So are arrays.
4 | #
5 | # In other words, a felt* can point to an element of an array, but also
6 | # to an element of a struct. It is therefore possible to access the next element of a
7 | # struct by incrementing the pointer, just like with arrays.
8 | #
9 | # It is also worth noting that a struct pointer implicitely casts to a felt pointer
10 | # (eg. Foo* is automatically casted to felt*)
11 |
12 | # I AM NOT DONE
13 |
14 | struct Vector2D:
15 | member x : felt
16 | member y : felt
17 | end
18 |
19 | struct Vector3D:
20 | member x : felt
21 | member y : felt
22 | member z : felt
23 | end
24 |
25 | # Returns the squared magnitude of a vector.
26 | # Examples:
27 | # - the squared magnitude of a 2D vector (x,y) is x * x + y * y.
28 | # - the squared magnitude of a 3D vector (x,y,z) is x * x + y * y + z * z.
29 | func squared_magnitude(struct_value : felt*, struct_size : felt) -> (res : felt):
30 | # FILL ME
31 | end
32 |
33 | # TESTS #
34 |
35 | @external
36 | func test_squared_magnitude{range_check_ptr : felt}():
37 | tempvar vector2D : Vector2D* = new Vector2D(x=4, y=7)
38 | let (res) = squared_magnitude(vector2D, Vector2D.SIZE)
39 | assert res = 4 * 4 + 7 * 7
40 |
41 | tempvar vector3D : Vector3D* = new Vector3D(x=8, y=2, z=1)
42 | let (res) = squared_magnitude(vector3D, Vector3D.SIZE)
43 | assert res = 8 * 8 + 2 * 2 + 1 * 1
44 |
45 | return ()
46 | end
47 |
--------------------------------------------------------------------------------
/exercises/registers/registers00.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # I AM NOT DONE
4 |
5 | # Ressources
6 | # https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#registers
7 | # https://www.cairo-lang.org/docs/how_cairo_works/functions.html#function-arguments-and-return-values
8 |
9 | # TODO
10 | # Rewrite this function body in a high level syntax
11 | @external
12 | func ret_42() -> (r : felt):
13 | # [ap] = 42; ap++
14 | # ret
15 | end
16 |
17 | # TODO
18 | # Rewrite this function body in a low level syntax, using registers
19 | @external
20 | func ret_0_and_1() -> (zero : felt, one : felt):
21 | # return (0, 1)
22 | end
23 |
24 | #########
25 | # TESTS #
26 | #########
27 |
28 | @external
29 | func test_ret_42():
30 | let (r) = ret_42()
31 | assert r = 42
32 |
33 | return ()
34 | end
35 |
36 | @external
37 | func test_0_and_1():
38 | let (zero, one) = ret_0_and_1()
39 | assert zero = 0
40 | assert one = 1
41 |
42 | return ()
43 | end
44 |
--------------------------------------------------------------------------------
/exercises/registers/registers01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # I AM NOT DONE
4 |
5 | # Resource
6 | # https://www.cairo-lang.org/docs/how_cairo_works/functions.html#function-arguments-and-return-values
7 |
8 | # TODO
9 | # Rewrite this function with a high level syntax
10 | @external
11 | func assert_is_42(n : felt):
12 | # [ap - 3] = 42
13 | # ret
14 | end
15 |
16 | # TODO
17 | # Rewrite this function with a low level syntax, using registers
18 | @external
19 | func sum(a : felt, b : felt) -> (s : felt):
20 | # return (a + b)
21 | end
22 |
23 | #########
24 | # TESTS #
25 | #########
26 |
27 | @external
28 | func test_assert_is_42_ok():
29 | assert_is_42(42)
30 | return ()
31 | end
32 |
33 | @external
34 | func test_assert_is_42_ko():
35 | %{ expect_revert() %}
36 | assert_is_42(21)
37 | return ()
38 | end
39 |
40 | @external
41 | func test_sum():
42 | let (s) = sum(2, 3)
43 | assert s = 5
44 | return ()
45 | end
46 |
--------------------------------------------------------------------------------
/exercises/registers/registers02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # I AM NOT DONE
4 |
5 | # Cairo memory is immutable.
6 | # Once a memory cell has been assigned, its value CANNOT be changed.
7 | # The program will crash if someone tries to assign a new, different, value to an already initialized memory cell.
8 | # However, trying to assign a memory cell twice, or more, **with the same value** won't cause any harm.
9 | # This property can be used to assert the value of a cell.
10 |
11 | # TODO
12 | # Rewrite this function in a high level syntax, using tempvar and assert
13 | func crash():
14 | # [ap] = 42; ap++
15 | # [ap - 1] = 42
16 | # [ap - 1] = 21
17 |
18 | ret
19 | end
20 |
21 | # TODO
22 | # Rewrite this funtion in a low level syntax
23 | func assert_42(number : felt):
24 | # assert number = 42
25 |
26 | return ()
27 | end
28 |
29 | # TODO
30 | # Write this function body so:
31 | # if the memory cell pointed by `p_number` is not initialized, set it to 42
32 | # else, if the value is initialized and different from 42, crash
33 | # else, do nothing and return
34 | func assert_pointer_42(p_number : felt*):
35 | return ()
36 | end
37 |
38 | # TODO
39 | # Write this function body so:
40 | # if the memory cell pointed by `p_number` is set to 42, do nothing and return
41 | # else crash
42 | func assert_pointer_42_no_set(p_number : felt*):
43 | return ()
44 | end
45 |
46 | #########
47 | # TESTS #
48 | #########
49 |
50 | from starkware.cairo.common.alloc import alloc
51 |
52 | @external
53 | func test_crash():
54 | %{ expect_revert() %}
55 | crash()
56 |
57 | return ()
58 | end
59 |
60 | @external
61 | func test_assert_42():
62 | assert_42(42)
63 |
64 | %{ expect_revert() %}
65 | assert_42(21)
66 |
67 | return ()
68 | end
69 |
70 | @external
71 | func test_assert_pointer_42_initialized():
72 | let (mem_zone : felt*) = alloc()
73 | assert mem_zone[0] = 42
74 | assert mem_zone[1] = 21
75 |
76 | assert_pointer_42(mem_zone)
77 |
78 | %{ expect_revert() %}
79 | assert_pointer_42(mem_zone + 1)
80 |
81 | return ()
82 | end
83 |
84 | @external
85 | func test_assert_pointer_42_not_initialized_ok():
86 | let (mem_zone : felt*) = alloc()
87 | assert mem_zone[0] = 42
88 | assert_pointer_42(mem_zone)
89 |
90 | assert_pointer_42(mem_zone + 1)
91 | assert mem_zone[1] = 42
92 |
93 | return ()
94 | end
95 |
96 | @external
97 | func test_assert_pointer_42_not_initialized_revert():
98 | let (mem_zone : felt*) = alloc()
99 | assert mem_zone[0] = 42
100 | assert_pointer_42(mem_zone)
101 |
102 | assert_pointer_42(mem_zone + 1)
103 | %{ expect_revert() %}
104 | assert mem_zone[1] = 21
105 |
106 | return ()
107 | end
108 |
109 | @external
110 | func test_assert_pointer_42_no_set():
111 | let (mem_zone : felt*) = alloc()
112 | assert mem_zone[0] = 42
113 | assert mem_zone[1] = 21
114 |
115 | assert_pointer_42_no_set(mem_zone)
116 |
117 | %{ expect_revert() %}
118 | assert_pointer_42_no_set(mem_zone + 1)
119 |
120 | return ()
121 | end
122 |
123 | @external
124 | func test_assert_pointer_42_no_set_crash():
125 | let (mem_zone : felt*) = alloc()
126 |
127 | %{ expect_revert() %}
128 | assert_pointer_42_no_set(mem_zone)
129 |
130 | return ()
131 | end
132 |
--------------------------------------------------------------------------------
/exercises/registers/registers03.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 | from starkware.cairo.common.math_cmp import is_le
3 |
4 | # I AM NOT DONE
5 |
6 | # TODO
7 | # Rewrite those functions with a high level syntax
8 | @external
9 | func sum_array(array_len : felt, array : felt*) -> (sum : felt):
10 | # [ap] = [fp - 4]; ap++
11 | # [ap] = [fp - 3]; ap++
12 | # [ap] = 0; ap++
13 | # call rec_sum_array
14 | # ret
15 | end
16 |
17 | func rec_sum_array(array_len : felt, array : felt*, sum : felt) -> (sum : felt):
18 | # jmp continue if [fp - 5] != 0
19 |
20 | # stop:
21 | # [ap] = [fp - 3]; ap++
22 | # jmp done
23 |
24 | # continue:
25 | # [ap] = [[fp - 4]]; ap++
26 | # [ap] = [fp - 5] - 1; ap++
27 | # [ap] = [fp - 4] + 1; ap++
28 | # [ap] = [ap - 3] + [fp - 3]; ap++
29 | # call rec_sum_array
30 |
31 | # done:
32 | # ret
33 | end
34 |
35 | # TODO
36 | # Rewrite this function with a low level syntax
37 | # It's possible to do it with only registers, labels and conditional jump. No reference or localvar
38 | @external
39 | func max{range_check_ptr}(a : felt, b : felt) -> (max : felt):
40 | # let (res) = is_le(a, b)
41 | # if res == 1:
42 | # return (b)
43 | # else:
44 | # return (a)
45 | # end
46 | end
47 |
48 | #########
49 | # TESTS #
50 | #########
51 |
52 | from starkware.cairo.common.alloc import alloc
53 |
54 | @external
55 | func test_max{range_check_ptr}():
56 | let (m) = max(21, 42)
57 | assert m = 42
58 | let (m) = max(42, 21)
59 | assert m = 42
60 | return ()
61 | end
62 |
63 | @external
64 | func test_sum():
65 | let (array) = alloc()
66 | assert array[0] = 1
67 | assert array[1] = 2
68 | assert array[2] = 3
69 | assert array[3] = 4
70 | assert array[4] = 5
71 | assert array[5] = 6
72 | assert array[6] = 7
73 | assert array[7] = 8
74 | assert array[8] = 9
75 | assert array[9] = 10
76 |
77 | let (s) = sum_array(10, array)
78 | assert s = 55
79 |
80 | return ()
81 | end
82 |
--------------------------------------------------------------------------------
/exercises/registers/registers04.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Felts supports basic math operations.
4 | # High level syntax allows one-line multiple operations while low level syntax doesn't.
5 | # Exercice source: https://www.cairo-lang.org/docs/how_cairo_works/cairo_intro.html#field-elements
6 |
7 | # I AM NOT DONE
8 |
9 | # TODO
10 | # Write this function body in a high level syntax
11 | func poly_high_level(x : felt) -> (res : felt):
12 | # return x³ + 23x² + 45x + 67 according to x
13 | return (res=res) # Do not change
14 | end
15 |
16 | # TODO
17 | # Write this function body in a low level syntax (result must be stored in [ap - 1] before ret)
18 | func poly_low_level(x : felt):
19 | # return x³ + 23x² + 45x + 67 according to x
20 | ret # Do not change
21 | end
22 |
23 | # Do not change the test
24 | @external
25 | func test_poly():
26 | poly_low_level(x=100)
27 | assert [ap - 1] = 1234567
28 | let (high_level_res) = poly_high_level(x=100)
29 | assert high_level_res = 1234567
30 | return ()
31 | end
32 |
--------------------------------------------------------------------------------
/exercises/revoked_references/revoked_references01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # References in Cairo are like aliases to specific memory cells pointed by ap
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO: complete the bar function to make the test pass
8 | # You will encounter a "revoked reference" error
9 | # https://www.cairo-lang.org/docs/how_cairo_works/consts.html#revoked-references
10 |
11 | from starkware.cairo.common.cairo_builtins import HashBuiltin
12 | from starkware.cairo.common.hash import hash2
13 |
14 | func foo(n):
15 | if n == 0:
16 | return ()
17 | end
18 | foo(n=n - 1)
19 | return ()
20 | end
21 |
22 | func bar{hash_ptr : HashBuiltin*}():
23 | hash2(1, 2) # Do not change
24 | foo(3) # Do not change
25 |
26 | # Insert something here to make the test pass
27 |
28 | hash2(3, 4) # Do not change
29 | return () # Do not change
30 | end
31 |
32 | # Do not change the test
33 | @external
34 | func test_bar{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
35 | bar{hash_ptr=pedersen_ptr}()
36 |
37 | return ()
38 | end
39 |
--------------------------------------------------------------------------------
/exercises/storage/storage01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Starknet provide persistent and mutable storage
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO
8 | # Create a storage named `bool` storing a single felt
9 |
10 | # TESTS #
11 |
12 | from starkware.cairo.common.cairo_builtins import HashBuiltin
13 |
14 | @external
15 | func test_store_bool{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
16 | let (x) = bool.read()
17 | assert x = 0
18 |
19 | bool.write(1)
20 |
21 | let (x) = bool.read()
22 | assert x = 1
23 |
24 | return ()
25 | end
26 |
--------------------------------------------------------------------------------
/exercises/storage/storage02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Starknet storage can be though about as a hashmap
4 |
5 | # I AM NOT DONE
6 |
7 | struct Id:
8 | member age : felt
9 | member height : felt
10 | member married : felt
11 | end
12 |
13 | # TODO
14 | # Create a storage named wallet, mapping a felt to another
15 | # Create a storage named height_map, mapping two felts to another
16 | # Create a storage named id, mapping a felt to an Id
17 |
18 | # TESTS #
19 |
20 | from starkware.cairo.common.cairo_builtins import HashBuiltin
21 |
22 | @external
23 | func test_wallet{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
24 | let (x) = wallet.read(0)
25 | assert x = 0
26 |
27 | wallet.write(0, 100)
28 |
29 | let (x) = wallet.read(0)
30 | assert x = 100
31 |
32 | return ()
33 | end
34 |
35 | @external
36 | func test_height_map{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
37 | let (z) = height_map.read(0, 0)
38 | assert z = 0
39 |
40 | height_map.write(0, 0, 5)
41 |
42 | let (z) = height_map.read(0, 0)
43 | assert z = 5
44 |
45 | return ()
46 | end
47 |
48 | @external
49 | func test_id{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
50 | let (mr_smith) = id.read(0)
51 | assert mr_smith = Id(0, 0, 0)
52 |
53 | id.write(0, Id(37, 185, 0))
54 |
55 | let (mr_smith) = id.read(0)
56 | assert mr_smith = Id(37, 185, 0)
57 |
58 | return ()
59 | end
60 |
--------------------------------------------------------------------------------
/exercises/storage/storage03.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # You can update stored values using externals, or just consult them (for free) using views
4 |
5 | # I AM NOT DONE
6 |
7 | @storage_var
8 | func bool() -> (bool : felt):
9 | end
10 |
11 | @external
12 | func toggle{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
13 | # TODO
14 | # Implement toggle external
15 | end
16 |
17 | @view
18 | func view_bool{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}() -> (
19 | bool : felt
20 | ):
21 | # TODO
22 | # Implement view_bool
23 | end
24 |
25 | # TESTS #
26 |
27 | from starkware.cairo.common.cairo_builtins import HashBuiltin
28 |
29 | @external
30 | func test_toggle_and_view{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
31 | let (x) = view_bool()
32 | assert x = 0
33 |
34 | toggle()
35 |
36 | let (x) = view_bool()
37 | assert x = 1
38 |
39 | toggle()
40 |
41 | let (x) = view_bool()
42 | assert x = 0
43 |
44 | return ()
45 | end
46 |
--------------------------------------------------------------------------------
/exercises/strings/README.md:
--------------------------------------------------------------------------------
1 | ## Short strings
2 |
3 | Short strings are a way to encode an ASCII string as a felt.
4 | A short string can be defined by converting an ASCII string to its hexadecimal encoding.
5 | The hexadecimal value can also be converted to decimal.
6 | Short string are thus limited to 31 characters to fit into a felt.
7 | The following code show equivalent ways of defining the same short string.
8 | ```
9 | let s = 'Hello'
10 | let s = 0x48656c6c6f
11 | let s = 310939249775
12 | ```
13 |
14 | Read more about short strings here https://www.cairo-lang.org/docs/how_cairo_works/consts.html#short-string-literals.
15 |
16 | Strings of arbitrary size can be constructed from short strings, an example implementation can be found here https://github.com/topology-gg/caistring
--------------------------------------------------------------------------------
/exercises/strings/strings00.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Cairo supports short strings which are encoded as ASCII under the hood
4 | # The felt is the decimal representation of the string in hexadecimal ASCII
5 | # e.g. let hello_string = 'Hello'
6 | # let hello_felt = 310939249775
7 | # let hello_hex = 0x48656c6c6f
8 | # https://www.cairo-lang.org/docs/how_cairo_works/consts.html#short-string-literals
9 |
10 | # I AM NOT DONE
11 |
12 | # TODO: Fix the say_hello function by returning the appropriate short strings
13 |
14 | func say_hello() -> (hello_string : felt, hello_felt : felt, hello_hex : felt):
15 | # FILL ME
16 | return (hello_string, hello_felt, hello_hex)
17 | end
18 |
19 | # Do not change the test
20 | @external
21 | func test_say_hello{syscall_ptr : felt*}():
22 | let (user_string, user_felt, user_hex) = say_hello()
23 | assert user_string = 'Hello Starklings'
24 | assert user_felt = 12011164701440182822452181791570417168947
25 | assert user_hex = 0x627569646c20627569646c20627569646c
26 | return ()
27 | end
28 |
--------------------------------------------------------------------------------
/exercises/strings/strings01.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Short strings really are felts in disguise, and support the same basic operations
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO: Find the key to decode the string that passes the test
8 |
9 | func decode_cipher1() -> (plaintext : felt):
10 | let ciphertext = 'Another One Bites The Dust'
11 | let key = 0
12 | let plaintext = ciphertext + key
13 | return (plaintext)
14 | end
15 |
16 | # TODO: Find the correct ciphertext that passes the test
17 |
18 | func decode_cipher2() -> (plaintext : felt):
19 | let ciphertext = 0
20 | let plaintext = 1337 * ciphertext + 0xc0de
21 | return (plaintext)
22 | end
23 |
24 | # Do not change the test
25 | @external
26 | func test_decode_string{syscall_ptr : felt*}():
27 | # The correct key should produce the corresponding plaintext
28 | let (decoded_string) = decode_cipher1()
29 | assert decoded_string = 'Twinkle Twinkle Little Star'
30 |
31 | # The correct ciphertext should produce corresponding plaintext
32 | let (decoded_string) = decode_cipher2()
33 | assert decoded_string = 'Magic Starknet Money'
34 |
35 | return ()
36 | end
37 |
--------------------------------------------------------------------------------
/exercises/syntax/README.md:
--------------------------------------------------------------------------------
1 | # Starknet syntax
2 |
3 | Starknet uses the Cairo language to write smart contracts code.
4 |
5 | You can learn more about the very basic syntax here: [https://starknet.io/docs/hello_starknet/intro.html](https://starknet.io/docs/hello_starknet/intro.html).
6 |
--------------------------------------------------------------------------------
/exercises/syntax/syntax01.cairo:
--------------------------------------------------------------------------------
1 | # All Starknet files must start with a specific line indicating the file is a smart contract,
2 | # not just a regular Cairo file
3 |
4 | # I AM NOT DONE
5 |
6 | # TODO: add the Starknet file specifier at the beginning of the file
7 |
8 | # You can ignore what follows for now
9 | @external
10 | func test_ok():
11 | return ()
12 | end
13 |
--------------------------------------------------------------------------------
/exercises/syntax/syntax02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Starknet provides a module management system.
4 | # It is very similar to the Python's one.
5 |
6 | # I AM NOT DONE
7 |
8 | # TODO: add the module imports needed to make the test pass!
9 |
10 | # You can ignore what follows for now
11 | @external
12 | func test_ok{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
13 | return ()
14 | end
15 |
--------------------------------------------------------------------------------
/exercises/syntax/syntax03.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Functions can take arguments and return results
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO: make the test pass!
8 |
9 | func takes_two_arguments_and_returns_one() -> ():
10 | return (a + b) # Do not change
11 | end
12 |
13 | # You could be tempted to change the test to make it pass, but don't?
14 | @external
15 | func test_sum{syscall_ptr : felt*}():
16 | let (sum) = takes_two_arguments_and_returns_one(1, 2)
17 | assert sum = 3
18 | return ()
19 | end
20 |
--------------------------------------------------------------------------------
/exercises/syntax/syntax04.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Function and other definitions can be scoped in namespaces, making the code more readable.
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO: make the test pass!
8 |
9 | # Do not change anything but the test
10 | namespace my_namespace:
11 | func returns_something() -> (result : felt):
12 | return (42)
13 | end
14 | end
15 |
16 | # Change the following test to make it pass
17 | @external
18 | func test_hello{syscall_ptr : felt*}():
19 | let (result) = returns_something()
20 | assert result = 42
21 | return ()
22 | end
23 |
--------------------------------------------------------------------------------
/exercises/syntax/syntax05.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # As with many other languages, you can describe object structures with the struct keyword.
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO: declare the Currency struct to make the test pass
8 |
9 | # Do not change the test
10 | @external
11 | func test_currency_sum{syscall_ptr : felt*}():
12 | alloc_locals
13 | local euro : Currency = Currency('Euro', 2)
14 | assert euro.name = 'Euro'
15 | return ()
16 | end
17 |
--------------------------------------------------------------------------------
/exercises/tricks/assert_bool.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Boolean assertions, such as "x OR y" for boolean felts, can also be implemented without conditionals.
4 |
5 | # I AM NOT DONE
6 |
7 | # TODO Implement the following boolean asserts without "if"
8 |
9 | func assert_or(x, y):
10 | # FILL ME
11 | return ()
12 | end
13 |
14 | func assert_and(x, y):
15 | # FILL ME
16 | return ()
17 | end
18 |
19 | func assert_nor(x, y):
20 | # FILL ME
21 | return ()
22 | end
23 |
24 | func assert_xor(x, y):
25 | # FILL ME
26 | return ()
27 | end
28 |
29 | # Do not modify the tests
30 | @external
31 | func test_assert_or():
32 | assert_or(0, 1)
33 | assert_or(1, 0)
34 | assert_or(1, 1)
35 | return ()
36 | end
37 |
38 | @external
39 | func test_assert_or_ko():
40 | %{ expect_revert() %}
41 | assert_or(0, 0)
42 | return ()
43 | end
44 |
45 | @external
46 | func test_assert_and():
47 | assert_and(1, 1)
48 | return ()
49 | end
50 |
51 | @external
52 | func test_assert_and_ko1():
53 | %{ expect_revert() %}
54 | assert_and(0, 0)
55 | return ()
56 | end
57 |
58 | @external
59 | func test_assert_and_ko2():
60 | %{ expect_revert() %}
61 | assert_and(0, 1)
62 | return ()
63 | end
64 |
65 | @external
66 | func test_assert_and_ko3():
67 | %{ expect_revert() %}
68 | assert_and(1, 0)
69 | return ()
70 | end
71 |
72 | @external
73 | func test_assert_nor():
74 | assert_nor(0, 0)
75 | return ()
76 | end
77 |
78 | @external
79 | func test_assert_nor_ko1():
80 | %{ expect_revert() %}
81 | assert_nor(0, 1)
82 | return ()
83 | end
84 |
85 | @external
86 | func test_assert_nor_ko2():
87 | %{ expect_revert() %}
88 | assert_nor(1, 0)
89 | return ()
90 | end
91 |
92 | @external
93 | func test_assert_nor_ko3():
94 | %{ expect_revert() %}
95 | assert_nor(1, 1)
96 | return ()
97 | end
98 |
99 | @external
100 | func test_assert_xor():
101 | assert_xor(0, 1)
102 | assert_xor(1, 0)
103 | return ()
104 | end
105 |
106 | @external
107 | func test_assert_xor_ko():
108 | %{ expect_revert() %}
109 | assert_xor(0, 0)
110 | return ()
111 | end
112 |
113 | @external
114 | func test_assert_xor_ko2():
115 | %{ expect_revert() %}
116 | assert_xor(1, 1)
117 | return ()
118 | end
119 |
--------------------------------------------------------------------------------
/exercises/tricks/inline_if.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.math_cmp import is_not_zero
4 | from starkware.cairo.common.bool import TRUE, FALSE
5 |
6 | # Sometimes, conditionals can be avoided by using a polynomial that maps valid inputs to 0
7 | # Use this trick to rewrite functions without "if"
8 |
9 | # I AM NOT DONE
10 |
11 | # TODO: Implement a ternary operator `if cond then return val_true else return val_false`
12 | # Make sure the condition is a boolean
13 |
14 | func if_then_else(cond : felt, val_true : felt, val_false) -> (res : felt):
15 | # FILL ME
16 | return (res)
17 | end
18 |
19 | @external
20 | func test_ternary_conditional_operator():
21 | let (res) = if_then_else(FALSE, 911, 420)
22 | assert 420 = res
23 | let (res) = if_then_else(TRUE, 911, 'over 9000')
24 | assert 911 = res
25 | let (res) = if_then_else(FALSE, 69420, 1559)
26 | assert 1559 = res
27 | let (res) = if_then_else(TRUE, 'nice', 69)
28 | assert 'nice' = res
29 | %{ expect_revert() %}
30 | let (res) = if_then_else(69, 'nope', 911)
31 | return ()
32 | end
33 |
--------------------------------------------------------------------------------
/exercises/tricks/no_conditionals.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | from starkware.cairo.common.math_cmp import is_not_zero
4 |
5 | # Sometimes, conditionals can be avoided by using an expression that
6 | # - maps valid inputs to 1,
7 | # - and/or maps invalid inputs to 0.
8 | # For instance (row - 2) * (row - 4) is 0 only when row is 2 or 4.
9 |
10 | # Use this trick to rewrite functions without "if" conditions
11 | # Note: This helps to avoid dealing with revoked references.
12 |
13 | # I AM NOT DONE
14 |
15 | func is_binary_if(x : felt) -> (res : felt):
16 | if x == 0:
17 | return (1)
18 | end
19 | if x == 1:
20 | return (1)
21 | end
22 | return (0)
23 | end
24 |
25 | # TODO: Return the right value to mimick the behavior of is_binary_if
26 |
27 | func is_binary_no_if(x : felt) -> (res : felt):
28 | # FILL ME
29 | return (res)
30 | end
31 |
32 | # TODO: Fix the function so that
33 | # - it returns the string 'cool' if x is 1337, 69420, 42
34 | # - it returns 'meh' on any other input
35 |
36 | func is_cool(x : felt) -> (res : felt):
37 | # FILL ME
38 | return (res)
39 | end
40 |
41 | # Do not change the test
42 | @external
43 | func test_is_binary{syscall_ptr : felt*}():
44 | let (eval_if) = is_binary_if(0)
45 | let (eval_no_if) = is_binary_no_if(0)
46 | assert (eval_if, eval_no_if) = (1, 1)
47 |
48 | let (eval_if) = is_binary_if(1)
49 | let (eval_no_if) = is_binary_no_if(1)
50 | assert (eval_if, eval_no_if) = (1, 1)
51 |
52 | let (eval_if) = is_binary_if(13)
53 | let (eval_no_if) = is_binary_no_if(37)
54 | assert (eval_if, eval_no_if) = (0, 0)
55 | return ()
56 | end
57 |
58 | @external
59 | func test_is_cool{syscall_ptr : felt*}():
60 | let (is_1337_cool) = is_cool(1337)
61 | let (is_69420_cool) = is_cool(69420)
62 | let (is_42_cool) = is_cool(42)
63 | let (is_0_cool) = is_cool(0)
64 | let (is_911_cool) = is_cool(911)
65 | let results = ('cool', 'cool', 'cool', 'meh', 'meh')
66 | assert (is_1337_cool, is_69420_cool, is_42_cool, is_0_cool, is_911_cool) = results
67 | return ()
68 | end
69 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | echo Installing starklings
5 |
6 | STARKLINGS_DIR=${STARKLINGS_DIR-"$HOME/.starklings"}
7 | mkdir -p "$STARKLINGS_DIR"
8 |
9 |
10 | PLATFORM="$(uname -s)"
11 | case $PLATFORM in
12 | Linux)
13 | PLATFORM="Linux"
14 | ;;
15 | Darwin)
16 | PLATFORM="macOS"
17 | ;;
18 | *)
19 | echo "unsupported platform: $PLATFORM"
20 | exit 1
21 | ;;
22 | esac
23 |
24 | STARKLINGS_REPO="https://github.com/onlydustxyz/starklings"
25 |
26 | echo Retrieving the latest version from $STARKLINGS_REPO...
27 |
28 | LATEST_RELEASE=$(curl -L -s -H 'Accept: application/json' "${STARKLINGS_REPO}/releases/latest")
29 | LATEST_VERSION=$(echo $LATEST_RELEASE | sed -e 's/.*"tag_name":"\([^"]*\)".*/\1/')
30 |
31 | echo Using version $LATEST_VERSION
32 |
33 | LATEST_RELEASE_URL="${STARKLINGS_REPO}/releases/download/${LATEST_VERSION}"
34 | STARKLINGS_TARBALL_NAME="starklings-${PLATFORM}.tar.gz"
35 | TARBALL_DOWNLOAD_URL="${LATEST_RELEASE_URL}/${STARKLINGS_TARBALL_NAME}"
36 |
37 | echo "Downloading starklings from ${TARBALL_DOWNLOAD_URL}"
38 | curl -L $TARBALL_DOWNLOAD_URL | tar -xvzC $STARKLINGS_DIR
39 |
40 | STARKLINGS_BINARY_DIR="${STARKLINGS_DIR}/dist/starklings"
41 | STARKLINGS_BINARY="${STARKLINGS_BINARY_DIR}/starklings"
42 | chmod +x $STARKLINGS_BINARY
43 |
44 | case $SHELL in
45 | */zsh)
46 | PROFILE=$HOME/.zshrc
47 | PREF_SHELL=zsh
48 | ;;
49 | */bash)
50 | PROFILE=$HOME/.bashrc
51 | PREF_SHELL=bash
52 | ;;
53 | */fish)
54 | PROFILE=$HOME/.config/fish/config.fish
55 | PREF_SHELL=fish
56 | ;;
57 | *)
58 | echo "error: could not detect shell, manually add ${STARKLINGS_BINARY_DIR} to your PATH."
59 | exit 1
60 | esac
61 |
62 | if [[ ":$PATH:" != *":${STARKLINGS_BINARY_DIR}:"* ]]; then
63 | echo >> $PROFILE && echo "export PATH=\"\$PATH:$STARKLINGS_BINARY_DIR\"" >> $PROFILE
64 | fi
65 |
66 | echo && echo "Detected your preferred shell is ${PREF_SHELL} and added starklings to PATH. Run 'source ${PROFILE}' or start a new terminal session to use starklings."
67 | echo "Then, simply run 'starklings --help' "
68 |
--------------------------------------------------------------------------------
/lib/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/lib/.gitkeep
--------------------------------------------------------------------------------
/protostar.toml:
--------------------------------------------------------------------------------
1 | ["protostar.config"]
2 | protostar_version = "0.1.0"
3 |
4 | ["protostar.project"]
5 | libs_path = "lib"
6 |
7 | ["protostar.contracts"]
8 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | authors = ["OnlyDust"]
3 | description = "An interactive tutorial to get you up and running with Starknet"
4 | name = "starklings"
5 | version = "1.4.0"
6 |
7 | [tool.poetry.dependencies]
8 | argparse = "^1.4.0"
9 | black = "^22.3.0"
10 | cairo-lang = {url = "https://files.pythonhosted.org/packages/c0/f6/c850604895a2ce5ff3ef77cdb470b6b0ef50889645748a748e18a1c2269e/cairo-lang-0.8.1.post1.zip#sha256=b3c1a23078ba4e0c8ec45d2cd2ba4873ad70b6723dfba70f70c978c9723ff6eb"}
11 | colorama = "^0.4.4"
12 | openzeppelin-cairo-contracts = "^0.1.0"
13 | pyinstaller = "ˆ5.0.1"
14 | pylint = "^2.13.8"
15 | pytest = "^7.1.2"
16 | pytest-mock = "^3.7.0"
17 | python = ">=3.8 <3.9"
18 | sentry-sdk = "^1.5.12"
19 | starklings-protostar = "^0.2.1"
20 | watchdog = "^2.1.7"
21 | rich = "^12.4.4"
22 | requests = "^2.28.0"
23 | pickleDB = "^0.9.2"
24 |
25 | [tool.poetry.dev-dependencies]
26 | GitPython = "^3.1.27"
27 | packaging = "^21.3"
28 | poethepoet = "ˆ0.13.1"
29 | tomli = "<2.0.0"
30 | responses = "^0.21.0"
31 |
32 | [tool.poe.tasks]
33 | build = ["generate_solutions", "package"]
34 | deploy = "python deploy.py"
35 | generate_solutions = {"script" = "src.solutions.factory:init"}
36 | package = "pyinstaller starklings.spec --noconfirm"
37 | test = "pytest src"
38 |
39 | [build-system]
40 | build-backend = "poetry.core.masonry.api"
41 | requires = ["poetry-core>=1.0.0"]
42 |
43 | [[tool.poetry.source]]
44 | name = "test-pypi"
45 | secondary = true
46 | url = "https://test.pypi.org/project/"
47 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | asyncio_mode=auto
3 |
--------------------------------------------------------------------------------
/resources/assets/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/resources/assets/warning.png
--------------------------------------------------------------------------------
/scripts/add_new_exercise.sh:
--------------------------------------------------------------------------------
1 | #! /bin/env bash
2 | # Add a new exercise
3 |
4 | # set -eux
5 |
6 | ROOT_DIR=$PWD
7 | EXERCISES="$PWD/exercises"
8 | EXERCISE_PATCHES="$PWD/.patches"
9 | COURSE="$PWD/src/exercises/__init__.py"
10 |
11 | if [[ $(basename "$ROOT_DIR") != "starklings" ]]; then echo "Execute $0 at the root of the starklings project!"; exit; fi
12 | if ([ -z "$1" ] || [ ! -f "$1" ]); then echo "Please provide a valid path to the exercise!"; exit; fi
13 | if ([ -z "$2" ] || [ ! -f "$2" ]); then echo "Please provide a valid path to the exercise solution!"; exit; fi
14 | if [ -z "$3" ]; then echo "Please provide a valid name for the exercise folder!"; exit; fi
15 |
16 | EXERCISE=$1
17 | EXERCISE_SOLUTION=$2
18 | EXERCISE_FOLDER=$3
19 |
20 | mkdir -p "$EXERCISES/$EXERCISE_FOLDER"
21 | mkdir -p "$EXERCISE_PATCHES/$EXERCISE_FOLDER"
22 |
23 | diff -Nb -U0 $EXERCISE $EXERCISE_SOLUTION > "$EXERCISE_PATCHES/$EXERCISE_FOLDER/$(basename $EXERCISE).patch"
24 | cp $EXERCISE "$EXERCISES/$EXERCISE_FOLDER/$(basename $EXERCISE)"
25 |
26 | echo "Done"
27 | echo "Update course in $COURSE"
28 |
--------------------------------------------------------------------------------
/scripts/patch_and_test_exercises.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ROOT_DIR=$(pwd)
4 | TMP_DIR="/tmp/tmp_exercises"
5 | PATCH_DIR="$ROOT_DIR/.patches"
6 |
7 | mkdir -p $TMP_DIR
8 |
9 | cp -r "$ROOT_DIR/exercises/." "$TMP_DIR/"
10 |
11 | for section in `ls $PATCH_DIR`
12 | do
13 | for exo in `ls $PATCH_DIR/$section`
14 | do
15 | patch $TMP_DIR/$section/"${exo%.*}" < "$PATCH_DIR/$section/$exo"
16 | mv $TMP_DIR/$section/"${exo%.*}" $TMP_DIR/$section/"test_${exo%.*}"
17 | done
18 | done
19 |
20 | RESULT=$($HOME/.protostar/dist/protostar/protostar test $TMP_DIR 2>&1 | grep -iF "failed")
21 |
22 | rm -rf $TMP_DIR
23 |
24 | if [ -z "$RESULT" ]
25 | then
26 | echo "All exercises passed"
27 | exit 0
28 | else
29 | echo "Some exercises failed"
30 | exit 1
31 | fi
32 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
1 | from src.cli import cli
2 |
--------------------------------------------------------------------------------
/src/cli.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import sentry_sdk
3 | from rich.syntax import Syntax
4 | from src.exercises.model import Directory
5 | from src.runner import Runner, single_exercise_check
6 | from src.exercises import current_working_exercises, course
7 | from src.exercises.seeker import ExerciseSeeker
8 | from src.user.login import login
9 | from src.utils.version_manager import VersionManager
10 | from src.config import root_directory, current_working_directory, dev_mode
11 | from src.solutions.repository import get_solution
12 | from src.console import console
13 |
14 | version_manager = VersionManager()
15 |
16 | sentry_sdk.init(
17 | "https://73212d09152344fd8e351ef180b8fa75@o1254095.ingest.sentry.io/6421829",
18 | # Set traces_sample_rate to 1.0 to capture 100%
19 | # of transactions for performance monitoring.
20 | # We recommend adjusting this value in production.
21 | traces_sample_rate=1.0,
22 | environment="development" if dev_mode else "production",
23 | release=str(version_manager.starklings_version),
24 | )
25 |
26 |
27 | def capture_solution_request(solution_path: str):
28 | with sentry_sdk.push_scope() as scope:
29 | scope.set_tag("requested_solution", str(solution_path))
30 | sentry_sdk.capture_message("Solution requested", level="info")
31 |
32 |
33 | def capture_single_exercise_check(exercise_path: Path):
34 | exercise_relative_path = exercise_path.relative_to(current_working_directory)
35 | with sentry_sdk.push_scope() as scope:
36 | scope.set_tag("exercise_to_check", str(exercise_relative_path))
37 | sentry_sdk.capture_message("Single exercise check", level="info")
38 |
39 |
40 | async def cli(args):
41 | exercise_seeker = ExerciseSeeker(current_working_exercises)
42 | runner = Runner(exercise_seeker)
43 |
44 | if args.version:
45 | version_manager.print_current_version()
46 | return
47 |
48 | if args.display_course:
49 | print(Directory("exercises", course), end="")
50 |
51 | if args.watch:
52 | with console.screen():
53 | sentry_sdk.capture_message("Starting the watch mode")
54 | runner.watch()
55 | return
56 |
57 | if args.verify:
58 | exercise_path = args.verify
59 | capture_single_exercise_check(exercise_path)
60 | await single_exercise_check(exercise_path)
61 | return
62 |
63 | if args.solution:
64 | capture_solution_request(args.solution)
65 | exercise_path = root_directory / args.solution
66 | try:
67 | solution = get_solution(exercise_path)
68 |
69 | syntax = Syntax(
70 | solution, "python", line_numbers=True, background_color="default"
71 | )
72 | console.print(syntax)
73 | except FileNotFoundError:
74 | print("Solution not found")
75 | return
76 |
77 | if args.login:
78 | with console.screen():
79 | login()
80 | return
81 |
--------------------------------------------------------------------------------
/src/config.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 |
4 | EXERCISES_DIRECTORY_NAME = "exercises"
5 | PATCHES_DIRECTORY_NAME = ".patches"
6 | SOLUTIONS_DIRECTORY_NAME = ".solutions"
7 |
8 | GITHUB_CLIENT_ID = "e5335dd9a6d4456d5a61"
9 | GITHUB_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
10 |
11 | root_directory = (Path(__file__).parents[1]).resolve()
12 | current_working_directory = Path(os.getcwd()).resolve()
13 | exercises_directory = root_directory / EXERCISES_DIRECTORY_NAME
14 | current_working_exercises_directory = (
15 | current_working_directory / EXERCISES_DIRECTORY_NAME
16 | )
17 | patches_directory = root_directory / PATCHES_DIRECTORY_NAME
18 | solutions_directory = root_directory / SOLUTIONS_DIRECTORY_NAME
19 |
20 | dev_mode = root_directory == current_working_directory
21 |
--------------------------------------------------------------------------------
/src/console.py:
--------------------------------------------------------------------------------
1 | from rich.console import Console
2 |
3 | console = Console()
4 |
--------------------------------------------------------------------------------
/src/database.py:
--------------------------------------------------------------------------------
1 | import pickledb
2 | from src.config import root_directory
3 |
4 | database = pickledb.load(str(root_directory / "starklings.db"), True)
5 |
--------------------------------------------------------------------------------
/src/exercises/__init__.py:
--------------------------------------------------------------------------------
1 | from src.config import exercises_directory, current_working_exercises_directory
2 |
3 | from .model import Directory, Exercise
4 |
5 | course = [
6 | Directory(
7 | "syntax",
8 | [
9 | Exercise("syntax01"),
10 | Exercise("syntax02"),
11 | Exercise("syntax03"),
12 | Exercise("syntax04"),
13 | Exercise("syntax05"),
14 | ],
15 | ),
16 | Directory(
17 | "operations",
18 | [
19 | Exercise("operations00"),
20 | Exercise("operations01"),
21 | Exercise("operations02"),
22 | Exercise("operations03"),
23 | ],
24 | ),
25 | Directory(
26 | "strings",
27 | [
28 | Exercise("strings00"),
29 | Exercise("strings01"),
30 | ],
31 | ),
32 | Directory(
33 | "storage",
34 | [
35 | Exercise("storage01"),
36 | Exercise("storage02"),
37 | Exercise("storage03"),
38 | ],
39 | ),
40 | Directory(
41 | "implicit_arguments",
42 | [
43 | Exercise("implicit_arguments01"),
44 | Exercise("implicit_arguments02"),
45 | Exercise("implicit_arguments03"),
46 | ],
47 | ),
48 | Directory(
49 | "recursions",
50 | [
51 | Exercise("recursion01"),
52 | Exercise("array01"),
53 | Exercise("array02"),
54 | Exercise("array03"),
55 | Exercise("array04"),
56 | Exercise("struct01"),
57 | Exercise("collatz_sequence"),
58 | ],
59 | ),
60 | Directory(
61 | "registers",
62 | [
63 | Exercise("registers00"),
64 | Exercise("registers01"),
65 | Exercise("registers02"),
66 | Exercise("registers03"),
67 | Exercise("registers04"),
68 | ],
69 | ),
70 | Directory(
71 | "revoked_references",
72 | [
73 | Exercise("revoked_references01"),
74 | ],
75 | ),
76 | Directory(
77 | "builtins",
78 | [
79 | Exercise("bitwise"),
80 | ],
81 | ),
82 | Directory(
83 | "tricks",
84 | [
85 | Exercise("no_conditionals"),
86 | Exercise("assert_bool"),
87 | Exercise("inline_if"),
88 | ],
89 | ),
90 | Directory(
91 | "hints",
92 | [
93 | Exercise("hints00"),
94 | Exercise("hints01"),
95 | ],
96 | ),
97 | ]
98 |
99 | exercises = Directory(exercises_directory, course).list_exercises()
100 |
101 | current_working_exercises = Directory(
102 | current_working_exercises_directory, course
103 | ).list_exercises()
104 |
--------------------------------------------------------------------------------
/src/exercises/checker.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from queue import Queue
3 | from pathlib import Path
4 | from threading import Lock
5 | from starklings_protostar.commands.test.runner import TestRunner
6 | from starklings_protostar.commands.test.reporter import Reporter
7 | from starklings_protostar.commands.test.cases import PassedCase
8 | from starklings_protostar.commands.test.test_collector import TestCollector
9 |
10 | lock = Lock()
11 |
12 |
13 | class ExerciceFailed(Exception):
14 | def __init__(self, message, **kwargs):
15 | super().__init__(**kwargs)
16 | self._message = message
17 |
18 | @property
19 | def message(self):
20 | return self._message
21 |
22 |
23 | async def check_exercise(exercise_path):
24 | try:
25 | test_subjects = TestCollector(
26 | target=Path(exercise_path),
27 | ).collect()
28 | queue = Queue()
29 | reporter = Reporter(queue)
30 | runner = TestRunner(reporter=reporter, include_paths=[])
31 | await asyncio.gather(
32 | *[runner.run_test_subject(subject) for subject in test_subjects]
33 | )
34 | except Exception as error:
35 | raise ExerciceFailed(str(error)) from error
36 | for test_case_result in reporter.test_case_results:
37 | if not isinstance(test_case_result, PassedCase):
38 | raise ExerciceFailed(str(test_case_result))
39 |
--------------------------------------------------------------------------------
/src/exercises/checker_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from src.exercises.checker import ExerciceFailed, check_exercise
3 |
4 |
5 | async def test_protostar_test_checker_success():
6 | await check_exercise("tests/test.cairo")
7 |
8 |
9 | async def test_protostar_test_checker_missing_exercise():
10 | with pytest.raises(ExerciceFailed):
11 | await check_exercise("tests/test_missing.cairo")
12 |
13 |
14 | async def test_protostar_test_checker_failing_exercise():
15 | with pytest.raises(ExerciceFailed):
16 | await check_exercise("tests/test_failure.cairo")
17 |
18 |
19 | async def test_protostar_test_checker_invalid_exercise():
20 | with pytest.raises(ExerciceFailed):
21 | await check_exercise("tests/test_invalid.cairo")
22 |
23 |
24 | async def test_protostar_test_checker_missing_syntax():
25 | with pytest.raises(ExerciceFailed):
26 | await check_exercise("tests/test_missing.cairo")
27 |
--------------------------------------------------------------------------------
/src/exercises/list_test.py:
--------------------------------------------------------------------------------
1 | from src.exercises import exercises
2 |
3 |
4 | def test_exercises_exist():
5 | for exercise in exercises:
6 | assert exercise.exists()
7 |
--------------------------------------------------------------------------------
/src/exercises/model.py:
--------------------------------------------------------------------------------
1 | from collections import namedtuple
2 | from pathlib import Path
3 | from typing import List
4 |
5 |
6 | class Exercise(namedtuple("Exercise", ["name"])):
7 | pass
8 |
9 |
10 | class Directory(namedtuple("Directory", ["name", "children"])):
11 | def __new__(cls, name, children):
12 | assert all(
13 | isinstance(x, (Directory, Exercise)) for x in children
14 | ), "Directory children can only be of type Directory of Exercise"
15 | self = super(Directory, cls).__new__(cls, name, children)
16 | return self
17 |
18 | def list_exercises(self) -> List[Path]:
19 | return self.inner_list_exercises("")
20 |
21 | def inner_list_exercises(self, prefix) -> List[Path]:
22 | dir_prefix = f"{self.name}/"
23 |
24 | res = []
25 |
26 | for child in self.children:
27 | if isinstance(child, Exercise):
28 | res.append(Path(prefix) / dir_prefix / f"{child.name}.cairo")
29 | elif isinstance(child, Directory):
30 | res += child.inner_list_exercises(dir_prefix)
31 | else:
32 | raise Exception(
33 | "directory children can only contain Exercises and Directory types"
34 | )
35 |
36 | return res
37 |
38 | def __str__(self):
39 | return self.inner_str("")
40 |
41 | def inner_str(self, parent_prefix):
42 | res = f"{parent_prefix}{self.name}/\n"
43 |
44 | for child in self.children:
45 | if isinstance(child, Exercise):
46 | res += f" {parent_prefix}- {child.name}\n"
47 | elif isinstance(child, Directory):
48 | res += child.inner_str(parent_prefix + " ")
49 |
50 | return res
51 |
--------------------------------------------------------------------------------
/src/exercises/model_test.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from .model import Directory, Exercise
3 |
4 |
5 | def test_display_course():
6 | my_course = Directory(
7 | "exercises",
8 | [
9 | Directory(
10 | "syntax",
11 | [
12 | Exercise("syntax00"),
13 | Exercise("syntax01"),
14 | ],
15 | ),
16 | Directory(
17 | "strings",
18 | [
19 | Exercise("string00"),
20 | Exercise("string01"),
21 | ],
22 | ),
23 | ],
24 | )
25 |
26 | assert (
27 | my_course.__str__()
28 | == """exercises/
29 | syntax/
30 | - syntax00
31 | - syntax01
32 | strings/
33 | - string00
34 | - string01
35 | """
36 | )
37 |
38 |
39 | def test_list_exercise():
40 | my_course = Directory(
41 | "exercises",
42 | [
43 | Directory(
44 | "syntax",
45 | [
46 | Exercise("syntax01"),
47 | Exercise("syntax02"),
48 | ],
49 | ),
50 | Directory(
51 | "string",
52 | [Exercise("strings00"), Exercise("strings01")],
53 | ),
54 | ],
55 | )
56 |
57 | assert my_course.list_exercises() == [
58 | Path("exercises/syntax/syntax01.cairo"),
59 | Path("exercises/syntax/syntax02.cairo"),
60 | Path("exercises/string/strings00.cairo"),
61 | Path("exercises/string/strings01.cairo"),
62 | ]
63 |
--------------------------------------------------------------------------------
/src/exercises/seeker.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import List
3 |
4 |
5 | def _is_exercise_not_done(exercise: Path) -> bool:
6 | return "\n# I AM NOT DONE\n" in exercise.read_text()
7 |
8 |
9 | class ExerciseSeeker:
10 | def __init__(self, exercises: List[Path]):
11 | self._exercises = exercises
12 |
13 | def get_next_undone(self) -> Path:
14 | for exercise in self._exercises:
15 | if _is_exercise_not_done(exercise):
16 | return exercise
17 | return None
18 |
--------------------------------------------------------------------------------
/src/exercises/seeker_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from src.exercises.seeker import ExerciseSeeker
3 |
4 |
5 | @pytest.fixture(name="finished_exercise")
6 | def finished_exercise_fixture(mocker):
7 | mock = mocker.patch("src.exercises.seeker.Path").return_value
8 | mock.read_text.return_value = "Yolo"
9 | return mock
10 |
11 |
12 | @pytest.fixture(name="unfinished_exercise")
13 | def unfinished_exercise_fixture(mocker):
14 | mock = mocker.patch("src.exercises.seeker.Path").return_value
15 | mock.read_text.return_value = "Yolo\n# I AM NOT DONE\nNext"
16 | return mock
17 |
18 |
19 | def test_next_undone_exercise(finished_exercise, unfinished_exercise):
20 | exercise_seeker = ExerciseSeeker([finished_exercise])
21 | assert exercise_seeker.get_next_undone() is None
22 |
23 | exercise_seeker = ExerciseSeeker([finished_exercise, unfinished_exercise])
24 | assert exercise_seeker.get_next_undone() is unfinished_exercise
25 |
--------------------------------------------------------------------------------
/src/file_watcher/dummy_file.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | func sum_func{syscall_ptr : felt*, range_check_ptr}(a : felt, b : felt) -> (res : felt):
4 | return (a + b)
5 | end
6 |
7 | @external
8 | func test_sum{syscall_ptr : felt*, range_check_ptr}():
9 | let (r) = sum_func(4, 3)
10 | assert r = 6
11 | return ()
12 | end
13 |
--------------------------------------------------------------------------------
/src/file_watcher/watcher.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from watchdog.observers import Observer
3 | from watchdog.events import FileSystemEventHandler
4 | from src.utils.debounce import debounce
5 |
6 |
7 | class Handler(FileSystemEventHandler):
8 | def __init__(self, callback):
9 | self._callback = debounce(0.1)(callback)
10 |
11 | def on_modified(self, event):
12 | self._callback(event)
13 |
14 |
15 | class FileWatcher:
16 | def __init__(self, root_dir: Path):
17 | self._root_dir = root_dir
18 | self._observer = Observer()
19 |
20 | def start(self, callback):
21 | event_handler = Handler(callback)
22 | self._observer.schedule(event_handler, self._root_dir, recursive=True)
23 | self._observer.start()
24 |
25 | def stop(self):
26 | self._observer.stop()
27 | self._observer.join()
28 |
--------------------------------------------------------------------------------
/src/file_watcher/watcher_test.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from time import sleep
3 | from src.file_watcher.watcher import FileWatcher
4 |
5 |
6 | def test_file_watcher(mocker):
7 | current_directry = Path(__file__).parent
8 | dummy_file_path = current_directry / "dummy_file.cairo"
9 | watcher = FileWatcher(current_directry)
10 | stub = mocker.stub()
11 | watcher.start(stub)
12 |
13 | assert stub.call_count == 0
14 | content = None
15 | with open(dummy_file_path, "r+", encoding="utf-8") as dummy_file:
16 | content = dummy_file.read()
17 | dummy_file.write("dummy")
18 |
19 | sleep(0.2)
20 |
21 | try:
22 | assert stub.call_count == 1
23 | assert stub.call_args[0][0].src_path in str(dummy_file_path)
24 | finally:
25 | with open(dummy_file_path, "w", encoding="utf-8") as dummy_file:
26 | dummy_file.write(content)
27 |
--------------------------------------------------------------------------------
/src/prompt.py:
--------------------------------------------------------------------------------
1 | from rich import print as rich_print
2 | from src.console import console
3 |
4 |
5 | def on_watch_start(exercise_path):
6 | rich_print("[bold]:robot::robot::robot: Watch mode started.[/bold]")
7 | rich_print(f"You can start to work on exercise {exercise_path}.\n")
8 |
9 |
10 | def on_single_exercise_success(exercise_path):
11 | console.clear()
12 | rich_print(
13 | f"[bold green]:partying_face::partying_face::partying_face: Exercise {exercise_path} completed![/bold green]"
14 | )
15 |
16 |
17 | def on_watch_exercise_success():
18 | rich_print("You can keep working on this exercise,")
19 | rich_print("or move on to the next one by removing the `I AM NOT DONE` comment.\n")
20 |
21 |
22 | def on_watch_no_next_exercise():
23 | rich_print("Congratulations! You have completed all the exercises!")
24 |
25 |
26 | def on_exercise_failure(exercise_path, error_message):
27 | console.clear()
28 | rich_print(
29 | f"[red]:construction: Exercise {exercise_path} failed. Please try again.[/red]"
30 | )
31 | rich_print(error_message)
32 |
33 |
34 | def on_exercise_check(exercise_path):
35 | console.clear()
36 | rich_print(f"[gray]:eyes: Checking exercise {exercise_path}...[/gray]")
37 |
38 |
39 | def on_file_not_found():
40 | console.clear()
41 | rich_print(
42 | "[red]:face_with_monocle: Creepy crap it looks that you are not running this script from the root directory of the repository.[/red]"
43 | )
44 | rich_print(
45 | "[red]Please make sure you are running the CLI from the cloned Starklings repository.[/red]"
46 | )
47 |
48 |
49 | def on_user_verification(verification_uri: str, verification_code: str):
50 | console.clear()
51 | rich_print(
52 | f"Please visit {verification_uri} to login and enter the following code: [bold]{verification_code}[/bold]"
53 | )
54 |
55 |
56 | def waiting_for_user_login():
57 | return console.status("Waiting for user login...")
58 |
--------------------------------------------------------------------------------
/src/repository/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/src/repository/__init__.py
--------------------------------------------------------------------------------
/src/repository/state_checker.py:
--------------------------------------------------------------------------------
1 | from logging import getLogger
2 | import os
3 | from packaging.version import Version
4 | from git import InvalidGitRepositoryError, Repo
5 |
6 | from src.utils.version_manager import VersionManager
7 |
8 | logger = getLogger()
9 |
10 |
11 | def versions_match(repo: Repo):
12 | # Check there is no breaking change between the binary and the repository
13 | repo_version = Version(repo.tags.pop().name[1:])
14 | binary_version = VersionManager().starklings_version
15 | if repo_version.major > binary_version.major or repo_version < binary_version:
16 | logger.error(
17 | """You installed starklings in version %s, but the repository is cloned in version %s
18 | Please update starklings running `bash install.sh` and update the repository running `git pull origin stable`""",
19 | binary_version,
20 | repo_version,
21 | )
22 | return False
23 | return True
24 |
25 |
26 | def correct_branch(repo: Repo):
27 | if repo.active_branch.name != "stable":
28 | logger.error(
29 | "You are not on the stable branch, please switch running `git checkout stable`"
30 | )
31 | return False
32 | return True
33 |
34 |
35 | def check():
36 | # Check cwd is a repository
37 | try:
38 | repo = Repo(os.getcwd())
39 | except InvalidGitRepositoryError:
40 | logger.error(
41 | "You are not running starklings in a git repository, make sure you run it in the cloned starklings repository"
42 | )
43 | return False
44 |
45 | return correct_branch(repo) and versions_match(repo)
46 |
--------------------------------------------------------------------------------
/src/repository/state_checker_test.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import pytest
3 | from packaging.version import Version
4 | from src.repository.state_checker import check, correct_branch, versions_match
5 |
6 |
7 | @pytest.fixture(name="repo")
8 | def repo_fixture(mocker):
9 | return mocker.patch("src.repository.state_checker.Repo").return_value
10 |
11 |
12 | def test_is_a_repo(mocker):
13 | logger = mocker.patch("src.repository.state_checker.logger")
14 | mocked_os = mocker.patch("src.repository.state_checker.os")
15 | mocker.patch("src.repository.state_checker.versions_match").return_value = True
16 | mocker.patch("src.repository.state_checker.correct_branch").return_value = True
17 |
18 | # Running directory is not a git repository
19 | mocked_os.getcwd.return_value = str(Path(__file__).parents[3])
20 | assert not check()
21 | logger.error.assert_called_once()
22 |
23 | # Running directory is a git repository
24 | mocked_os.getcwd.return_value = str(Path(__file__).parents[2])
25 | logger.reset_mock()
26 | assert check()
27 | logger.error.assert_not_called()
28 |
29 |
30 | def test_versions_match(mocker, repo):
31 | logger = mocker.patch("src.repository.state_checker.logger")
32 | version_manager = mocker.patch(
33 | "src.repository.state_checker.VersionManager"
34 | ).return_value
35 |
36 | # Repo is behing binary
37 | version_manager.starklings_version = Version("2.0.0")
38 | repo.tags.pop.return_value.name = "v1.0.0"
39 | assert not versions_match(repo)
40 | logger.error.assert_called_once()
41 |
42 | # Repo is ahead of binary
43 | version_manager.starklings_version = Version("2.0.0")
44 | repo.tags.pop.return_value.name = "v3.0.0"
45 | logger.reset_mock()
46 | assert not versions_match(repo)
47 | logger.error.assert_called_once()
48 |
49 | # Repo is up to date
50 | version_manager.starklings_version = Version("2.0.0")
51 | repo.tags.pop.return_value.name = "v2.0.0"
52 | logger.reset_mock()
53 | assert versions_match(repo)
54 | logger.error.assert_not_called()
55 |
56 | # Repo is ahead of binary but not breaking
57 | version_manager.starklings_version = Version("2.0.0")
58 | repo.tags.pop.return_value.name = "v2.4.0"
59 | logger.reset_mock()
60 | assert versions_match(repo)
61 | logger.error.assert_not_called()
62 |
63 |
64 | def test_correct_branch(repo):
65 | repo.active_branch.name = "stable"
66 | assert correct_branch(repo)
67 |
68 | repo.active_branch.name = "master"
69 | assert not correct_branch(repo)
70 |
--------------------------------------------------------------------------------
/src/runner.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import contextlib
3 | from pathlib import Path
4 | import sys
5 | from threading import Lock
6 | from time import sleep
7 |
8 | import sentry_sdk
9 |
10 | from src.exercises.checker import ExerciceFailed, check_exercise
11 | from src.file_watcher.watcher import FileWatcher
12 | from src import prompt
13 | from src.exercises.seeker import ExerciseSeeker
14 | from src.config import current_working_directory
15 |
16 | check_exercise_lock = Lock()
17 |
18 |
19 | async def single_exercise_check(exercise_path: Path, watch_mode=False):
20 | if check_exercise_lock.locked():
21 | return
22 | with check_exercise_lock:
23 | prompt.on_exercise_check(exercise_path)
24 | try:
25 | await check_exercise(str(exercise_path))
26 | capture_exercise_solved(exercise_path)
27 | prompt.on_single_exercise_success(exercise_path)
28 | if watch_mode:
29 | prompt.on_watch_exercise_success()
30 | except ExerciceFailed as error:
31 | prompt.on_exercise_failure(exercise_path, error.message)
32 |
33 |
34 | def capture_exercise_solved(exercise_path: str):
35 | with sentry_sdk.push_scope() as scope:
36 | scope.set_tag("exercise_solved", str(exercise_path))
37 | sentry_sdk.capture_message("Exercise solved", level="info")
38 |
39 |
40 | class Runner:
41 | def __init__(self, exercise_seeker: ExerciseSeeker):
42 | self._file_watcher = FileWatcher(current_working_directory)
43 | self._exercise_seeker = exercise_seeker
44 |
45 | def on_file_changed(self, _):
46 | next_exercise_path = self._exercise_seeker.get_next_undone()
47 | if next_exercise_path is not None:
48 | asyncio.run(single_exercise_check(next_exercise_path, True))
49 | else:
50 | prompt.on_watch_no_next_exercise()
51 |
52 | def watch(self):
53 | try:
54 | prompt.on_watch_start(self._exercise_seeker.get_next_undone())
55 | with contextlib.suppress(KeyboardInterrupt):
56 | self._file_watcher.start(self.on_file_changed)
57 | while True:
58 | sleep(5)
59 | except FileNotFoundError:
60 | prompt.on_file_not_found()
61 | sys.exit(1)
62 |
--------------------------------------------------------------------------------
/src/runner_test.py:
--------------------------------------------------------------------------------
1 | import io
2 | from unittest.mock import patch
3 | from pathlib import Path
4 | from src.runner import ExerciseSeeker, Runner, current_working_directory
5 |
6 | PATH = Path("tests/exercises/test_end_of_exercise_messages")
7 |
8 |
9 | def runner_on_file_changed(current_working_exercises):
10 | exercise_seeker = ExerciseSeeker(current_working_exercises)
11 | runner = Runner(exercise_seeker)
12 | runner.on_file_changed(current_working_directory)
13 |
14 |
15 | @patch("sys.stdout", new_callable=io.StringIO)
16 | def test_runner_on_file_changed_with_next_exercises(mock_stdout):
17 | current_working_exercises = [
18 | PATH / "with_next_exercises" / "syntax01.cairo",
19 | PATH / "with_next_exercises" / "syntax02.cairo",
20 | ]
21 | runner_on_file_changed(current_working_exercises)
22 | assert (
23 | mock_stdout.getvalue()
24 | != "Congratulations! You have completed all the exercises!\n"
25 | )
26 |
27 |
28 | @patch("sys.stdout", new_callable=io.StringIO)
29 | def test_runner_on_file_changed_without_next_exercises(mock_stdout):
30 | current_working_exercises = [
31 | PATH / "without_next_exercises" / "syntax01.cairo",
32 | PATH / "without_next_exercises" / "syntax02.cairo",
33 | ]
34 | runner_on_file_changed(current_working_exercises)
35 | assert (
36 | mock_stdout.getvalue()
37 | == "Congratulations! You have completed all the exercises!\n"
38 | )
39 |
--------------------------------------------------------------------------------
/src/solutions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/src/solutions/__init__.py
--------------------------------------------------------------------------------
/src/solutions/factory.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 | import shutil
4 | from typing import List
5 | from src.config import solutions_directory, exercises_directory, patches_directory
6 | from src.exercises import exercises
7 |
8 |
9 | def empty_directory(path: Path):
10 | if path.exists():
11 | shutil.rmtree(path)
12 | path.mkdir()
13 |
14 |
15 | def create_solution(exercise_path: Path):
16 | exercise_relative_path = exercise_path.relative_to(exercises_directory)
17 | solution_path = (
18 | solutions_directory / exercise_path.name
19 | ) # Flatten the exercises tree in the solutions directory
20 | patch_path = patches_directory / f"{exercise_relative_path}.patch"
21 | try:
22 | os.makedirs(solution_path.parent)
23 | except FileExistsError:
24 | pass
25 | solution_path.touch()
26 | os.system(f"patch {exercise_path} -o {solution_path} < {patch_path}")
27 |
28 |
29 | def init(exercise_list: List[Path] = None):
30 | exercise_list = exercise_list or exercises
31 | empty_directory(solutions_directory)
32 | for exercise in exercise_list:
33 | create_solution(exercise)
34 |
--------------------------------------------------------------------------------
/src/solutions/factory_test.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from src.solutions.factory import init
3 | from src.config import exercises_directory
4 | from src.solutions.repository import get_solution
5 |
6 |
7 | @pytest.fixture(name="an_exercise")
8 | def an_exercise_fixture():
9 | return exercises_directory / "syntax/syntax01.cairo"
10 |
11 |
12 | @pytest.fixture(autouse=True)
13 | def init_fixture(an_exercise):
14 | init([an_exercise])
15 |
16 |
17 | def test_solution_getter(an_exercise):
18 | solution = get_solution(an_exercise)
19 | assert (
20 | solution
21 | == """%lang starknet
22 | # All Starknet files must start with a specific line indicating the file is a smart contract,
23 | # not just a regular Cairo file
24 |
25 | # I AM NOT DONE
26 |
27 | # TODO: add the Starknet file specifier at the beginning of the file
28 |
29 | # You can ignore what follows for now
30 | @external
31 | func test_ok():
32 | return ()
33 | end
34 | """
35 | )
36 |
--------------------------------------------------------------------------------
/src/solutions/repository.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from src.config import solutions_directory
3 |
4 |
5 | def get_solution(exercise_path: Path):
6 | # When packaged, the solutions are all flattened into the .solutions directory.
7 | solution_path = solutions_directory / exercise_path.name
8 | return solution_path.read_text()
9 |
--------------------------------------------------------------------------------
/src/user/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/src/user/__init__.py
--------------------------------------------------------------------------------
/src/user/access_token.py:
--------------------------------------------------------------------------------
1 | from src.database import database
2 |
3 | ACCESS_TOKEN_KEY = "access_token"
4 |
5 |
6 | def get_access_token():
7 | return database.get(ACCESS_TOKEN_KEY)
8 |
9 |
10 | def set_access_token(access_token: str):
11 | database.set(ACCESS_TOKEN_KEY, access_token)
12 |
--------------------------------------------------------------------------------
/src/user/login.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | import requests
3 | from src.config import GITHUB_CLIENT_ID, GITHUB_GRANT_TYPE
4 | from src.prompt import on_user_verification, waiting_for_user_login
5 | from src.user.access_token import set_access_token
6 |
7 |
8 | def login():
9 | user_verification_request = requests.post(
10 | "https://github.com/login/device/code",
11 | data={
12 | "client_id": GITHUB_CLIENT_ID,
13 | },
14 | headers={
15 | "Accept": "application/json",
16 | },
17 | )
18 | user_verification_response = user_verification_request.json()
19 |
20 | on_user_verification(
21 | user_verification_response["verification_uri"],
22 | user_verification_response["user_code"],
23 | )
24 | with waiting_for_user_login():
25 | interval = user_verification_response["interval"]
26 | total_retries = user_verification_response["expires_in"] // interval
27 | device_code = user_verification_response["device_code"]
28 | retry_count = 0
29 |
30 | poll_access_token = request_access_token(device_code)
31 | while "error" in poll_access_token.json():
32 | if retry_count == total_retries:
33 | raise Exception("Failed to get access token")
34 | retry_count += 1
35 | sleep(interval)
36 | poll_access_token = request_access_token(device_code)
37 |
38 | access_token = poll_access_token.json()["access_token"]
39 | set_access_token(access_token)
40 |
41 |
42 | def request_access_token(device_code: str):
43 | return requests.post(
44 | "https://github.com/login/oauth/access_token",
45 | data={
46 | "client_id": GITHUB_CLIENT_ID,
47 | "grant_type": GITHUB_GRANT_TYPE,
48 | "device_code": device_code,
49 | },
50 | headers={
51 | "Accept": "application/json",
52 | },
53 | )
54 |
--------------------------------------------------------------------------------
/src/user/login_test.py:
--------------------------------------------------------------------------------
1 | import responses
2 | from src.user.login import login
3 |
4 |
5 | @responses.activate
6 | def test_user_login(mocker):
7 | on_user_verification_mock = mocker.patch("src.user.login.on_user_verification")
8 | set_access_token_mock = mocker.patch("src.user.login.set_access_token")
9 |
10 | verification_uri = "https://github.com/login/device"
11 | user_code = "user_code"
12 | access_token = "access_token"
13 | device_code = "device_code"
14 |
15 | user_verification_response = responses.Response(
16 | method="POST",
17 | url="https://github.com/login/device/code",
18 | json={
19 | "verification_uri": verification_uri,
20 | "user_code": user_code,
21 | "interval": 0.01,
22 | "expires_in": 899,
23 | "device_code": device_code,
24 | },
25 | )
26 |
27 | authorization_pending_response = responses.Response(
28 | method="POST",
29 | url="https://github.com/login/oauth/access_token",
30 | json={"error": "authorization_pending"},
31 | )
32 |
33 | access_token_response = responses.Response(
34 | method="POST",
35 | url="https://github.com/login/oauth/access_token",
36 | json={
37 | "access_token": access_token,
38 | },
39 | )
40 |
41 | responses.add(user_verification_response)
42 | responses.add(authorization_pending_response)
43 | responses.add(authorization_pending_response)
44 | responses.add(authorization_pending_response)
45 | responses.add(access_token_response)
46 |
47 | login()
48 |
49 | on_user_verification_mock.assert_called_with(verification_uri, user_code)
50 | set_access_token_mock.assert_called_with(access_token)
51 |
--------------------------------------------------------------------------------
/src/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/src/utils/__init__.py
--------------------------------------------------------------------------------
/src/utils/debounce.py:
--------------------------------------------------------------------------------
1 | # Taken from https://stackoverflow.com/questions/61476962/python-decorator-for-debouncing-including-function-arguments#:~:text=Debouncing%20means%20to%20supress%20the,if%20no%20new%20function%20calls
2 |
3 | import threading
4 |
5 |
6 | def debounce(wait_time):
7 | """
8 | Decorator that will debounce a function so that it is called after wait_time seconds
9 | If it is called multiple times, will wait for the last call to be debounced and run only this one.
10 | """
11 |
12 | def decorator(function):
13 | def debounced(*args, **kwargs):
14 | def call_function():
15 | debounced.timer = None
16 | return function(*args, **kwargs)
17 |
18 | # if we already have a call to the function currently waiting to be executed, reset the timer
19 | if debounced.timer is not None:
20 | debounced.timer.cancel()
21 |
22 | # after wait_time, call the function provided to the decorator with its arguments
23 | debounced.timer = threading.Timer(wait_time, call_function)
24 | debounced.timer.start()
25 |
26 | debounced.timer = None
27 | return debounced
28 |
29 | return decorator
30 |
--------------------------------------------------------------------------------
/src/utils/debounce_test.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 | from .debounce import debounce
3 |
4 |
5 | def test_debounce(mocker):
6 | stub = mocker.stub()
7 |
8 | @debounce(0.1)
9 | def debounced_function():
10 | stub()
11 |
12 | debounced_function()
13 | debounced_function()
14 |
15 | sleep(0.2)
16 |
17 | assert stub.call_count == 1
18 |
--------------------------------------------------------------------------------
/src/utils/version_manager.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import re
3 | from logging import getLogger
4 | from typing import Optional
5 | import tomli
6 | from packaging import version
7 | from packaging.version import Version as PackagingVersion
8 | from src.config import root_directory
9 |
10 |
11 | class VersionManager:
12 | @staticmethod
13 | def parse(version_str: str) -> PackagingVersion:
14 | return version.parse(version_str)
15 |
16 | @property
17 | def _pyproject_toml_path(self) -> Path:
18 | # When running from the built binary, the pyproject.toml file is under an "info" directory
19 | info_directory = root_directory / "info"
20 | if info_directory.exists():
21 | return info_directory / "pyproject.toml"
22 | return root_directory / "pyproject.toml"
23 |
24 | @property
25 | def starklings_version(self) -> Optional[PackagingVersion]:
26 | try:
27 | with open(self._pyproject_toml_path, "r", encoding="UTF-8") as file:
28 | version_s = tomli.loads(file.read())["tool"]["poetry"]["version"]
29 | return VersionManager.parse(version_s)
30 | except FileNotFoundError:
31 | getLogger().warning("Couldn't read Starklings version")
32 | return None
33 |
34 | @property
35 | def cairo_version(self) -> Optional[PackagingVersion]:
36 | try:
37 | with open(self._pyproject_toml_path, "r", encoding="UTF-8") as file:
38 | raw_version = tomli.loads(file.read())["tool"]["poetry"][
39 | "dependencies"
40 | ]["cairo-lang"]["url"]
41 | version_regex = re.compile(r".*cairo-lang-(.+)\.zip.*")
42 | version_match = version_regex.match(raw_version)
43 | return VersionManager.parse(version_match[1])
44 | except FileNotFoundError:
45 | getLogger().warning("Couldn't read cairo-lang version")
46 | return None
47 |
48 | def print_current_version(self) -> None:
49 | print(f"Starklings version: {self.starklings_version or 'unknown'}")
50 | print(f"Cairo-lang version: {self.cairo_version or 'unknown'}")
51 |
--------------------------------------------------------------------------------
/src/utils/version_manager_test.py:
--------------------------------------------------------------------------------
1 | from src.utils.version_manager import VersionManager
2 |
3 |
4 | def test_version_manager():
5 | version_manager = VersionManager()
6 |
7 | assert version_manager.starklings_version > VersionManager.parse("0.0.0")
8 | assert version_manager.cairo_version > VersionManager.parse("0.0.0")
9 |
--------------------------------------------------------------------------------
/starklings-backend/.env.DEFAULT:
--------------------------------------------------------------------------------
1 | SECRET_KEY=<>
2 | DATABASE_HOST=<>
3 | DATABASE_NAME=<>
4 | DATABASE_USER=<>
5 | DATABASE_PWD=<>
--------------------------------------------------------------------------------
/starklings-backend/README.md:
--------------------------------------------------------------------------------
1 | # Starklings API
2 |
3 | Starklings backend within a single Flask API
4 |
5 | ## Installation Setup
6 |
7 | - Tested with Python >=3.8
8 |
9 | #### 1. Starlings Installation
10 |
11 | Make sure you installed starlings at first :
12 | ```
13 | cd ../
14 | pip install poetry
15 | poetry install
16 |
17 | # or on MAC M1 :
18 | CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib poetry install
19 | ```
20 |
21 | #### 2. Flask Installation
22 |
23 | ```
24 | python -m venv starklings-venv
25 | source starklings-venv/bin/activate
26 | pip install --upgrade pip
27 | pip install -r requirements.txt
28 | ```
29 |
30 | ## Launch API
31 |
32 | - Development
33 | ```
34 | APP_SETTINGS=config.DevConfig python app.py
35 | ```
36 |
37 | - Production
38 | ```
39 | APP_SETTINGS=config.ProdConfig python app.py
40 | ```
41 |
42 |
43 | ## API Documentation
44 |
45 |
46 | - `/exercise`
47 | Exercise Validation route
48 |
49 | 1. Headers
50 |
51 | | Key | Value |
52 | | :--------------- |:---------------:|
53 | | Content-Type | application/json |
54 | | Accept | application/json |
55 |
56 | 2. Data (JSON)
57 |
58 | | Key | Value |
59 | | :--------------- |:---------------:|
60 | | wallet_address | String: Wallet that wants to verify exercise |
61 | | exercise | String: Concatenated path of the wanted exercise (e.g storage/storage01) |
62 | | exercise_data | String: Cairo file as a string |
63 |
64 | 3. Return
65 |
66 | On Validation
67 | ```
68 | {
69 | "result": "Exercice Succeed"
70 | }
71 | ```
72 |
73 | On Error
74 | ```
75 | {
76 | "error": "Cairo Error Msg"
77 | "result": "Exercice Failed"
78 | }
79 | ```
--------------------------------------------------------------------------------
/starklings-backend/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 | from flask_cors import CORS
3 | import os
4 | import sys
5 | sys.path.append('../src/exercises')
6 | from starklings_backend.routes import app_routes
7 | from starklings_backend.models.shared import db
8 |
9 |
10 | app = Flask(__name__)
11 | CORS(app)
12 | env_config = os.getenv("APP_SETTINGS", "config.DevConfig")
13 | app.config.from_object('config.DevConfig')
14 |
15 | db.init_app(app)
16 | app.register_blueprint(app_routes)
17 |
18 |
19 |
20 | if __name__ == '__main__':
21 | app.run(host="0.0.0.0", port=8080)
--------------------------------------------------------------------------------
/starklings-backend/config.py:
--------------------------------------------------------------------------------
1 | from os import environ, path
2 | from dotenv import load_dotenv
3 |
4 | basedir = path.abspath(path.dirname(__file__))
5 | load_dotenv(path.join(basedir, '.env'))
6 |
7 | class Config:
8 | """Base config."""
9 | SECRET_KEY = environ.get('SECRET_KEY')
10 | host = environ.get('DATABASE_HOST', '')
11 | database = environ.get('DATABASE_NAME', '')
12 | user = environ.get('DATABASE_USER', '')
13 | password = environ.get('DATABASE_PWD', '')
14 | SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{user}:{password}@{host}/{database}"
15 |
16 | SQLALCHEMY_TRACK_MODIFICATIONS = False
17 |
18 |
19 | class DevConfig(Config):
20 | ENV='development'
21 | TESTING = True
22 | DEBUG = True
23 | DEVELOPMENT = True
24 | DATABASE_URI = environ.get('DEV_DATABASE_URI')
25 |
26 |
27 | class ProdConfig(Config):
28 | ENV='production'
29 | TESTING = False
30 | DEBUG = False
31 | DEVELOPMENT = False
32 | DATABASE_URI = environ.get('PROD_DATABASE_URI')
33 |
34 |
--------------------------------------------------------------------------------
/starklings-backend/db.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pymysql
3 | from dotenv import load_dotenv
4 | load_dotenv()
5 |
6 | connection = pymysql.connect(
7 | host=os.environ.get('DATABASE_HOST', ''),
8 | database=os.environ.get('DATABASE_NAME', ''),
9 | user=os.environ.get('DATABASE_USER', ''),
10 | password=os.environ.get('DATABASE_PWD', ''),
11 | charset="utf8mb4",
12 | cursorclass=pymysql.cursors.DictCursor
13 | )
14 |
15 | cursor = connection.cursor()
16 |
17 | validated_sql_query = """CREATE TABLE validated_exercise (
18 | exercise_name varchar(255) NOT NULL,
19 | user_id int NOT NULL FOREIGN KEY
20 | ...
21 | )
22 | """
23 |
24 |
25 | user_sql_query = """CREATE TABLE starklings_user (
26 | user_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
27 | wallet_address varchar(255) NOT NULL UNIQUE,
28 | score int NOT NULL DEFAULT 0,
29 | username varchar(255) NOT NULL UNIQUE,
30 | signature varchar(255) NOT NULL
31 | )
32 | """
33 |
34 | #cursor.execute(validated_sql_query)
35 | cursor.execute(user_sql_query)
36 | connection.close()
37 |
--------------------------------------------------------------------------------
/starklings-backend/requirements.txt:
--------------------------------------------------------------------------------
1 | bcrypt==3.2.2
2 | Flask==2.1.3
3 | flask-cors==3.0.10
4 | aioflask==0.4.0
5 | Flask-SQLAlchemy==2.5.1
6 | mysqlclient==2.1.1
7 | pathspec==0.9.0
8 | PyJWT==2.4.0
9 | PyMySQL==1.0.2
10 | python-dotenv==0.20.0
11 | requests==2.28.1
12 | rich==12.4.4
13 | SQLAlchemy==1.4.39
14 | typing_extensions==4.3.0
15 | urllib3==1.26.11
16 | pytest==7.1.2
17 |
--------------------------------------------------------------------------------
/starklings-backend/starklings_backend/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onlydustxyz/starklings/ed480cce4cd74c518ffeaa40f63b71892c6e6a0a/starklings-backend/starklings_backend/__init__.py
--------------------------------------------------------------------------------
/starklings-backend/starklings_backend/exercise.py:
--------------------------------------------------------------------------------
1 | #Starklings Imports
2 | from checker import check_exercise, ExerciceFailed
3 | from threading import Lock
4 |
5 | check_exercise_lock = Lock()
6 |
7 | async def verify_exercise(exercise_path):
8 | if check_exercise_lock.locked():
9 | return
10 | with check_exercise_lock:
11 | try:
12 | await check_exercise(str(exercise_path))
13 | except ExerciceFailed as error:
14 | raise ExerciceFailed(error.message)
15 | return await check_exercise(exercise_path)
--------------------------------------------------------------------------------
/starklings-backend/starklings_backend/models/shared.py:
--------------------------------------------------------------------------------
1 | # apps.shared.models
2 | from flask_sqlalchemy import SQLAlchemy
3 |
4 | db = SQLAlchemy()
--------------------------------------------------------------------------------
/starklings-backend/starklings_backend/models/user.py:
--------------------------------------------------------------------------------
1 | from starklings_backend.models.shared import db
2 |
3 | class Starklingsuser(db.Model):
4 | user_id = db.Column(db.Integer, primary_key=True)
5 | score = db.Column(db.Integer, nullable=False, default=0)
6 | signature = db.Column(db.String(255), nullable=False)
7 | username = db.Column(db.String(255), unique=True, nullable=False)
8 | wallet_address = db.Column(db.String(255), unique=True, nullable=False)
9 |
--------------------------------------------------------------------------------
/starklings-backend/starklings_backend/routes.py:
--------------------------------------------------------------------------------
1 | from flask import request, Blueprint
2 | import asyncio
3 | import bcrypt
4 | from sqlalchemy.exc import IntegrityError
5 | from flask_sqlalchemy import SQLAlchemy
6 | from starklings_backend.utils import verify_email
7 | from starklings_backend.models.shared import db
8 | from starklings_backend.models.user import Starklingsuser
9 | from starklings_backend.exercise import verify_exercise
10 | from checker import ExerciceFailed
11 | import tempfile
12 |
13 | app_routes = Blueprint('app_routes', __name__)
14 |
15 | @app_routes.route('/', methods=['GET'])
16 | def landing():
17 | return 'Starklings API'
18 |
19 | #######################
20 | # Users Routes #
21 | #######################
22 | @app_routes.route('/registerUser', methods=['POST'])
23 | def register_user():
24 | """
25 | Inserts a new user in the Database
26 | @TODO: Starknet ID / Signature and implements model
27 | """
28 | try:
29 | signature = request.json.get('signature', None)
30 | wallet_address = request.json.get('wallet_address', None)
31 | username = request.json.get('username', wallet_address)
32 | if None in [wallet_address, signature]:
33 | return "Wrong form", 400
34 | #@TODO: Check Signature validity
35 |
36 | user = Starklingsuser(wallet_address=wallet_address, signature=signature, username=username)
37 | db.session.commit()
38 | return f'Welcome! {username}', 200
39 |
40 | except IntegrityError as e:
41 | db.session.rollback()
42 | return 'User Already Exists', 400
43 | except AttributeError:
44 | return 'Provide an Email and Password in JSON format in the request body', 400
45 |
46 |
47 | @app_routes.route('/fetchUserInfo', methods=['POST'])
48 | def fetch_user_info():
49 | """
50 | Authenticate a user
51 | @TODO Implements Fetch User Information
52 | """
53 | try:
54 | wallet_address = request.json.get('wallet_address', None)
55 | if not wallet_address:
56 | return 'Missing address', 400
57 | user = Starklingsuser.query.filter_by(wallet_address=wallet_address).first()
58 | if not user:
59 | return 'User Not Found!', 404
60 |
61 | return f'Logged in, Welcome {user.username}!', 200
62 | except AttributeError as e:
63 | print(e)
64 | return 'Provide the wallet address in JSON format in the request body', 400
65 |
66 |
67 | #######################
68 | # Exercises Routes #
69 | #######################
70 | @app_routes.route('/exercise/check', methods=['POST'])
71 | async def starklings_exercise_checker():
72 | """
73 | Check exercise given a body and a user
74 | @TODO: Implement User DB for storing results
75 | """
76 | try:
77 | address = request.json.get('wallet_address', None)
78 | exercise = request.json.get('exercise', 'storage/storage01')
79 | exercise_data = request.json.get('exercise_data', None)
80 | if not address:
81 | return 'Missing Address', 400
82 | tmp = tempfile.NamedTemporaryFile()
83 | with open(tmp.name, 'w') as temp_exercise:
84 | temp_exercise.write(exercise_data)
85 | res = await verify_exercise(tmp.name)
86 | tmp.close()
87 | return {
88 | "result": "success"
89 | }
90 | except ExerciceFailed as error:
91 | print(error)
92 | return {
93 | "result": "failure",
94 | "error": error.message
95 | }, 400
96 |
--------------------------------------------------------------------------------
/starklings-backend/starklings_backend/utils.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import os
4 | import re
5 |
6 | regex = re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+')
7 |
8 |
9 | def verify_email(email):
10 | if re.fullmatch(regex, email):
11 | return True
12 | else:
13 | return False
14 |
15 | class Requester:
16 | def __init__(self, base_url, **kwargs):
17 | self.base_url = base_url
18 | self.session = requests.Session()
19 | for arg in kwargs:
20 | if isinstance(kwargs[arg], dict):
21 | kwargs[arg] = self.__deep_merge(getattr(self.session, arg), kwargs[arg])
22 | setattr(self.session, arg, kwargs[arg])
23 |
24 | def request(self, method, url, **kwargs):
25 | return self.session.request(method, self.base_url+url, **kwargs)
26 |
27 | def head(self, url, **kwargs):
28 | return self.session.head(self.base_url+url, **kwargs)
29 |
30 | def get(self, url, **kwargs):
31 | return self.session.get(self.base_url+url, **kwargs)
32 |
33 | def post(self, url, data, **kwargs):
34 | return self.session.post(self.base_url+url, data=data, **kwargs)
35 |
36 | def put(self, url, **kwargs):
37 | return self.session.put(self.base_url+url, **kwargs)
38 |
39 | def patch(self, url, **kwargs):
40 | return self.session.patch(self.base_url+url, **kwargs)
41 |
42 | def delete(self, url, **kwargs):
43 | return self.session.delete(self.base_url+url, **kwargs)
44 |
45 | @staticmethod
46 | def __deep_merge(source, destination):
47 | for key, value in source.items():
48 | if isinstance(value, dict):
49 | node = destination.setdefault(key, {})
50 | Requester.__deep_merge(value, node)
51 | else:
52 | destination[key] = value
53 | return destination
54 |
--------------------------------------------------------------------------------
/starklings-backend/tests/test_app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import tempfile
3 |
4 | import pytest
5 |
6 | from app import app
7 |
8 | @pytest.fixture
9 | def client():
10 | db_fd, app.config['DATABASE'] = tempfile.mkstemp()
11 | app.config['TESTING'] = True
12 |
13 | os.close(db_fd)
14 | os.unlink(app.config['DATABASE'])
--------------------------------------------------------------------------------
/starklings.py:
--------------------------------------------------------------------------------
1 | from argparse import ArgumentParser
2 | import asyncio
3 | import os
4 | from pathlib import Path
5 | from rich.traceback import install
6 | from src import cli
7 | from src.config import current_working_directory
8 |
9 | install(show_locals=True)
10 |
11 |
12 | def is_valid_file(parser, arg):
13 | file_path = Path(arg).resolve()
14 | if not file_path.exists():
15 | file_path = current_working_directory / arg
16 | if not file_path.exists():
17 | return parser.error(f"The file {arg} does not exist!")
18 | return file_path
19 |
20 |
21 | script_root = Path(os.getcwd())
22 |
23 | root_parser = ArgumentParser()
24 |
25 | root_parser.add_argument(
26 | "--version",
27 | "-V",
28 | help="Show version-related data",
29 | action="store_true",
30 | )
31 |
32 | root_parser.add_argument(
33 | "--verify",
34 | "-v",
35 | metavar="relative_path_to_exercise",
36 | help="Verify a single exercise",
37 | type=lambda x: is_valid_file(root_parser, x),
38 | )
39 |
40 | root_parser.add_argument(
41 | "--watch",
42 | "-w",
43 | help="Watch edited files and verify them",
44 | action="store_true",
45 | )
46 |
47 | root_parser.add_argument(
48 | "--solution",
49 | "-s",
50 | metavar="relative_path_to_exercise",
51 | help="Provide a solution for an exercise",
52 | type=lambda x: is_valid_file(root_parser, x),
53 | )
54 |
55 | root_parser.add_argument(
56 | "--display-course",
57 | "-d",
58 | action="store_true",
59 | help="Display the course content",
60 | )
61 |
62 | root_parser.add_argument(
63 | "--login",
64 | "-l",
65 | help="Login with Github",
66 | action="store_true",
67 | )
68 |
69 | try:
70 | asyncio.run(cli(root_parser.parse_args()))
71 | except Exception as error:
72 | print(
73 | "Unexpected Starklings error. Report it here:\n"
74 | + "https://github.com/onlydustxyz/starklings/issues\n"
75 | )
76 | raise error
77 |
--------------------------------------------------------------------------------
/starklings.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python ; coding: utf-8 -*-
2 | from PyInstaller.utils.hooks import collect_data_files, collect_submodules
3 |
4 | block_cipher = None
5 | extra_files = [
6 | ('pyproject.toml', 'info'),
7 | ('.solutions/*', '.solutions')
8 | ] + collect_data_files('starkware')
9 | # Extra imports which are necessary for executing hints
10 | extra_imports = [
11 | "eth_hash.auto",
12 | ] + collect_submodules('starkware')
13 |
14 | a = Analysis(['starklings.py'],
15 | pathex=[],
16 | binaries=[],
17 | datas=extra_files,
18 | hiddenimports=extra_imports,
19 | hookspath=[],
20 | hooksconfig={},
21 | runtime_hooks=[],
22 | excludes=[],
23 | win_no_prefer_redirects=False,
24 | win_private_assemblies=False,
25 | cipher=block_cipher,
26 | noarchive=False)
27 | pyz = PYZ(a.pure, a.zipped_data,
28 | cipher=block_cipher)
29 |
30 | exe = EXE(pyz,
31 | a.scripts,
32 | [],
33 | exclude_binaries=True,
34 | name='starklings',
35 | debug=False,
36 | bootloader_ignore_signals=False,
37 | strip=False,
38 | upx=True,
39 | console=True,
40 | disable_windowed_traceback=False,
41 | target_arch=None,
42 | codesign_identity=None,
43 | entitlements_file=None )
44 | coll = COLLECT(exe,
45 | a.binaries,
46 | a.zipfiles,
47 | a.datas,
48 | strip=False,
49 | upx=True,
50 | upx_exclude=[],
51 | name='starklings')
52 |
--------------------------------------------------------------------------------
/tests/exercises/test_end_of_exercise_messages/with_next_exercises/syntax01.cairo:
--------------------------------------------------------------------------------
1 | # All Starknet files must start with a specific line indicating the file is a smart contract,
2 | # not just a regular Cairo file
3 |
4 | # TODO: add the Starknet file specifier at the beginning of the file
5 |
6 | # You can ignore what follows for now
7 | @external
8 | func test_ok():
9 | return ()
10 | end
11 |
--------------------------------------------------------------------------------
/tests/exercises/test_end_of_exercise_messages/with_next_exercises/syntax02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Starknet provides a module management system.
4 | # It is very similar to the Python's one.
5 |
6 | # I AM NOT DONE
7 |
8 | # TODO: add the module imports needed to make the test pass!
9 |
10 | # You can ignore what follows for now
11 | @external
12 | func test_ok{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
13 | return ()
14 | end
15 |
--------------------------------------------------------------------------------
/tests/exercises/test_end_of_exercise_messages/without_next_exercises/syntax01.cairo:
--------------------------------------------------------------------------------
1 | # All Starknet files must start with a specific line indicating the file is a smart contract,
2 | # not just a regular Cairo file
3 |
4 | # TODO: add the Starknet file specifier at the beginning of the file
5 |
6 | # You can ignore what follows for now
7 | @external
8 | func test_ok():
9 | return ()
10 | end
11 |
--------------------------------------------------------------------------------
/tests/exercises/test_end_of_exercise_messages/without_next_exercises/syntax02.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | # Starknet provides a module management system.
4 | # It is very similar to the Python's one.
5 |
6 | # TODO: add the module imports needed to make the test pass!
7 |
8 | # You can ignore what follows for now
9 | @external
10 | func test_ok{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
11 | return ()
12 | end
13 |
--------------------------------------------------------------------------------
/tests/test.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | func sum_func{syscall_ptr : felt*, range_check_ptr}(a : felt, b : felt) -> (res : felt):
4 | return (a + b)
5 | end
6 |
7 | @external
8 | func test_sum{syscall_ptr : felt*, range_check_ptr}():
9 | let (r) = sum_func(4, 3)
10 | assert r = 7
11 | return ()
12 | end
13 |
--------------------------------------------------------------------------------
/tests/test_failure.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | func sum_func{syscall_ptr : felt*, range_check_ptr}(a : felt, b : felt) -> (res : felt):
4 | return (a + b)
5 | end
6 |
7 | @external
8 | func test_sum{syscall_ptr : felt*, range_check_ptr}():
9 | let (r) = sum_func(4, 3)
10 | assert r = 6
11 | return ()
12 | end
13 |
--------------------------------------------------------------------------------
/tests/test_invalid.cairo:
--------------------------------------------------------------------------------
1 | %lang starknet
2 |
3 | func sum_func{syscall_ptr : felt*, range_check_ptr}(a : felt, b : felt) -> (res : felt):
4 | return (a + b)
5 | end
6 |
7 | @external
8 | func test_sum{syscall_ptr : felt*, range_check_ptr}():
9 | let (r) = sum_func(4, 3)
10 | assert r
11 | return ()
12 | end
13 |
--------------------------------------------------------------------------------
/tests/test_missing.cairo:
--------------------------------------------------------------------------------
1 | @external
2 | func test_ok():
3 | return ()
4 | end
5 |
--------------------------------------------------------------------------------