Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
ddoktorski committed Nov 10, 2023
2 parents 308d42e + fcf4f61 commit 9cfb21c
Show file tree
Hide file tree
Showing 29 changed files with 836 additions and 490 deletions.
47 changes: 47 additions & 0 deletions docs/migration_guide.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,53 @@
Migration guide
===============

**********************
0.18.3 Migration guide
**********************

Version 0.18.3 of **starknet.py** comes with support for RPC 0.5.0!


0.18.3 Targeted versions
------------------------

- Starknet - `0.12.2 <https://community.starknet.io/t/introducing-p2p-authentication-and-mismatch-resolution-in-v0-12-2/97993>`_
- RPC - `0.5.1 <https://github.com/starkware-libs/starknet-specs/releases/tag/v0.5.1>`_


0.18.3 Breaking changes
-----------------------

1. Support for ``TESTNET2`` network has been removed.

.. currentmodule:: starknet_py.net.client

2. :meth:`FullNodeClient.get_pending_transactions` method has been removed. It is advised to use :meth:`FullNodeClient.get_block` method with ``block_number="pending"`` argument.

.. currentmodule:: starknet_py.net.client_models

3. :class:`PendingStarknetBlock` field ``parent_hash`` is now named ``parent_block_hash``.
4. :class:`FunctionInvocation` fields ``events`` and ``messages`` have been changed from ``List[Event]`` and ``List[L2toL1Message]`` to ``List[OrderedEvent]`` and ``List[OrderedMessage]`` respectively.
5. ``cairo_version`` parameter in :meth:`Account.sign_invoke_transaction` and :meth:`Account.execute` has been removed.

0.18.3 Minor changes
--------------------

1. :class:`StarknetBlock`, :class:`StarknetBlockWithTxHashes`, :class:`PendingStarknetBlock` and :class:`PendingStarknetBlockWithTxHashes` now have two additional fields: ``starknet_version`` and ``l1_gas_price``.
2. :class:`PendingStarknetBlock` and :class:`PendingStarknetBlockWithTxHashes` fields ``timestamp``, ``sequencer_address`` and ``parent_block_hash`` are now required, not optional.
3. :class:`TransactionReceipt` now has an additional field - ``message_hash`` (for ``L1_HANDLER_TXN_RECEIPT``).
4. Most fields in ``TransactionTrace`` classes are now optional.
5. :class:`InvokeTransactionTrace`, :class:`DeclareTransactionTrace`, :class:`DeployAccountTransactionTrace` and :class:`L1HandlerTransactionTrace` classes now have an additional field - ``state_diff``.


|
.. raw:: html

<hr>

|
**********************
0.18.2 Migration guide
**********************
Expand Down
49 changes: 30 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "starknet-py"
version = "0.18.2"
version = "0.18.3"
description = "A python SDK for Starknet"
authors = ["Tomasz Rejowski <tomasz.rejowski@swmansion.com>", "Jakub Ptak <jakub.ptak@swmansion.com>"]
include = ["starknet_py", "starknet_py/utils/crypto/libcrypto_c_exports.*"]
Expand Down Expand Up @@ -54,31 +54,37 @@ cairo-lang = {version = "0.12.2", python = ">=3.9, <3.10"}
starknet-devnet = {version = "0.6.2", python = ">=3.9, <3.10"}

[tool.poe.tasks]
test.shell = "pytest -n auto -v --reruns 10 --only-rerun aiohttp.client_exceptions.ClientConnectorError --cov=starknet_py starknet_py"
test = [
"clean_coverage",
"test_ci_gateway_v1 --disable-warnings -qq",
"test_ci_full_node_v1 --disable-warnings -qq",
"test_ci_gateway_v2 --disable-warnings -qq",
"test_ci_full_node_v2 --disable-warnings -qq",
"test_ci_on_networks_gateway --disable-warnings -qq",
"test_ci_on_networks_full_node --disable-warnings -qq",
"test_report --skip-covered"
]

test_ci = ["test_ci_gateway_v1", "test_ci_full_node_v1", "test_ci_gateway_v2", "test_ci_full_node_v2"]
test_ci_gateway_v1.shell = "coverage run -m pytest --client=gateway --contract_dir=v1 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_full_node_v1.shell = "coverage run -m pytest --client=full_node --contract_dir=v1 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_gateway_v2.shell = "coverage run -m pytest --client=gateway --contract_dir=v2 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_full_node_v2.shell = "coverage run -m pytest --client=full_node --contract_dir=v2 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_gateway_v1 = "coverage run -a -m pytest --client=gateway --contract_dir=v1 starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_full_node_v1 = "coverage run -a -m pytest --client=full_node --contract_dir=v1 starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_gateway_v2 = "coverage run -a -m pytest --client=gateway --contract_dir=v2 starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"
test_ci_full_node_v2 = "coverage run -a -m pytest --client=full_node --contract_dir=v2 starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core --ignore=starknet_py/tests/e2e/tests_on_networks"

# order of tests below is important, explanation in /tests_on_networks/client_test.py above 'test_wait_for_tx_reverted_full_node'
test_ci_on_networks = ["test_ci_on_networks_full_node", "test_ci_on_networks_gateway"]
test_ci_on_networks_gateway = "coverage run -m pytest --client=gateway -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/tests_on_networks"
test_ci_on_networks_full_node = "coverage run -m pytest --client=full_node -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/tests_on_networks"
test_ci_on_networks_gateway = "coverage run -a -m pytest --client=gateway starknet_py/tests/e2e/tests_on_networks"
test_ci_on_networks_full_node = "coverage run -a -m pytest --client=full_node starknet_py/tests/e2e/tests_on_networks"

test_ci_docs = ["test_ci_docs_gateway_v1", "test_ci_docs_full_node_v1", "test_ci_docs_gateway_v2", "test_ci_docs_full_node_v2"]
test_ci_docs_gateway_v1.shell = "coverage run -m pytest --client=gateway --contract_dir=v1 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs"
test_ci_docs_full_node_v1.shell = "coverage run -m pytest --client=full_node --contract_dir=v1 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs"
test_ci_docs_gateway_v2.shell = "coverage run -m pytest --client=gateway --contract_dir=v2 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs"
test_ci_docs_full_node_v2.shell = "coverage run -m pytest --client=full_node --contract_dir=v2 -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs"

test_unit.shell = "pytest -n auto -v starknet_py --ignore=starknet_py/tests/e2e"
test_e2e.shell = "pytest -n auto -v starknet_py/tests/e2e --ignore=starknet_py/tests/e2e/docs"
test_docs.shell = "pytest -n auto -v starknet_py/tests/e2e/docs"
test_core.shell = "pytest -v starknet_py/tests/e2e/core --net=integration"
test_report.shell = "coverage report"
test_ci_docs_gateway_v1 = "coverage run -a -m pytest --client=gateway --contract_dir=v1 starknet_py/tests/e2e/docs"
test_ci_docs_full_node_v1 = "coverage run -a -m pytest --client=full_node --contract_dir=v1 starknet_py/tests/e2e/docs"
test_ci_docs_gateway_v2 = "coverage run -a -m pytest --client=gateway --contract_dir=v2 starknet_py/tests/e2e/docs"
test_ci_docs_full_node_v2 = "coverage run -a -m pytest --client=full_node --contract_dir=v2 starknet_py/tests/e2e/docs"

test_report = "coverage report -m"
test_html.shell = "coverage html && open ./htmlcov/index.html"
clean_coverage = "coverage erase"
docs_create = { shell = "make -C docs html" }
docs_open = { shell = "open docs/_build/html/index.html" }
lint = "pylint starknet_py"
Expand All @@ -102,7 +108,7 @@ source = ["starknet_py"]


[tool.coverage.report]
omit = ["*_test.py", "starknet_py/tests/e2e/*", "starknet_py/utils/docs.py"]
omit = ["*_test.py", "test_*.py", "starknet_py/tests/*"]
skip_empty = true


Expand All @@ -128,6 +134,11 @@ profile = "black"
skip_gitignore = true

[tool.pytest.ini_options]
addopts = [
"-v",
"--reruns=5",
"--only-rerun=aiohttp.client_exceptions.ClientConnectorError"
]
markers = [
"run_on_testnet: marks test that will only run on testnet (when --net=testnet)",
"run_on_devnet: marks test that will only run on devnet (when --net=devnet)"
Expand Down
46 changes: 45 additions & 1 deletion starknet_py/hash/address.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from typing import Sequence

from starknet_py.constants import CONTRACT_ADDRESS_PREFIX, L2_ADDRESS_UPPER_BOUND
from starknet_py.hash.utils import compute_hash_on_elements
from starknet_py.hash.utils import (
HEX_PREFIX,
_starknet_keccak,
compute_hash_on_elements,
encode_uint,
get_bytes_length,
)


def compute_address(
Expand Down Expand Up @@ -33,3 +39,41 @@ def compute_address(
)

return raw_address % L2_ADDRESS_UPPER_BOUND


def get_checksum_address(address: str) -> str:
"""
Outputs formatted checksum address.
Follows implementation of starknet.js. It is not compatible with EIP55 as it treats hex string as encoded number,
instead of encoding it as ASCII string.
:param address: Address to encode
:return: Checksum address
"""
if not address.lower().startswith(HEX_PREFIX):
raise ValueError(f"{address} is not a valid hexadecimal address.")

int_address = int(address, 16)
string_address = address[2:].zfill(64)

address_in_bytes = encode_uint(int_address, get_bytes_length(int_address))
address_hash = _starknet_keccak(address_in_bytes)

result = "".join(
(
char.upper()
if char.isalpha() and (address_hash >> 256 - 4 * i - 1) & 1
else char
)
for i, char in enumerate(string_address)
)

return f"{HEX_PREFIX}{result}"


def is_checksum_address(address: str) -> bool:
"""
Checks if provided string is in a checksum address format.
"""
return get_checksum_address(address) == address
51 changes: 50 additions & 1 deletion starknet_py/hash/address_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from starknet_py.hash.address import compute_address
import pytest

from starknet_py.hash.address import (
compute_address,
get_checksum_address,
is_checksum_address,
)


def test_compute_address():
Expand All @@ -22,3 +28,46 @@ def test_compute_address_with_deployer_address():
)
== 3179899882984850239687045389724311807765146621017486664543269641150383510696
)


@pytest.mark.parametrize(
"address, checksum_address",
[
(
"0x2fd23d9182193775423497fc0c472e156c57c69e4089a1967fb288a2d84e914",
"0x02Fd23d9182193775423497fc0c472E156C57C69E4089A1967fb288A2d84e914",
),
(
"0x00abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefab",
"0x00AbcDefaBcdefabCDEfAbCDEfAbcdEFAbCDEfabCDefaBCdEFaBcDeFaBcDefAb",
),
(
"0xfedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafedcbafe",
"0x00fEdCBafEdcbafEDCbAFedCBAFeDCbafEdCBAfeDcbaFeDCbAfEDCbAfeDcbAFE",
),
("0xa", "0x000000000000000000000000000000000000000000000000000000000000000A"),
(
"0x0",
"0x0000000000000000000000000000000000000000000000000000000000000000",
),
],
)
def test_get_checksum_address(address, checksum_address):
assert get_checksum_address(address) == checksum_address


@pytest.mark.parametrize("address", ["", "0xx", "0123"])
def test_get_checksum_address_raises_on_invalid_address(address):
with pytest.raises(ValueError):
get_checksum_address(address)


@pytest.mark.parametrize(
"address, is_checksum",
[
("0x02Fd23d9182193775423497fc0c472E156C57C69E4089A1967fb288A2d84e914", True),
("0x000000000000000000000000000000000000000000000000000000000000000a", False),
],
)
def test_is_checksum_address(address, is_checksum):
assert is_checksum_address(address) == is_checksum
19 changes: 19 additions & 0 deletions starknet_py/hash/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from starknet_py.constants import EC_ORDER

MASK_250 = 2**250 - 1
HEX_PREFIX = "0x"


def _starknet_keccak(data: bytes) -> int:
Expand All @@ -25,6 +26,12 @@ def _starknet_keccak(data: bytes) -> int:
return int_from_bytes(k.digest()) & MASK_250


def keccak256(data: bytes) -> int:
k = keccak.new(digest_bits=256)
k.update(data)
return int_from_bytes(k.digest())


def pedersen_hash(left: int, right: int) -> int:
"""
One of two hash functions (along with _starknet_keccak) used throughout Starknet.
Expand Down Expand Up @@ -70,3 +77,15 @@ def private_to_stark_key(priv_key: int) -> int:
Deduces the public key given a private key.
"""
return cpp_get_public_key(priv_key)


def encode_uint(value: int, bytes_length: int = 32) -> bytes:
return value.to_bytes(bytes_length, byteorder="big")


def encode_uint_list(data: List[int]) -> bytes:
return b"".join(encode_uint(x) for x in data)


def get_bytes_length(value: int) -> int:
return (value.bit_length() + 7) // 8
54 changes: 53 additions & 1 deletion starknet_py/hash/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
# fmt: off
import pytest

from starknet_py.hash.utils import compute_hash_on_elements, pedersen_hash
from starknet_py.hash.utils import (
compute_hash_on_elements,
encode_uint,
encode_uint_list,
keccak256,
pedersen_hash,
)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -33,3 +39,49 @@ def test_compute_hash_on_elements(data, calculated_hash):
)
def test_pedersen_hash(first, second, hash_):
assert pedersen_hash(first, second) == hash_


@pytest.mark.parametrize(
"value, expected_encoded",
[
(0, b"\x00" * 32),
(1, b"\x00" * 31 + b"\x01"),
(123456789, b"\x00" * 28 + b"\x07\x5b\xcd\x15")
]
)
def test_encode_uint(value, expected_encoded):
assert encode_uint(value) == expected_encoded


@pytest.mark.parametrize(
"value, expected_encoded",
[
([], b""),
([1, 2, 3], b"\x00" * 31 + b"\x01" + b"\x00" * 31 + b"\x02" + b"\x00" * 31 + b"\x03"),
]
)
def test_encode_uint_list(value, expected_encoded):
assert encode_uint_list(value) == expected_encoded


@pytest.mark.parametrize(
"string, expected_hash",
[
("", 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470),
("test", 0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658),
("longer test string", 0x47bed17bfbbc08d6b5a0f603eff1b3e932c37c10b865847a7bc73d55b260f32a)
]
)
def test_keccak256_strings(string, expected_hash):
assert keccak256(string.encode("utf-8")) == expected_hash


@pytest.mark.parametrize(
"value, expected_hash",
[
(4, 0x8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b),
(5, 0x036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0)
]
)
def test_keccak256_ints(value, expected_hash):
assert keccak256(encode_uint(value)) == expected_hash
Loading

0 comments on commit 9cfb21c

Please sign in to comment.