Skip to content

Commit 686d4ee

Browse files
committed
v0.21.0
2 parents 8783383 + 8d02f01 commit 686d4ee

File tree

19 files changed

+11081
-68
lines changed

19 files changed

+11081
-68
lines changed

.github/workflows/checks.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: Checks
22

33
env:
44
DEVNET_SHA: "c6ffb99"
5+
CAIRO_LANG_VERSION: "0.13.1"
56

67
on:
78
push:
@@ -86,7 +87,7 @@ jobs:
8687
- name: Install deprecated cairo compiler
8788
run: |
8889
pip install --upgrade setuptools
89-
pip install cairo-lang==0.13.0
90+
pip install cairo-lang==${{ env.CAIRO_LANG_VERSION }}
9091
9192
- name: Set up Python ${{ matrix.python-version }}
9293
uses: actions/setup-python@v4
@@ -105,7 +106,7 @@ jobs:
105106
uses: actions/cache@v3
106107
with:
107108
path: starknet_py/tests/e2e/mock/contracts_compiled
108-
key: ${{ runner.os }}-contracts-${{ hashFiles('starknet_py/tests/e2e/mock/contracts', 'poetry.lock') }}
109+
key: ${{ runner.os }}-contracts-${{ hashFiles('starknet_py/tests/e2e/mock/contracts', 'poetry.lock') }}-${{ env.CAIRO_LANG_VERSION }}
109110

110111
- name: Compile contracts
111112
if: steps.cache-contracts.outputs.cache-hit != 'true'

docs/migration_guide.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ Migration guide
22
===============
33

44
******************************
5-
0.21.0 (alpha) Migration guide
5+
0.21.0 Migration guide
66
******************************
77

8-
Version 0.21.0 of **starknet.py** comes with support for RPC 0.7.0-rc2!
8+
Version 0.21.0 of **starknet.py** comes with support for RPC 0.7.0!
99

1010
0.21.0 Targeted versions
1111
------------------------
1212

1313
- Starknet - `0.13.1 <https://docs.starknet.io/documentation/starknet_versions/version_notes/#version0.13.1>`_
14-
- RPC - `0.7.0-rc2 <https://github.com/starkware-libs/starknet-specs/releases/tag/v0.7.0-rc2>`_
14+
- RPC - `0.7.0 <https://github.com/starkware-libs/starknet-specs/releases/tag/v0.7.0>`_
1515

1616
0.21.0 Breaking changes
1717
-----------------------
@@ -31,6 +31,7 @@ Version 0.21.0 of **starknet.py** comes with support for RPC 0.7.0-rc2!
3131
3. :class:`InvokeTransactionTrace`, :class:`DeclareTransactionTrace` and :class:`DeployAccountTransactionTrace` have a new required field ``execution_resources``
3232
4. :class:`EstimatedFee` has new required fields ``data_gas_consumed`` and ``data_gas_price``
3333
5. :class:`StarknetBlock`, :class:`PendingStarknetBlock`, :class:`StarknetBlockWithTxHashes`, :class:`PendingStarknetBlockWithTxHashes` have new required fields ``l1_data_gas_price`` and ``l1_da_mode``
34+
6. :class:`SierraContractClass` has an additional propery ``parsed_abi``
3435

3536
**********************
3637
0.20.0 Migration guide

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "starknet-py"
3-
version = "0.21.0-alpha"
3+
version = "0.21.0"
44
description = "A python SDK for Starknet"
55
authors = ["Tomasz Rejowski <tomasz.rejowski@swmansion.com>", "Jakub Ptak <jakub.ptak@swmansion.com>"]
66
include = ["starknet_py", "starknet_py/utils/crypto/libcrypto_c_exports.*"]

starknet_py/contract.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from __future__ import annotations
22

33
import dataclasses
4-
import json
54
from abc import ABC, abstractmethod
65
from dataclasses import dataclass
76
from functools import cached_property
@@ -280,7 +279,8 @@ def _get_abi(self) -> List:
280279
sierra_compiled_contract = create_sierra_compiled_contract(
281280
compiled_contract=self.compiled_contract
282281
)
283-
abi = json.loads(sierra_compiled_contract.abi)
282+
abi = sierra_compiled_contract.parsed_abi
283+
284284
except Exception as exc:
285285
raise ValueError(
286286
"Contract's ABI can't be converted to format List[Dict]. "

starknet_py/contract_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def test_compute_hash(balance_contract):
88
assert (
99
Contract.compute_contract_hash(balance_contract)
10-
== 0x12177EA61E5791CC068D7EE979B74F60A7205A23404C07440F4892B826147C0
10+
== 0x7A98EAB69A2592EF5D3805990A43525D633DDC42B4D5B2524C7F38B7C59265F
1111
)
1212

1313

starknet_py/hash/casm_class_hash.py

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
from typing import List
1+
from typing import List, Optional, Sequence, Tuple
22

33
from poseidon_py.poseidon_hash import poseidon_hash_many
44

55
from starknet_py.cairo.felt import encode_shortstring
6+
from starknet_py.hash.compiled_class_hash_objects import (
7+
BytecodeLeaf,
8+
BytecodeSegment,
9+
BytecodeSegmentedNode,
10+
BytecodeSegmentStructure,
11+
NestedIntList,
12+
)
613
from starknet_py.net.client_models import CasmClass, CasmClassEntryPoint
714

815
CASM_CLASS_VERSION = "COMPILED_CLASS_V1"
@@ -26,7 +33,14 @@ def compute_casm_class_hash(casm_contract_class: CasmClass) -> int:
2633
_entry_points_array(_entry_points.constructor)
2734
)
2835

29-
bytecode_hash = poseidon_hash_many(casm_contract_class.bytecode)
36+
if casm_contract_class.bytecode_segment_lengths is not None:
37+
bytecode_hash = create_bytecode_segment_structure(
38+
bytecode=casm_contract_class.bytecode,
39+
bytecode_segment_lengths=casm_contract_class.bytecode_segment_lengths,
40+
visited_pcs=None,
41+
).hash()
42+
else:
43+
bytecode_hash = poseidon_hash_many(casm_contract_class.bytecode)
3044

3145
return poseidon_hash_many(
3246
[
@@ -51,3 +65,90 @@ def _entry_points_array(entry_points: List[CasmClassEntryPoint]) -> List[int]:
5165
)
5266

5367
return entry_points_array
68+
69+
70+
# create_bytecode_segment_structure and _create_bytecode_segment_structure_inner are copied from
71+
# https://github.com/starkware-libs/cairo-lang/blob/v0.13.1/src/starkware/starknet/core/os/contract_class/compiled_class_hash.py
72+
73+
74+
def create_bytecode_segment_structure(
75+
bytecode: List[int],
76+
bytecode_segment_lengths: NestedIntList,
77+
visited_pcs: Optional[Sequence[int]],
78+
) -> BytecodeSegmentStructure:
79+
"""
80+
Creates a BytecodeSegmentStructure instance from the given bytecode and
81+
bytecode_segment_lengths.
82+
"""
83+
rev_visited_pcs = list(
84+
visited_pcs if visited_pcs is not None else range(len(bytecode))
85+
)[::-1]
86+
87+
res, total_len = _create_bytecode_segment_structure_inner(
88+
bytecode=bytecode,
89+
bytecode_segment_lengths=bytecode_segment_lengths,
90+
visited_pcs=rev_visited_pcs,
91+
bytecode_offset=0,
92+
)
93+
assert total_len == len(
94+
bytecode
95+
), f"Invalid length bytecode segment structure: {total_len}. Bytecode length: {len(bytecode)}."
96+
assert len(rev_visited_pcs) == 0, f"PC {rev_visited_pcs[-1]} is out of range."
97+
return res
98+
99+
100+
def _create_bytecode_segment_structure_inner(
101+
bytecode: List[int],
102+
bytecode_segment_lengths: NestedIntList,
103+
visited_pcs: List[int],
104+
bytecode_offset: int,
105+
) -> Tuple[BytecodeSegmentStructure, int]:
106+
"""
107+
Helper function for `create_bytecode_segment_structure`.
108+
`visited_pcs` should be given in reverse order, and is consumed by the function.
109+
Returns the BytecodeSegmentStructure and the total length of the processed segment.
110+
"""
111+
if isinstance(bytecode_segment_lengths, int):
112+
segment_end = bytecode_offset + bytecode_segment_lengths
113+
114+
# Remove all the visited PCs that are in the segment.
115+
while len(visited_pcs) > 0 and bytecode_offset <= visited_pcs[-1] < segment_end:
116+
visited_pcs.pop()
117+
118+
return (
119+
BytecodeLeaf(data=bytecode[bytecode_offset:segment_end]),
120+
bytecode_segment_lengths,
121+
)
122+
123+
res = []
124+
total_len = 0
125+
for item in bytecode_segment_lengths:
126+
visited_pc_before = visited_pcs[-1] if len(visited_pcs) > 0 else None
127+
128+
current_structure, item_len = _create_bytecode_segment_structure_inner(
129+
bytecode=bytecode,
130+
bytecode_segment_lengths=item,
131+
visited_pcs=visited_pcs,
132+
bytecode_offset=bytecode_offset,
133+
)
134+
135+
visited_pc_after = visited_pcs[-1] if len(visited_pcs) > 0 else None
136+
is_used = visited_pc_after != visited_pc_before
137+
138+
if is_used and visited_pc_before != bytecode_offset:
139+
raise ValueError(
140+
f"Invalid segment structure: PC {visited_pc_before} was visited, "
141+
f"but the beginning of the segment ({bytecode_offset}) was not."
142+
)
143+
144+
res.append(
145+
BytecodeSegment(
146+
segment_length=item_len,
147+
is_used=is_used,
148+
inner_structure=current_structure,
149+
)
150+
)
151+
bytecode_offset += item_len
152+
total_len += item_len
153+
154+
return BytecodeSegmentedNode(segments=res), total_len

starknet_py/hash/casm_class_hash_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
("minimal_contract_compiled.casm", 0x186f6c4ca3af40dbcbf3f08f828ab0ee072938aaaedccc74ef3b9840cbd9fb3),
1717
("test_contract_compiled.casm", 0x379b75c0922c68e73f6451b69e8e50b7f0745e6fa3f67ffc0b9608238eeaf45),
1818
("token_bridge_compiled.casm", 0x1d60f20e5dd449af4e6b0d63821cfa95f3469faa942caf78eba2172e2ec3468),
19+
("precompiled/starknet_contract_v2_6.casm", 0x603dd72504d8b0bc54df4f1102fdcf87fc3b2b94750a9083a5876913eec08e4),
1920
],
2021
)
2122
def test_compute_casm_class_hash(casm_contract_class_source, expected_casm_class_hash):

starknet_py/hash/class_hash_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212

1313
@pytest.mark.parametrize(
1414
"contract_source, expected_class_hash", [
15-
("balance_compiled.json", 0x12177ea61e5791cc068d7ee979b74f60a7205a23404c07440f4892b826147c0),
16-
("map_compiled.json", 0x45dc8f1a90d242f9ebdd07c42301eb16845fbad294f7f9118cce544c16d64b4),
17-
("erc20_compiled.json", 0x528d1ce44f53e888c2259738018e2e77bea9cb97c8b7fc7edab67aa4a880181),
18-
("oz_proxy_compiled.json", 0x3e1526155defb7e26a017e9020e1043cce3c5a9144a9ce497c95648ababbdf1),
19-
("argent_proxy_compiled.json", 0x191295ed4e4bbc63209aaf4d025979f8180fe998c761f616ccd29b5acc8ae1f),
20-
("universal_deployer_compiled.json", 0x1fda6c88607d4edd7881671959cf73fb2172c952910a60f3d01ef0cd63a635),
15+
("balance_compiled.json", 0x7a98eab69a2592ef5d3805990a43525d633ddc42b4d5b2524c7f38b7c59265f),
16+
("map_compiled.json", 0x5eefff2c17c81fb81b1c34d2a9f324e7baf8c3099165b94d037a84b74b6900e),
17+
("erc20_compiled.json", 0x2c709fc176283331897d0c5f113ba64b00e1530c3e91103dcf1b05a056b1aa1),
18+
("oz_proxy_compiled.json", 0x382f95037fa7983ff69465b9d3f7394ce336870631066de682cf547dc1899dd),
19+
("argent_proxy_compiled.json", 0x743aa3636b7c795931e9c4ed56dc57e7edda223a66c09df04fda40f9ba4cd53),
20+
("universal_deployer_compiled.json", 0x710bb1f5ef7f208249a370372a7586b72a759fbd2923013b14bd7f2e51bc4c),
2121
("precompiled/oz_proxy_address_0.8.1_compiled.json", 0x413c36c287cb410d42f9e531563f68ac60a2913b5053608d640fb9b643acfe6),
2222
]
2323
)
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# File is copied from
2+
# https://github.com/starkware-libs/cairo-lang/blob/v0.13.1/src/starkware/starknet/core/os/contract_class/compiled_class_hash_objects.py
3+
4+
import dataclasses
5+
import itertools
6+
from abc import ABC, abstractmethod
7+
from typing import Any, List, Union
8+
9+
from poseidon_py.poseidon_hash import poseidon_hash_many
10+
11+
12+
class BytecodeSegmentStructure(ABC):
13+
"""
14+
Represents the structure of the bytecode to allow loading it partially into the OS memory.
15+
See the documentation of the OS function `bytecode_hash_node` in `compiled_class.cairo`
16+
for more details.
17+
"""
18+
19+
@abstractmethod
20+
def hash(self) -> int:
21+
"""
22+
Computes the hash of the node.
23+
"""
24+
25+
def bytecode_with_skipped_segments(self):
26+
"""
27+
Returns the bytecode of the node.
28+
Skipped segments are replaced with [-1, -2, -2, -2, ...].
29+
"""
30+
res: List[int] = []
31+
self.add_bytecode_with_skipped_segments(res)
32+
return res
33+
34+
@abstractmethod
35+
def add_bytecode_with_skipped_segments(self, data: List[int]):
36+
"""
37+
Same as bytecode_with_skipped_segments, but appends the result to the given list.
38+
"""
39+
40+
41+
@dataclasses.dataclass
42+
class BytecodeLeaf(BytecodeSegmentStructure):
43+
"""
44+
Represents a leaf in the bytecode segment tree.
45+
"""
46+
47+
data: List[int]
48+
49+
def hash(self) -> int:
50+
return poseidon_hash_many(self.data)
51+
52+
def add_bytecode_with_skipped_segments(self, data: List[int]):
53+
data.extend(self.data)
54+
55+
56+
@dataclasses.dataclass
57+
class BytecodeSegmentedNode(BytecodeSegmentStructure):
58+
"""
59+
Represents an internal node in the bytecode segment tree.
60+
Each child can be loaded into memory or skipped.
61+
"""
62+
63+
segments: List["BytecodeSegment"]
64+
65+
def hash(self) -> int:
66+
return (
67+
poseidon_hash_many(
68+
itertools.chain( # pyright: ignore
69+
*[
70+
(node.segment_length, node.inner_structure.hash())
71+
for node in self.segments
72+
]
73+
)
74+
)
75+
+ 1
76+
)
77+
78+
def add_bytecode_with_skipped_segments(self, data: List[int]):
79+
for segment in self.segments:
80+
if segment.is_used:
81+
segment.inner_structure.add_bytecode_with_skipped_segments(data)
82+
else:
83+
data.append(-1)
84+
data.extend(-2 for _ in range(segment.segment_length - 1))
85+
86+
87+
@dataclasses.dataclass
88+
class BytecodeSegment:
89+
"""
90+
Represents a child of BytecodeSegmentedNode.
91+
"""
92+
93+
# The length of the segment.
94+
segment_length: int
95+
# Should the segment (or part of it) be loaded to memory.
96+
# In other words, is the segment used during the execution.
97+
# Note that if is_used is False, the entire segment is not loaded to memory.
98+
# If is_used is True, it is possible that part of the segment will be skipped (according
99+
# to the "is_used" field of the child segments).
100+
is_used: bool
101+
# The inner structure of the segment.
102+
inner_structure: BytecodeSegmentStructure
103+
104+
def __post_init__(self):
105+
assert (
106+
self.segment_length > 0
107+
), f"Invalid segment length: {self.segment_length}."
108+
109+
110+
# Represents a nested list of integers. E.g., [1, [2, [3], 4], 5, 6].
111+
NestedIntList = Union[int, List[Any]]

0 commit comments

Comments
 (0)