diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index ec02f3afc..f1638fe58 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -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 `_ +- RPC - `0.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 + +
+ +| + ********************** 0.18.2 Migration guide ********************** diff --git a/pyproject.toml b/pyproject.toml index 89d21a3f6..2487d8b97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 ", "Jakub Ptak "] include = ["starknet_py", "starknet_py/utils/crypto/libcrypto_c_exports.*"] @@ -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" @@ -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 @@ -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)" diff --git a/starknet_py/hash/address.py b/starknet_py/hash/address.py index 62bc31014..3e4edbae5 100644 --- a/starknet_py/hash/address.py +++ b/starknet_py/hash/address.py @@ -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( @@ -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 diff --git a/starknet_py/hash/address_test.py b/starknet_py/hash/address_test.py index af90f36d6..393d90577 100644 --- a/starknet_py/hash/address_test.py +++ b/starknet_py/hash/address_test.py @@ -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(): @@ -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 diff --git a/starknet_py/hash/utils.py b/starknet_py/hash/utils.py index c686bf72d..7f2c563dc 100644 --- a/starknet_py/hash/utils.py +++ b/starknet_py/hash/utils.py @@ -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: @@ -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. @@ -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 diff --git a/starknet_py/hash/utils_test.py b/starknet_py/hash/utils_test.py index 1523b95ef..841224c47 100644 --- a/starknet_py/hash/utils_test.py +++ b/starknet_py/hash/utils_test.py @@ -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( @@ -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 diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index 53855e239..05b2310dd 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -273,17 +273,7 @@ async def sign_invoke_transaction( nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, - # TODO (#1184): remove that - cairo_version: Optional[int] = None, ) -> Invoke: - # TODO (#1184): remove that - if cairo_version is not None: - warnings.warn( - "Parameter 'cairo_version' has been deprecated. It is calculated automatically based on your account's " - "contract class.", - category=DeprecationWarning, - ) - execute_tx = await self._prepare_invoke( calls, nonce=nonce, @@ -415,17 +405,7 @@ async def execute( nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, - # TODO (#1184): remove that - cairo_version: Optional[int] = None, ) -> SentTransactionResponse: - # TODO (#1184): remove that - if cairo_version is not None: - warnings.warn( - "Parameter 'cairo_version' has been deprecated. It is calculated automatically based on your account's " - "contract class.", - category=DeprecationWarning, - ) - execute_transaction = await self.sign_invoke_transaction( calls, nonce=nonce, @@ -464,7 +444,7 @@ async def deploy_account( Provided address must be first prefunded with enough tokens, otherwise the method will fail. - If using Client for either TESTNET, TESTNET2 or MAINNET, this method will verify if the address balance + If using Client for either TESTNET or MAINNET, this method will verify if the address balance is high enough to cover deployment costs. :param address: calculated and prefunded address of the new account. @@ -517,7 +497,6 @@ async def deploy_account( if chain in ( StarknetChainId.TESTNET, - StarknetChainId.TESTNET2, StarknetChainId.MAINNET, ): balance = await account.get_balance() @@ -537,7 +516,6 @@ def _default_token_address_for_chain( ) -> str: if (chain_id or self._chain_id) not in [ StarknetChainId.TESTNET, - StarknetChainId.TESTNET2, StarknetChainId.MAINNET, ]: raise ValueError( diff --git a/starknet_py/net/account/account_test.py b/starknet_py/net/account/account_test.py index 8f5b5e373..468e35d9d 100644 --- a/starknet_py/net/account/account_test.py +++ b/starknet_py/net/account/account_test.py @@ -1,4 +1,3 @@ -import sys from unittest.mock import AsyncMock, patch import pytest @@ -8,13 +7,13 @@ from starknet_py.net.full_node_client import FullNodeClient from starknet_py.net.gateway_client import GatewayClient from starknet_py.net.models import StarknetChainId, parse_address -from starknet_py.net.networks import MAINNET, TESTNET, TESTNET2 +from starknet_py.net.networks import MAINNET, TESTNET from starknet_py.net.signer.stark_curve_signer import KeyPair, StarkCurveSigner from starknet_py.tests.e2e.fixtures.constants import MAX_FEE @pytest.mark.asyncio -@pytest.mark.parametrize("net", (TESTNET, TESTNET2, MAINNET)) +@pytest.mark.parametrize("net", (TESTNET, MAINNET)) @pytest.mark.parametrize( "call_contract", [ @@ -50,11 +49,6 @@ async def test_get_balance_default_token_address(net, call_contract): assert call.to_addr == parse_address(FEE_CONTRACT_ADDRESS) -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_account_get_balance(account, map_contract): balance = await account.get_balance() diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index f1915699e..57bf02f2f 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -109,8 +109,6 @@ async def sign_invoke_transaction( nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, - # TODO (#1184): remove that and docstring - cairo_version: Optional[int] = None, ) -> Invoke: """ Takes calls and creates signed Invoke. @@ -119,12 +117,6 @@ async def sign_invoke_transaction( :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. - :param cairo_version: - Cairo version of the account used. - - .. deprecated:: 0.18.2 - Parameter `cairo_version` has been deprecated - it is calculated automatically based on - your account's contract class. :return: Invoke created from the calls. """ @@ -203,8 +195,6 @@ async def execute( nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, - # TODO (#1184): remove that and docstring - cairo_version: Optional[int] = None, ) -> SentTransactionResponse: """ Takes calls and executes transaction. @@ -213,12 +203,6 @@ async def execute( :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. - :param cairo_version: - Cairo version of the account used. - - .. deprecated:: 0.18.2 - Parameter `cairo_version` has been deprecated - it is calculated automatically based on - your account's contract class. :return: SentTransactionResponse. """ diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 160e0115e..77949c48d 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -179,7 +179,10 @@ async def wait_for_tx( raise TransactionRejectedError(message=tx_receipt.rejection_reason) if execution_status == TransactionExecutionStatus.REVERTED: - raise TransactionRevertedError(message=tx_receipt.revert_error) + # TODO (#1047): message should be always revert_reason once GatewayClient is deprecated + raise TransactionRevertedError( + message=(tx_receipt.revert_reason or tx_receipt.revert_error) + ) if execution_status == TransactionExecutionStatus.SUCCEEDED: return tx_receipt diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index e49446610..518d36446 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -71,8 +71,28 @@ class L2toL1Message: """ payload: List[int] - l1_address: int - l2_address: int + l2_address: int # from_address in spec + l1_address: int # to_address in spec + + +@dataclass +class ResourcePrice: + """ + Dataclass representing prices of L1 gas. + """ + + price_in_wei: int + price_in_strk: Optional[int] + + +@dataclass +class ResourceLimits: + """ + Dataclass representing resource limits. + """ + + max_amount: int + max_price_per_unit: int class TransactionType(Enum): @@ -97,7 +117,8 @@ class Transaction(ABC): # together with the rest of the data, it remains here (but is still Optional just in case as spec says) hash: Optional[int] signature: List[int] - max_fee: int + # Optional for DECLARE_V3 and DEPLOY_ACCOUNT_V3, where there is no `max_fee` field, but `l1_gas` + max_fee: Optional[int] version: int def __post_init__(self): @@ -128,6 +149,7 @@ class DeclareTransaction(Transaction): sender_address: int compiled_class_hash: Optional[int] = None # only in DeclareV2, hence Optional nonce: Optional[int] = None + l1_gas: Optional[ResourceLimits] = None # DECLARE_V3-only field, hence Optional @dataclass @@ -140,6 +162,9 @@ class DeployTransaction(Transaction): contract_address_salt: int constructor_calldata: List[int] class_hash: int + l1_gas: Optional[ + ResourceLimits + ] = None # DEPLOY_ACCOUNT_V3-only field, hence Optional @dataclass @@ -201,6 +226,7 @@ class TransactionFinalityStatus(Enum): ACCEPTED_ON_L1 = "ACCEPTED_ON_L1" +# TODO (#1047): split into PendingTransactionReceipt and TransactionReceipt? @dataclass class TransactionReceipt: """ @@ -213,7 +239,9 @@ class TransactionReceipt: events: List[Event] = field(default_factory=list) l2_to_l1_messages: List[L2toL1Message] = field(default_factory=list) - execution_status: Optional[TransactionExecutionStatus] = None + execution_status: Optional[ + TransactionExecutionStatus + ] = None # gateway/pending receipt field finality_status: Optional[TransactionFinalityStatus] = None status: Optional[ TransactionStatus @@ -225,6 +253,12 @@ class TransactionReceipt: block_number: Optional[int] = None block_hash: Optional[int] = None actual_fee: int = 0 + # TODO (#1047): change that into ExecutionResources class after gateway removal + # (values of course differ for each client) + # TODO (#1179): this field should be required + execution_resources: Optional[dict] = field(default_factory=dict) + + message_hash: Optional[int] = None # L1_HANDLER_TXN_RECEIPT-only rejection_reason: Optional[str] = None revert_reason: Optional[str] = None # full_node-only field @@ -232,7 +266,6 @@ class TransactionReceipt: # gateway only l1_to_l2_consumed_message: Optional[L1toL2Message] = None - execution_resources: Optional[dict] = field(default_factory=dict) transaction_index: Optional[int] = None @@ -279,13 +312,33 @@ class BlockStatus(Enum): @dataclass class PendingStarknetBlock: """ - Dataclass representing pending block on Starknet. + Dataclass representing a pending block on Starknet. """ transactions: List[Transaction] - timestamp: Optional[int] = None - sequencer_address: Optional[int] = None - parent_hash: Optional[int] = None + parent_block_hash: int + timestamp: int + sequencer_address: int + # TODO (#1179): this field should be required + l1_gas_price: Optional[ResourcePrice] = None + # TODO (#1179): this field should be required + starknet_version: Optional[str] = None + + +@dataclass +class PendingStarknetBlockWithTxHashes: + """ + Dataclass representing a pending block on Starknet containing transaction hashes. + """ + + transactions: List[int] + parent_block_hash: int + timestamp: int + sequencer_address: int + # TODO (#1179): this field should be required + l1_gas_price: Optional[ResourcePrice] = None + # TODO (#1179): this field should be required + starknet_version: Optional[str] = None @dataclass @@ -294,11 +347,19 @@ class StarknetBlockCommon: Dataclass representing a block header. """ + # TODO (#1047): change that into composition (with all the breaking changes it will be a minor thing there) + # pylint: disable=too-many-instance-attributes + block_hash: int parent_block_hash: int block_number: int root: int timestamp: int + sequencer_address: int + # TODO (#1179): this field should be required + l1_gas_price: Optional[ResourcePrice] + # TODO (#1179): this field should be required + starknet_version: Optional[str] @dataclass @@ -307,11 +368,20 @@ class StarknetBlock(StarknetBlockCommon): Dataclass representing a block on Starknet. """ - sequencer_address: int status: BlockStatus transactions: List[Transaction] +@dataclass +class StarknetBlockWithTxHashes(StarknetBlockCommon): + """ + Dataclass representing a block on Starknet containing transaction hashes. + """ + + status: BlockStatus + transactions: List[int] + + @dataclass class GatewayBlockTransactionReceipt: # pylint: disable=too-many-instance-attributes @@ -350,29 +420,6 @@ class GatewayBlock: starknet_version: Optional[str] = None -@dataclass -class StarknetBlockWithTxHashes(StarknetBlockCommon): - """ - Dataclass representing a block on Starknet containing transaction hashes. - """ - - sequencer_address: int - status: BlockStatus - transactions: List[int] - - -@dataclass -class PendingStarknetBlockWithTxHashes: - """ - Dataclass representing a block on Starknet containing transaction hashes. - """ - - transactions: List[int] - parent_block_hash: Optional[int] = None - sequencer_address: Optional[int] = None - timestamp: Optional[int] = None - - @dataclass class BlockHashAndNumber: block_hash: int @@ -674,9 +721,9 @@ class CasmClass: @dataclass -class TransactionStatusResponse: +class GatewayTransactionStatusResponse: """ - Dataclass representing transaction status. + Dataclass representing transaction status for the GatewayClient. """ block_hash: Optional[int] @@ -685,6 +732,16 @@ class TransactionStatusResponse: execution_status: Optional[TransactionExecutionStatus] = None +@dataclass +class TransactionStatusResponse: + """ + Dataclass representing transaction status for the FullNodeClient. + """ + + finality_status: TransactionStatus + execution_status: Optional[TransactionExecutionStatus] = None + + @dataclass class SignatureInput: """ @@ -706,17 +763,59 @@ class SignatureOnStateDiff: signature_input: SignatureInput +class DAMode(Enum): + """ + Enum specifying a storage domain in Starknet. Each domain has different gurantess regarding availability. + """ + + L1 = "L1" + L2 = "L2" + + +@dataclass +class ExecutionResources: + """ + Dataclass representing the resources consumed by the transaction. + """ + + # pylint: disable=too-many-instance-attributes + + # For now the class and schema related to it is unused, it is waiting here for refactoring. + steps: int + range_check_builtin_applications: int + pedersen_builtin_applications: int + poseidon_builtin_applications: int + ec_op_builtin_applications: int + ecdsa_builtin_applications: int + bitwise_builtin_applications: int + keccak_builtin_applications: int + memory_holes: Optional[int] = None + + # ------------------------------- Trace API dataclasses ------------------------------- @dataclass -class EventContent: +class OrderedEvent: """ - Dataclass representing contents of an event. + Dataclass representing an event alongside its order within the transaction. """ keys: List[int] data: List[int] + order: int + + +@dataclass +class OrderedMessage: + """ + Dataclass representing a message alongside its order within the transaction. + """ + + payload: List[int] + l2_address: int # from_address in spec + l1_address: int # to_address in spec + order: int class SimulationFlag(str, Enum): @@ -743,6 +842,7 @@ class CallType(Enum): Enum class representing call types. """ + DELEGATE = "DELEGATE" LIBRARY_CALL = "LIBRARY_CALL" CALL = "CALL" @@ -763,8 +863,8 @@ class FunctionInvocation: call_type: CallType result: List[int] calls: List["FunctionInvocation"] - events: List[Event] - messages: List[L2toL1Message] + events: List[OrderedEvent] + messages: List[OrderedMessage] @dataclass @@ -782,9 +882,10 @@ class InvokeTransactionTrace: Dataclass representing a transaction trace of an INVOKE transaction. """ - validate_invocation: Optional[FunctionInvocation] execute_invocation: Union[FunctionInvocation, RevertedFunctionInvocation] - fee_transfer_invocation: Optional[FunctionInvocation] + validate_invocation: Optional[FunctionInvocation] = None + fee_transfer_invocation: Optional[FunctionInvocation] = None + state_diff: Optional[StateDiff] = None @dataclass @@ -793,8 +894,9 @@ class DeclareTransactionTrace: Dataclass representing a transaction trace of an DECLARE transaction. """ - validate_invocation: Optional[FunctionInvocation] - fee_transfer_invocation: Optional[FunctionInvocation] + validate_invocation: Optional[FunctionInvocation] = None + fee_transfer_invocation: Optional[FunctionInvocation] = None + state_diff: Optional[StateDiff] = None @dataclass @@ -803,9 +905,10 @@ class DeployAccountTransactionTrace: Dataclass representing a transaction trace of an DEPLOY_ACCOUNT transaction. """ - validate_invocation: Optional[FunctionInvocation] constructor_invocation: FunctionInvocation - fee_transfer_invocation: Optional[FunctionInvocation] + validate_invocation: Optional[FunctionInvocation] = None + fee_transfer_invocation: Optional[FunctionInvocation] = None + state_diff: Optional[StateDiff] = None @dataclass @@ -815,6 +918,7 @@ class L1HandlerTransactionTrace: """ function_invocation: FunctionInvocation + state_diff: Optional[StateDiff] = None TransactionTrace = Union[ diff --git a/starknet_py/net/client_utils.py b/starknet_py/net/client_utils.py index e34632812..b2d751e6c 100644 --- a/starknet_py/net/client_utils.py +++ b/starknet_py/net/client_utils.py @@ -2,7 +2,8 @@ from typing_extensions import get_args -from starknet_py.net.client_models import Hash, Tag +from starknet_py.hash.utils import encode_uint, encode_uint_list +from starknet_py.net.client_models import Hash, L1HandlerTransaction, Tag def hash_to_felt(value: Hash) -> str: @@ -17,3 +18,21 @@ def hash_to_felt(value: Hash) -> str: def is_block_identifier(value: Union[int, Hash, Tag]) -> bool: return isinstance(value, str) and value in get_args(Tag) + + +def encode_l1_message(tx: L1HandlerTransaction) -> bytes: + # TODO (#1047): remove this assert once GatewayClient is deprecated and nonce is always required + assert tx.nonce is not None + + from_address = tx.calldata[0] + # Pop first element to have in calldata the actual payload + tx.calldata.pop(0) + + return ( + encode_uint(from_address) + + encode_uint(tx.contract_address) + + encode_uint(tx.nonce) + + encode_uint(tx.entry_point_selector) + + encode_uint(len(tx.calldata)) + + encode_uint_list(tx.calldata) + ) diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index deed7a616..7793140fe 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -6,6 +6,7 @@ from marshmallow import EXCLUDE from starknet_py.constants import RPC_CONTRACT_ERROR +from starknet_py.hash.utils import keccak256 from starknet_py.net.client import Client from starknet_py.net.client_errors import ClientError from starknet_py.net.client_models import ( @@ -19,6 +20,7 @@ EstimatedFee, EventsChunk, Hash, + L1HandlerTransaction, PendingBlockStateUpdate, PendingStarknetBlock, PendingStarknetBlockWithTxHashes, @@ -32,9 +34,11 @@ Tag, Transaction, TransactionReceipt, + TransactionStatusResponse, TransactionTrace, TransactionType, ) +from starknet_py.net.client_utils import encode_l1_message from starknet_py.net.http_client import RpcHttpClient from starknet_py.net.models.transaction import ( AccountTransaction, @@ -58,7 +62,6 @@ PendingBlockStateUpdateSchema, PendingStarknetBlockSchema, PendingStarknetBlockWithTxHashesSchema, - PendingTransactionsSchema, SentTransactionSchema, SierraContractClassSchema, SimulatedTransactionSchema, @@ -66,6 +69,7 @@ StarknetBlockWithTxHashesSchema, SyncStatusSchema, TransactionReceiptSchema, + TransactionStatusResponseSchema, TransactionTraceSchema, TypesOfTransactionsSchema, ) @@ -321,6 +325,20 @@ async def get_transaction( raise TransactionNotReceivedError() from ex return cast(Transaction, TypesOfTransactionsSchema().load(res, unknown=EXCLUDE)) + async def get_l1_message_hash(self, tx_hash: Hash) -> Hash: + """ + :param tx_hash: Transaction's hash + :return: Message hash + """ + tx = await self.get_transaction(tx_hash) + if not isinstance(tx, L1HandlerTransaction): + raise TypeError( + f"Transaction {tx_hash} is not a result of L1->L2 interaction." + ) + + encoded_message = encode_l1_message(tx) + return keccak256(encoded_message) + async def get_transaction_receipt(self, tx_hash: Hash) -> TransactionReceipt: res = await self._client.call( method_name="getTransactionReceipt", @@ -626,19 +644,6 @@ async def get_class_at( ) return cast(ContractClass, ContractClassSchema().load(res, unknown=EXCLUDE)) - async def get_pending_transactions(self) -> List[Transaction]: - """ - Returns the transactions in the transaction pool, recognized by sequencer - - :returns: List of transactions - """ - res = await self._client.call(method_name="pendingTransactions", params={}) - res = {"pending_transactions": res} - - return cast( - List[Transaction], PendingTransactionsSchema().load(res, unknown=EXCLUDE) - ) - async def get_contract_nonce( self, contract_address: Hash, @@ -658,6 +663,35 @@ async def get_contract_nonce( res = cast(str, res) return int(res, 16) + async def spec_version(self) -> str: + """ + Returns the version of the Starknet JSON-RPC specification being used. + + :return: String with version of the Starknet JSON-RPC specification. + """ + res = await self._client.call( + method_name="specVersion", + params={}, + ) + return res + + async def get_transaction_status(self, tx_hash: Hash) -> TransactionStatusResponse: + """ + Gets the transaction status (possibly reflecting that the transaction is still in the mempool, + or dropped from it). + + :param tx_hash: Hash of the executed transaction. + :return: Finality and execution status of a transaction. + """ + res = await self._client.call( + method_name="getTransactionStatus", + params={"transaction_hash": tx_hash}, + ) + return cast( + TransactionStatusResponse, + TransactionStatusResponseSchema().load(res, unknown=EXCLUDE), + ) + # ------------------------------- Trace API ------------------------------- async def trace_transaction( @@ -738,24 +772,14 @@ async def trace_block_transactions( :param block_number: Block's number or literals `"pending"` or `"latest"` :return: List of execution traces of all transactions included in the given block with transaction hashes. """ - - # TODO (#1169): remove this hack after RPC Trace API update from `BLOCK_HASH` to `BLOCK_ID` - - if block_hash == "pending" or block_number == "pending": - warnings.warn( - 'Only possible argument in RPC specification is "block_hash". ' - 'Using "latest" block instead of "pending". "pending" blocks do not have a hash.' - ) - block_number = None - block_hash = "latest" - - block = await self.get_block(block_hash=block_hash, block_number=block_number) - assert isinstance(block, StarknetBlock) + block_identifier = get_block_identifier( + block_hash=block_hash, block_number=block_number + ) res = await self._client.call( method_name="traceBlockTransactions", params={ - "block_hash": _to_rpc_felt(block.block_hash), + **block_identifier, }, ) return cast( diff --git a/starknet_py/net/gateway_client.py b/starknet_py/net/gateway_client.py index 9832110fd..d0b626699 100644 --- a/starknet_py/net/gateway_client.py +++ b/starknet_py/net/gateway_client.py @@ -17,6 +17,7 @@ DeployAccountTransactionResponse, EstimatedFee, GatewayBlock, + GatewayTransactionStatusResponse, Hash, SentTransactionResponse, SierraContractClass, @@ -25,7 +26,6 @@ Tag, Transaction, TransactionReceipt, - TransactionStatusResponse, ) from starknet_py.net.client_utils import hash_to_felt, is_block_identifier from starknet_py.net.http_client import GatewayHttpClient @@ -388,7 +388,7 @@ async def _add_transaction( async def get_transaction_status( self, tx_hash: Hash, - ) -> TransactionStatusResponse: + ) -> GatewayTransactionStatusResponse: """ Fetches the transaction's status and block number diff --git a/starknet_py/net/models/chains.py b/starknet_py/net/models/chains.py index e71294640..ff95ad204 100644 --- a/starknet_py/net/models/chains.py +++ b/starknet_py/net/models/chains.py @@ -2,7 +2,7 @@ from typing import Optional from starknet_py.common import int_from_bytes -from starknet_py.net.networks import MAINNET, TESTNET, TESTNET2, Network +from starknet_py.net.networks import MAINNET, TESTNET, Network class StarknetChainId(IntEnum): @@ -12,7 +12,6 @@ class StarknetChainId(IntEnum): MAINNET = int_from_bytes(b"SN_MAIN") TESTNET = int_from_bytes(b"SN_GOERLI") - TESTNET2 = int_from_bytes(b"SN_GOERLI2") def chain_from_network( @@ -21,7 +20,6 @@ def chain_from_network( mapping = { MAINNET: StarknetChainId.MAINNET, TESTNET: StarknetChainId.TESTNET, - TESTNET2: StarknetChainId.TESTNET2, } if isinstance(net, str) and net in mapping: diff --git a/starknet_py/net/networks.py b/starknet_py/net/networks.py index 1aa083de9..979749c64 100644 --- a/starknet_py/net/networks.py +++ b/starknet_py/net/networks.py @@ -4,11 +4,7 @@ MAINNET = "mainnet" TESTNET = "testnet" -# TODO (#1178): remove that -TESTNET2 = "testnet2" - -# TODO (#1178): remove that -PredefinedNetwork = Literal["mainnet", "testnet", "testnet2"] +PredefinedNetwork = Literal["mainnet", "testnet"] class CustomGatewayUrls(TypedDict): @@ -23,14 +19,11 @@ def net_address_from_net(net: str) -> str: return { MAINNET: "https://alpha-mainnet.starknet.io", TESTNET: "https://alpha4.starknet.io", - # TODO (#1178): remove that - TESTNET2: "https://alpha4-2.starknet.io", }.get(net, net) def default_token_address_for_network(net: Network) -> str: - # TODO (#1178): remove that - if net not in [TESTNET, TESTNET2, MAINNET]: + if net not in [TESTNET, MAINNET]: raise ValueError( "Argument token_address must be specified when using a custom net address" ) diff --git a/starknet_py/net/schemas/common.py b/starknet_py/net/schemas/common.py index bdec50893..3850199e0 100644 --- a/starknet_py/net/schemas/common.py +++ b/starknet_py/net/schemas/common.py @@ -77,9 +77,6 @@ def _deserialize( ) -> TransactionStatus: values = [v.value for v in TransactionStatus] - # TODO (#1143): change case insensitivity in a proper release - assert isinstance(value, str) - value = _pascal_to_screaming_upper(value) if value not in values: raise ValidationError( f"Invalid value provided for TransactionStatus: {value}." @@ -101,9 +98,6 @@ def _deserialize( ) -> TransactionExecutionStatus: values = [v.value for v in TransactionExecutionStatus] - # TODO (#1143): change case insensitivity in a proper release - assert isinstance(value, str) - value = _pascal_to_screaming_upper(value) if value not in values: raise ValidationError( f"Invalid value provided for TransactionExecutionStatus: {value}." @@ -125,9 +119,6 @@ def _deserialize( ) -> TransactionFinalityStatus: values = [v.value for v in TransactionFinalityStatus] - # TODO (#1143): change case insensitivity in a proper release - assert isinstance(value, str) - value = _pascal_to_screaming_upper(value) if value not in values: raise ValidationError( f"Invalid value provided for TransactionFinalityStatus: {value}." diff --git a/starknet_py/net/schemas/gateway.py b/starknet_py/net/schemas/gateway.py index b86a2ff67..b8f1af443 100644 --- a/starknet_py/net/schemas/gateway.py +++ b/starknet_py/net/schemas/gateway.py @@ -29,6 +29,7 @@ GatewayBlock, GatewayBlockTransactionReceipt, GatewayStateDiff, + GatewayTransactionStatusResponse, InvokeTransaction, L1HandlerTransaction, L1toL2Message, @@ -44,7 +45,6 @@ StateUpdateWithBlock, StorageDiffItem, TransactionReceipt, - TransactionStatusResponse, ) from starknet_py.net.schemas.common import ( BlockStatusField, @@ -624,8 +624,8 @@ class TransactionStatusSchema(Schema): block_hash = Felt(data_key="block_hash", load_default=None) @post_load - def make_result(self, data, **kwargs) -> TransactionStatusResponse: - return TransactionStatusResponse(**data) + def make_result(self, data, **kwargs) -> GatewayTransactionStatusResponse: + return GatewayTransactionStatusResponse(**data) class SignatureInputSchema(Schema): diff --git a/starknet_py/net/schemas/rpc.py b/starknet_py/net/schemas/rpc.py index 2b428e6cf..f40cb96b4 100644 --- a/starknet_py/net/schemas/rpc.py +++ b/starknet_py/net/schemas/rpc.py @@ -21,18 +21,21 @@ EntryPointsByType, EstimatedFee, Event, - EventContent, EventsChunk, + ExecutionResources, FunctionInvocation, InvokeTransaction, InvokeTransactionTrace, L1HandlerTransaction, L1HandlerTransactionTrace, L2toL1Message, + OrderedEvent, + OrderedMessage, PendingBlockStateUpdate, PendingStarknetBlock, PendingStarknetBlockWithTxHashes, ReplacedClass, + ResourcePrice, RevertedFunctionInvocation, SentTransactionResponse, SierraContractClass, @@ -45,6 +48,7 @@ StorageDiffItem, SyncStatus, TransactionReceipt, + TransactionStatusResponse, ) from starknet_py.net.schemas.common import ( BlockStatusField, @@ -119,6 +123,9 @@ class TransactionReceiptSchema(Schema): l2_to_l1_messages = fields.List( fields.Nested(L2toL1MessageSchema()), data_key="messages_sent", load_default=[] ) + message_hash = Felt(data_key="message_hash", load_default=None) + # TODO (#1179): this field should be required + execution_resources = fields.Dict(data_key="execution_resources", load_default=None) @post_load def make_dataclass(self, data, **kwargs) -> TransactionReceipt: @@ -135,6 +142,26 @@ def make_dataclass(self, data, **kwargs): return EstimatedFee(**data) +class TransactionStatusResponseSchema(Schema): + finality_status = StatusField(data_key="finality_status", required=True) + execution_status = ExecutionStatusField( + data_key="execution_status", load_default=None + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> TransactionStatusResponse: + return TransactionStatusResponse(**data) + + +class ResourcePriceSchema(Schema): + price_in_strk = Felt(data_key="price_in_strk", load_default=None) + price_in_wei = Felt(data_key="price_in_wei", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> ResourcePrice: + return ResourcePrice(**data) + + class TransactionSchema(Schema): hash = Felt(data_key="transaction_hash", load_default=None) signature = fields.List(Felt(), data_key="signature", load_default=[]) @@ -215,14 +242,20 @@ class TypesOfTransactionsSchema(OneOfSchema): class PendingStarknetBlockSchema(Schema): - parent_hash = Felt(data_key="parent_hash", load_default=None) - sequencer_address = Felt(data_key="sequencer_address", load_default=None) + parent_block_hash = Felt(data_key="parent_hash", required=True) + sequencer_address = Felt(data_key="sequencer_address", required=True) transactions = fields.List( fields.Nested(TypesOfTransactionsSchema(unknown=EXCLUDE)), data_key="transactions", required=True, ) - timestamp = fields.Integer(data_key="timestamp", load_default=None) + timestamp = fields.Integer(data_key="timestamp", required=True) + # TODO (#1179): this field should be required + l1_gas_price = fields.Nested( + ResourcePriceSchema(), data_key="l1_gas_price", load_default=None + ) + # TODO (#1179): this field should be required + starknet_version = fields.String(data_key="starknet_version", load_default=None) @post_load def make_dataclass(self, data, **kwargs): @@ -242,12 +275,39 @@ class StarknetBlockSchema(Schema): required=True, ) timestamp = fields.Integer(data_key="timestamp", required=True) + # TODO (#1179): this field should be required + starknet_version = fields.String(data_key="starknet_version", load_default=None) + # TODO (#1179): this field should be required + l1_gas_price = fields.Nested( + ResourcePriceSchema(), data_key="l1_gas_price", load_default=None + ) @post_load def make_dataclass(self, data, **kwargs) -> StarknetBlock: return StarknetBlock(**data) +class StarknetBlockWithTxHashesSchema(Schema): + block_hash = Felt(data_key="block_hash", required=True) + parent_block_hash = Felt(data_key="parent_hash", required=True) + block_number = fields.Integer(data_key="block_number", required=True) + sequencer_address = Felt(data_key="sequencer_address", required=True) + status = BlockStatusField(data_key="status", required=True) + root = NonPrefixedHex(data_key="new_root", required=True) + transactions = fields.List(Felt(), data_key="transactions", required=True) + timestamp = fields.Integer(data_key="timestamp", required=True) + # TODO (#1179): this field should be required + starknet_version = fields.String(data_key="starknet_version", load_default=None) + # TODO (#1179): this field should be required + l1_gas_price = fields.Nested( + ResourcePriceSchema(), data_key="l1_gas_price", load_default=None + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> StarknetBlockWithTxHashes: + return StarknetBlockWithTxHashes(**data) + + class BlockHashAndNumberSchema(Schema): block_hash = Felt(data_key="block_hash", required=True) block_number = fields.Integer(data_key="block_number", required=True) @@ -270,26 +330,17 @@ def make_dataclass(self, data, **kwargs) -> SyncStatus: return SyncStatus(**data) -class StarknetBlockWithTxHashesSchema(Schema): - block_hash = Felt(data_key="block_hash", required=True) +class PendingStarknetBlockWithTxHashesSchema(Schema): parent_block_hash = Felt(data_key="parent_hash", required=True) - block_number = fields.Integer(data_key="block_number", required=True) sequencer_address = Felt(data_key="sequencer_address", required=True) - status = BlockStatusField(data_key="status", required=True) - root = NonPrefixedHex(data_key="new_root", required=True) transactions = fields.List(Felt(), data_key="transactions", required=True) timestamp = fields.Integer(data_key="timestamp", required=True) - - @post_load - def make_dataclass(self, data, **kwargs) -> StarknetBlockWithTxHashes: - return StarknetBlockWithTxHashes(**data) - - -class PendingStarknetBlockWithTxHashesSchema(Schema): - parent_block_hash = Felt(data_key="parent_hash", load_default=None) - sequencer_address = Felt(data_key="sequencer_address", load_default=None) - transactions = fields.List(Felt(), data_key="transactions", required=True) - timestamp = fields.Integer(data_key="timestamp", load_default=None) + # TODO (#1179): this field should be required + starknet_version = fields.String(data_key="starknet_version", load_default=None) + # TODO (#1179): this field should be required + l1_gas_price = fields.Nested( + ResourcePriceSchema(), data_key="l1_gas_price", load_default=None + ) @post_load def make_dataclass(self, data, **kwargs) -> PendingStarknetBlockWithTxHashes: @@ -512,27 +563,58 @@ def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionResponse: return DeployAccountTransactionResponse(**data) -class PendingTransactionsSchema(Schema): - pending_transactions = fields.List( - fields.Nested(TypesOfTransactionsSchema(unknown=EXCLUDE)), - required=True, +class ExecutionResourcesSchema(Schema): + steps = Felt(data_key="steps", required=True) + range_check_builtin_applications = Felt( + data_key="range_check_builtin_applications", required=True + ) + pedersen_builtin_applications = Felt( + data_key="pedersen_builtin_applications", required=True ) + poseidon_builtin_applications = Felt( + data_key="poseidon_builtin_applications", required=True + ) + ec_op_builtin_applications = Felt( + data_key="ec_op_builtin_applications", required=True + ) + ecdsa_builtin_applications = Felt( + data_key="ecdsa_builtin_applications", required=True + ) + bitwise_builtin_applications = Felt( + data_key="bitwise_builtin_applications", required=True + ) + keccak_builtin_applications = Felt( + data_key="keccak_builtin_applications", required=True + ) + memory_holes = Felt(data_key="memory_holes", load_default=None) @post_load - def make_dataclass(self, data, **kwargs): - return data["pending_transactions"] + def make_dataclass(self, data, **kwargs) -> ExecutionResources: + return ExecutionResources(**data) # ------------------------------- Trace API ------------------------------- -class EventContentSchema(Schema): +class OrderedEventSchema(Schema): keys = fields.List(Felt(), data_key="keys", required=True) data = fields.List(Felt(), data_key="data", required=True) + order = fields.Integer(data_key="order", required=True) @post_load def make_dataclass(self, data, **kwargs): - return EventContent(**data) + return OrderedEvent(**data) + + +class OrderedMessageSchema(Schema): + l2_address = Felt(data_key="from_address", required=True) + l1_address = Felt(data_key="to_address", required=True) + payload = fields.List(Felt(), data_key="payload", required=True) + order = fields.Integer(data_key="order", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> OrderedMessage: + return OrderedMessage(**data) class FunctionInvocationSchema(Schema): @@ -553,7 +635,7 @@ class FunctionInvocationSchema(Schema): required=True, ) events = fields.List( - fields.Nested(EventContentSchema()), data_key="events", required=True + fields.Nested(OrderedEventSchema()), data_key="events", required=True ) messages = fields.List( fields.Nested(L2toL1MessageSchema()), data_key="messages", required=True @@ -585,17 +667,20 @@ def get_data_type(self, data): class InvokeTransactionTraceSchema(Schema): - validate_invocation = fields.Nested( - FunctionInvocationSchema(), data_key="validate_invocation", load_default=None - ) execute_invocation = fields.Nested( ExecuteInvocationSchema(), data_key="execute_invocation", required=True ) + validate_invocation = fields.Nested( + FunctionInvocationSchema(), data_key="validate_invocation", load_default=None + ) fee_transfer_invocation = fields.Nested( FunctionInvocationSchema(), data_key="fee_transfer_invocation", load_default=None, ) + state_diff = fields.Nested( + StateDiffSchema(), data_key="state_diff", load_default=None + ) @post_load def make_dataclass(self, data, **kwargs) -> InvokeTransactionTrace: @@ -611,6 +696,9 @@ class DeclareTransactionTraceSchema(Schema): data_key="fee_transfer_invocation", load_default=None, ) + state_diff = fields.Nested( + StateDiffSchema(), data_key="state_diff", load_default=None + ) @post_load def make_dataclass(self, data, **kwargs) -> DeclareTransactionTrace: @@ -618,17 +706,20 @@ def make_dataclass(self, data, **kwargs) -> DeclareTransactionTrace: class DeployAccountTransactionTraceSchema(Schema): - validate_invocation = fields.Nested( - FunctionInvocationSchema(), data_key="validate_invocation", load_default=None - ) constructor_invocation = fields.Nested( FunctionInvocationSchema(), data_key="constructor_invocation", required=True ) + validate_invocation = fields.Nested( + FunctionInvocationSchema(), data_key="validate_invocation", load_default=None + ) fee_transfer_invocation = fields.Nested( FunctionInvocationSchema(), data_key="fee_transfer_invocation", load_default=None, ) + state_diff = fields.Nested( + StateDiffSchema(), data_key="state_diff", load_default=None + ) @post_load def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionTrace: @@ -639,6 +730,9 @@ class L1HandlerTransactionTraceSchema(Schema): function_invocation = fields.Nested( FunctionInvocationSchema(), data_key="function_invocation", required=True ) + state_diff = fields.Nested( + StateDiffSchema(), data_key="state_diff", load_default=None + ) @post_load def make_dataclass(self, data, **kwargs) -> L1HandlerTransactionTrace: @@ -653,22 +747,17 @@ class TransactionTraceSchema(OneOfSchema): "L1_HANDLER": L1HandlerTransactionTraceSchema(), } - # TODO (#1177): change this from sketchy strings to `type` property def get_data_type(self, data): - # All possible types of transaction trace (loaded by sketchy logic), - # it's possible that more are added later in the future, and it needs to be reformatted - if "function_invocation" in data: - return "L1_HANDLER" - if "constructor_invocation" in data: - return "DEPLOY_ACCOUNT" - if "execute_invocation" in data: - return "INVOKE" - return "DECLARE" + return data["type"] class SimulatedTransactionSchema(Schema): + # `unknown=EXCLUDE` in order to skip `type=...` field we don't want transaction_trace = fields.Nested( - TransactionTraceSchema(), data_key="transaction_trace", required=True + TransactionTraceSchema(), + data_key="transaction_trace", + required=True, + unknown=EXCLUDE, ) fee_estimation = fields.Nested( EstimatedFeeSchema(), data_key="fee_estimation", required=True @@ -681,8 +770,9 @@ def make_dataclass(self, data, **kwargs) -> SimulatedTransaction: class BlockTransactionTraceSchema(Schema): transaction_hash = Felt(data_key="transaction_hash", required=True) + # `unknown=EXCLUDE` in order to skip `type=...` field we don't want trace_root = fields.Nested( - TransactionTraceSchema(), data_key="trace_root", required=True + TransactionTraceSchema(), data_key="trace_root", required=True, unknown=EXCLUDE ) @post_load diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 3f94e34d1..2a1db142c 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -1,4 +1,3 @@ -import sys from typing import cast from unittest.mock import AsyncMock, patch @@ -141,11 +140,6 @@ async def test_get_class_hash_at(map_contract, account): assert class_hash != 0 -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio() async def test_get_nonce(account, map_contract): nonce = await account.get_nonce() @@ -672,27 +666,3 @@ async def test_argent_cairo1_account_execute( ) assert get_balance[0] == value - - -# TODO (#1184): remove that -@pytest.mark.asyncio -async def test_cairo1_account_deprecations( - deployed_balance_contract, - argent_cairo1_account: BaseAccount, -): - call = Call( - to_addr=deployed_balance_contract.address, - selector=get_selector_from_name("increase_balance"), - calldata=[20], - ) - with pytest.warns( - DeprecationWarning, - match="Parameter 'cairo_version' has been deprecated. It is calculated automatically based on your account's " - "contract class.", - ): - _ = await argent_cairo1_account.execute( - calls=call, max_fee=int(1e16), cairo_version=1 - ) - _ = await argent_cairo1_account.sign_invoke_transaction( - calls=call, max_fee=int(1e16), cairo_version=1 - ) diff --git a/starknet_py/tests/e2e/block_test.py b/starknet_py/tests/e2e/block_test.py index 276565e4a..71b6838cc 100644 --- a/starknet_py/tests/e2e/block_test.py +++ b/starknet_py/tests/e2e/block_test.py @@ -1,5 +1,3 @@ -import sys - import pytest from starknet_py.contract import Contract @@ -24,11 +22,6 @@ async def declare_contract(account: BaseAccount, compiled_contract: str): await declare_result.wait_for_acceptance() -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_pending_block(account, map_compiled_contract): await declare_contract(account, map_compiled_contract) @@ -42,11 +35,6 @@ async def test_pending_block(account, map_compiled_contract): assert isinstance(blk, PendingStarknetBlock) -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_latest_block(account, map_compiled_contract): await declare_contract(account, map_compiled_contract) diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index 9ddf1c030..2dbbd6df0 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -1,7 +1,6 @@ # pylint: disable=too-many-arguments import asyncio import dataclasses -import sys from typing import Tuple from unittest.mock import AsyncMock, patch @@ -82,11 +81,6 @@ async def test_get_transaction_raises_on_not_received(client): await client.get_transaction(tx_hash=0x9999) -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_get_block_by_hash( client, @@ -103,11 +97,6 @@ async def test_get_block_by_hash( assert block.gas_price > 0 -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_get_block_by_number( client, @@ -524,11 +513,6 @@ async def test_get_l1_handler_transaction(client): assert transaction.nonce == 0x34C20 -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_state_update_declared_contract_hashes( @@ -544,11 +528,6 @@ async def test_state_update_declared_contract_hashes( assert class_hash in state_update.state_diff.deprecated_declared_contract_hashes -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_state_update_storage_diffs( @@ -565,11 +544,6 @@ async def test_state_update_storage_diffs( assert isinstance(state_update, PendingBlockStateUpdate) -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_state_update_deployed_contracts( @@ -631,11 +605,6 @@ async def test_get_declare_v2_transaction( ) -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_get_block_with_declare_v2( client, @@ -662,11 +631,6 @@ async def test_get_block_with_declare_v2( ) -# TODO (#1154): remove line below -@pytest.mark.xfail( - "--client=gateway" in sys.argv, - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) @pytest.mark.asyncio async def test_get_new_state_update( client, diff --git a/starknet_py/tests/e2e/client/full_node_test.py b/starknet_py/tests/e2e/client/full_node_test.py index 90134de23..833a965a6 100644 --- a/starknet_py/tests/e2e/client/full_node_test.py +++ b/starknet_py/tests/e2e/client/full_node_test.py @@ -113,31 +113,6 @@ async def test_method_raises_on_both_block_hash_and_number(full_node_client): await full_node_client.get_block(block_number=0, block_hash="0x0") -@pytest.mark.asyncio -async def test_pending_transactions(full_node_client): - with patch( - "starknet_py.net.http_client.RpcHttpClient.call", AsyncMock() - ) as mocked_http_call: - mocked_http_call.return_value = [ - { - "transaction_hash": "0x01", - "class_hash": "0x05", - "version": "0x0", - "type": "DEPLOY", - "contract_address": "0x02", - "contract_address_salt": "0x0", - "constructor_calldata": [], - } - ] - - pending_transactions = await full_node_client.get_pending_transactions() - - assert len(pending_transactions) == 1 - assert pending_transactions[0].hash == 0x1 - assert pending_transactions[0].signature == [] - assert pending_transactions[0].max_fee == 0 - - @pytest.mark.asyncio async def test_get_transaction_receipt_deploy_account( full_node_client, deploy_account_details_factory @@ -419,6 +394,8 @@ async def test_get_syncing_status(full_node_client): # ---------------------------- Trace API tests ---------------------------- +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions_skip_validate( full_node_account, deployed_balance_contract @@ -445,6 +422,8 @@ async def test_simulate_transactions_skip_validate( ) +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions_skip_fee_charge( full_node_account, deployed_balance_contract @@ -467,6 +446,8 @@ async def test_simulate_transactions_skip_fee_charge( ) +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions_invoke( full_node_account, deployed_balance_contract @@ -500,6 +481,8 @@ async def test_simulate_transactions_invoke( assert simulated_txs[0].transaction_trace.execute_invocation is not None +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions_declare(full_node_account): compiled_contract = read_contract( @@ -518,6 +501,8 @@ async def test_simulate_transactions_declare(full_node_account): assert simulated_txs[0].transaction_trace.validate_invocation is not None +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions_two_txs( full_node_account, deployed_balance_contract @@ -563,6 +548,8 @@ async def test_simulate_transactions_two_txs( assert simulated_txs[1].transaction_trace.validate_invocation is not None +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions_deploy_account( full_node_client, deploy_account_details_factory diff --git a/starknet_py/tests/e2e/client/gateway_test.py b/starknet_py/tests/e2e/client/gateway_test.py index f62545059..e3828d77b 100644 --- a/starknet_py/tests/e2e/client/gateway_test.py +++ b/starknet_py/tests/e2e/client/gateway_test.py @@ -9,13 +9,13 @@ CasmClassEntryPointsByType, ContractClass, DeployTransaction, + GatewayTransactionStatusResponse, L1HandlerTransaction, SierraContractClass, TransactionStatus, - TransactionStatusResponse, ) from starknet_py.net.gateway_client import GatewayClient -from starknet_py.net.networks import MAINNET, TESTNET, TESTNET2, CustomGatewayUrls +from starknet_py.net.networks import MAINNET, TESTNET, CustomGatewayUrls @pytest.mark.asyncio @@ -96,7 +96,7 @@ async def test_get_transaction_status(invoke_transaction_hash, gateway_client): tx_status_resp = await gateway_client.get_transaction_status( invoke_transaction_hash ) - assert isinstance(tx_status_resp, TransactionStatusResponse) + assert isinstance(tx_status_resp, GatewayTransactionStatusResponse) assert tx_status_resp.transaction_status == TransactionStatus.ACCEPTED_ON_L2 @@ -110,8 +110,6 @@ def test_gateway_client_warn_deprecation(): "net, net_address", ( (TESTNET, "https://alpha4.starknet.io"), - # TODO (#1178): remove that - (TESTNET2, "https://alpha4-2.starknet.io"), (MAINNET, "https://alpha-mainnet.starknet.io"), ), ) diff --git a/starknet_py/tests/e2e/docs/code_examples/test_full_node_client.py b/starknet_py/tests/e2e/docs/code_examples/test_full_node_client.py index da86bceb8..2d96445f3 100644 --- a/starknet_py/tests/e2e/docs/code_examples/test_full_node_client.py +++ b/starknet_py/tests/e2e/docs/code_examples/test_full_node_client.py @@ -221,6 +221,8 @@ async def test_trace_transaction(full_node_client): # docs-end: trace_transaction +# TODO (#1179): remove @pytest.mark.skip +@pytest.mark.skip(reason="Old devnet without RPC 0.5.0") @pytest.mark.asyncio async def test_simulate_transactions( full_node_account, deployed_balance_contract, deploy_account_transaction diff --git a/starknet_py/tests/e2e/docs/code_examples/test_gateway_client.py b/starknet_py/tests/e2e/docs/code_examples/test_gateway_client.py index 1430389f1..4035f8b3c 100644 --- a/starknet_py/tests/e2e/docs/code_examples/test_gateway_client.py +++ b/starknet_py/tests/e2e/docs/code_examples/test_gateway_client.py @@ -46,10 +46,7 @@ async def test_get_block_traces(gateway_client): # docs-end: get_block_traces -# TODO (#1154): remove line below -@pytest.mark.xfail( - reason="0.12.2 returns Felts in state_root, devnet returns NonPrefixedHex", -) +@pytest.mark.xfail(reason="Devnet doesn't support `include_block` parameter") @pytest.mark.asyncio async def test_get_state_update(gateway_client): # docs-start: get_state_update diff --git a/starknet_py/tests/e2e/tests_on_networks/client_test.py b/starknet_py/tests/e2e/tests_on_networks/client_test.py index b5e4dde33..f15187176 100644 --- a/starknet_py/tests/e2e/tests_on_networks/client_test.py +++ b/starknet_py/tests/e2e/tests_on_networks/client_test.py @@ -13,16 +13,11 @@ TransactionExecutionStatus, TransactionFinalityStatus, TransactionReceipt, + TransactionStatus, ) from starknet_py.net.gateway_client import GatewayClient -from starknet_py.tests.e2e.fixtures.constants import ( - PREDEPLOYED_EMPTY_CONTRACT_ADDRESS, - PREDEPLOYED_MAP_CONTRACT_ADDRESS, -) -from starknet_py.transaction_errors import ( - TransactionRejectedError, - TransactionRevertedError, -) +from starknet_py.tests.e2e.fixtures.constants import PREDEPLOYED_EMPTY_CONTRACT_ADDRESS +from starknet_py.transaction_errors import TransactionRevertedError @pytest.mark.parametrize( @@ -52,7 +47,7 @@ async def test_get_transaction_receipt(client_integration, transaction_hash): # Same thing could happen when you run tests locally and then push to run them on CI. @pytest.mark.skipif( condition="--client=gateway" in sys.argv, - reason="Separate FullNode tests from Gateway ones.", + reason="Gateway client has been disabled on integration network.", ) @pytest.mark.asyncio async def test_wait_for_tx_reverted_full_node(full_node_account_integration): @@ -66,75 +61,14 @@ async def test_wait_for_tx_reverted_full_node(full_node_account_integration): sign_invoke = await account.sign_invoke_transaction(calls=call, max_fee=int(1e16)) invoke = await account.client.send_transaction(sign_invoke) - with pytest.raises(TransactionRevertedError, match=r".*reverted.*"): - await account.client.wait_for_tx(tx_hash=invoke.transaction_hash) - - -@pytest.mark.skipif( - condition="--client=full_node" in sys.argv, - reason="Separate FullNode tests from Gateway ones.", -) -@pytest.mark.asyncio -async def test_wait_for_tx_reverted_gateway(gateway_account_integration): - account = gateway_account_integration - # Calldata too long for the function (it has no parameters) to trigger REVERTED status - call = Call( - to_addr=int(PREDEPLOYED_EMPTY_CONTRACT_ADDRESS, 0), - selector=get_selector_from_name("empty"), - calldata=[0x1, 0x2, 0x3, 0x4, 0x5], - ) - sign_invoke = await account.sign_invoke_transaction(calls=call, max_fee=int(1e16)) - invoke = await account.client.send_transaction(sign_invoke) - with pytest.raises(TransactionRevertedError, match="Input too long for arguments"): await account.client.wait_for_tx(tx_hash=invoke.transaction_hash) -# No same test for full_node, because nodes don't know about rejected transactions -# https://community.starknet.io/t/efficient-utilization-of-sequencer-capacity-in-starknet-v0-12-1/95607#api-changes-3 -@pytest.mark.skipif( - condition="--client=full_node" in sys.argv, - reason="Separate FullNode tests from Gateway ones.", -) -@pytest.mark.asyncio -async def test_wait_for_tx_rejected_gateway(gateway_account_integration): - account = gateway_account_integration - call = Call( - to_addr=int(PREDEPLOYED_MAP_CONTRACT_ADDRESS, 0), - selector=get_selector_from_name("put"), - calldata=[0x102, 0x125], - ) - call2 = Call( - to_addr=int( - "0x05cd21d6b3952a869fda11fa9a5bd2657bd68080d3da255655ded47a81c8bd53", 0 - ), - selector=get_selector_from_name("put"), - calldata=[0x103, 0x126], - ) - sign_invoke = await account.sign_invoke_transaction(calls=call, max_fee=int(1e16)) - sign_invoke2 = await account.sign_invoke_transaction(calls=call2, max_fee=int(1e16)) - # same nonces to trigger REJECTED error - assert sign_invoke2.nonce == sign_invoke.nonce - - # this one should pass - invoke = await account.client.send_transaction(sign_invoke) - # this should be rejected - invoke2 = await account.client.send_transaction(sign_invoke2) - - with pytest.raises(TransactionRejectedError): - _ = await account.client.wait_for_tx(tx_hash=invoke2.transaction_hash) - - invoke2_receipt = await account.client.get_transaction_receipt( - tx_hash=invoke2.transaction_hash - ) - - assert invoke2_receipt.execution_status == TransactionExecutionStatus.REJECTED - - # Same here as in comment above 'test_wait_for_tx_reverted_full_node' @pytest.mark.skipif( condition="--client=gateway" in sys.argv, - reason="Separate FullNode tests from Gateway ones.", + reason="Gateway client has been disabled on integration network.", ) @pytest.mark.asyncio async def test_wait_for_tx_full_node_accepted(full_node_account_integration): @@ -154,26 +88,9 @@ async def test_wait_for_tx_full_node_accepted(full_node_account_integration): @pytest.mark.skipif( - condition="--client=full_node" in sys.argv, - reason="Separate FullNode tests from Gateway ones.", + condition="--client=gateway" in sys.argv, + reason="Gateway client has been disabled on integration network.", ) -@pytest.mark.asyncio -async def test_wait_for_tx_gateway_accepted(gateway_account_integration): - account = gateway_account_integration - call = Call( - to_addr=int(PREDEPLOYED_EMPTY_CONTRACT_ADDRESS, 0), - selector=get_selector_from_name("empty"), - calldata=[], - ) - sign_invoke = await account.sign_invoke_transaction(calls=call, max_fee=int(1e16)) - invoke = await account.client.send_transaction(sign_invoke) - - result = await account.client.wait_for_tx(tx_hash=invoke.transaction_hash) - - assert result.execution_status == TransactionExecutionStatus.SUCCEEDED - assert result.finality_status == TransactionFinalityStatus.ACCEPTED_ON_L2 - - @pytest.mark.asyncio async def test_transaction_not_received_max_fee_too_small(account_integration): account = account_integration @@ -191,6 +108,10 @@ async def test_transaction_not_received_max_fee_too_small(account_integration): _ = await account.client.send_transaction(sign_invoke) +@pytest.mark.skipif( + condition="--client=gateway" in sys.argv, + reason="Gateway client has been disabled on integration network.", +) @pytest.mark.asyncio async def test_transaction_not_received_max_fee_too_big(account_integration): account = account_integration @@ -208,6 +129,10 @@ async def test_transaction_not_received_max_fee_too_big(account_integration): _ = await account.client.send_transaction(sign_invoke) +@pytest.mark.skipif( + condition="--client=gateway" in sys.argv, + reason="Gateway client has been disabled on integration network.", +) @pytest.mark.asyncio async def test_transaction_not_received_invalid_nonce(account_integration): account = account_integration @@ -224,6 +149,10 @@ async def test_transaction_not_received_invalid_nonce(account_integration): _ = await account.client.send_transaction(sign_invoke) +@pytest.mark.skipif( + condition="--client=gateway" in sys.argv, + reason="Gateway client has been disabled on integration network.", +) @pytest.mark.asyncio async def test_transaction_not_received_invalid_signature(account_integration): account = account_integration @@ -392,26 +321,42 @@ async def test_get_transaction_by_block_id_and_index( assert receipt.execution_status is not None -@pytest.mark.skipif( - condition="--client=gateway" in sys.argv, - reason="Separate FullNode tests from Gateway ones.", -) @pytest.mark.asyncio -async def test_get_pending_transactions(full_node_client_integration): +async def test_get_block(full_node_client_integration): client = full_node_client_integration - res = await client.get_pending_transactions() + res = await client.get_block(block_number="latest") - for tx in res: + for tx in res.transactions: assert tx.hash is not None +@pytest.mark.skipif( + condition="--client=gateway" in sys.argv, + reason="Method get_l1_message_hash not implemented for Gateway client.", +) @pytest.mark.asyncio -async def test_get_block(full_node_client_integration): - client = full_node_client_integration - res = await client.get_block(block_number="latest") +async def test_get_l1_message_hash(full_node_client_integration): + tx_hash = "0x0060bd50c38082211e6aedb21838fe7402a67216d559d9a4848e6c5e9670c90e" + l1_message_hash = await full_node_client_integration.get_l1_message_hash(tx_hash) + assert ( + hex(l1_message_hash) + == "0x140185c79e5a04c7c3fae513001f358beb66653dcee75be38f05bd30adba85dd" + ) - for tx in res.transactions: - assert tx.hash is not None + +@pytest.mark.skipif( + condition="--client=gateway" in sys.argv, + reason="Method get_l1_message_hash not implemented for Gateway client.", +) +@pytest.mark.asyncio +async def test_get_l1_message_hash_raises_on_incorrect_transaction_type( + full_node_client_integration, +): + tx_hash = "0x06d11fa74255c1f86aace54cbf382ab8c89e2b90fb0801f751834ca52bf2a2a2" + with pytest.raises( + TypeError, match=f"Transaction {tx_hash} is not a result of L1->L2 interaction." + ): + await full_node_client_integration.get_l1_message_hash(tx_hash) @pytest.mark.asyncio @@ -452,21 +397,68 @@ async def test_get_state_update_with_block(gateway_client_integration): assert res.state_update is not None -# TODO (#1166): remove tests below after mainnet release @pytest.mark.asyncio -async def test_get_block_different_starknet_versions(): - mainnet = GatewayClient(net="mainnet") - testnet = GatewayClient(net="testnet") +async def test_spec_version(full_node_client_testnet): + spec_version = await full_node_client_testnet.spec_version() + + assert spec_version is not None + assert isinstance(spec_version, str) + + +@pytest.mark.asyncio +async def test_get_transaction_status(full_node_client_testnet): + tx_status = await full_node_client_testnet.get_transaction_status( + tx_hash=0x1FCE504A8F9C837CA84B784836E5AF041221C1BFB40C03AE0BDC0C713D09A21 + ) + + assert tx_status.finality_status == TransactionStatus.ACCEPTED_ON_L1 + assert tx_status.execution_status == TransactionExecutionStatus.SUCCEEDED + + +@pytest.mark.asyncio +async def test_get_block_new_header_fields(full_node_client_testnet): + # testing l1_gas_price and starknet_version fields + block = await full_node_client_testnet.get_block_with_txs(block_number=800000) - _ = await mainnet.get_block(block_number=100000) - _ = await testnet.get_block(block_number=100000) + assert block.starknet_version is not None + assert block.l1_gas_price is not None + assert block.l1_gas_price.price_in_wei > 0 + + pending_block = await full_node_client_testnet.get_block_with_txs( + block_number="pending" + ) + + assert pending_block.starknet_version is not None + assert pending_block.l1_gas_price is not None + assert pending_block.l1_gas_price.price_in_wei > 0 @pytest.mark.asyncio -async def test_get_state_update_different_starknet_versions(): - mainnet = GatewayClient(net="mainnet") - testnet = GatewayClient(net="testnet") +async def test_get_block_with_tx_hashes_new_header_fields(full_node_client_testnet): + # testing l1_gas_price and starknet_version fields + block = await full_node_client_testnet.get_block_with_tx_hashes(block_number=800000) + + assert block.starknet_version is not None + assert block.l1_gas_price is not None + assert block.l1_gas_price.price_in_wei > 0 - _ = await mainnet.get_state_update(block_number=100000) + pending_block = await full_node_client_testnet.get_block_with_tx_hashes( + block_number="pending" + ) + + assert pending_block.starknet_version is not None + assert pending_block.l1_gas_price is not None + assert pending_block.l1_gas_price.price_in_wei > 0 + + +@pytest.mark.asyncio +async def test_get_tx_receipt_new_fields(full_node_client_testnet): + l1_handler_tx_hash = ( + 0xBEFE411182979262478CA8CA73BED724237D03D303CE420D94DE7664A78347 + ) + receipt = await full_node_client_testnet.get_transaction_receipt( + tx_hash=l1_handler_tx_hash + ) - _ = await testnet.get_state_update(block_number=100000, include_block=True) + assert receipt.execution_resources is not None + assert len(receipt.execution_resources.keys()) in [8, 9] diff --git a/starknet_py/tests/e2e/tests_on_networks/trace_api_test.py b/starknet_py/tests/e2e/tests_on_networks/trace_api_test.py index 16936a1a7..8be042641 100644 --- a/starknet_py/tests/e2e/tests_on_networks/trace_api_test.py +++ b/starknet_py/tests/e2e/tests_on_networks/trace_api_test.py @@ -1,5 +1,6 @@ import pytest +from starknet_py.net.account.account import Account from starknet_py.net.client_models import ( DeclareTransaction, DeclareTransactionTrace, @@ -13,6 +14,15 @@ Transaction, TransactionTrace, ) +from starknet_py.net.full_node_client import FullNodeClient +from starknet_py.net.models import StarknetChainId +from starknet_py.net.signer.stark_curve_signer import KeyPair +from starknet_py.tests.e2e.fixtures.constants import ( + CONTRACTS_COMPILED_V0_DIR, + TESTNET_ACCOUNT_ADDRESS, + TESTNET_ACCOUNT_PRIVATE_KEY, +) +from starknet_py.tests.e2e.fixtures.misc import read_contract # TODO (#1179): move those tests to full_node_test.py @@ -33,6 +43,47 @@ async def test_trace_transaction(full_node_client_testnet): assert tx_to_trace[type(tx)] == type(trace) +@pytest.mark.asyncio +async def test_trace_transaction_invoke(full_node_client_testnet): + invoke_tx_hash = 0xDC6B381884866DD6C4ACCDE75AA1FA7506E6B57612D3D3659F7B919EA07D7C + trace = await full_node_client_testnet.trace_transaction(tx_hash=invoke_tx_hash) + + assert type(trace) is InvokeTransactionTrace + assert trace.execute_invocation is not None + + +@pytest.mark.asyncio +async def test_trace_transaction_declare(full_node_client_testnet): + declare_tx_hash = 0x62DD22627065568C6E4BD619C511217456B5A82ACBDEAD7C3B5DFFF92209451 + trace = await full_node_client_testnet.trace_transaction(tx_hash=declare_tx_hash) + + assert type(trace) is DeclareTransactionTrace + + +@pytest.mark.asyncio +async def test_trace_transaction_deploy_account(full_node_client_testnet): + deploy_account_tx_hash = ( + 0x7AD24E5D266CE371EC88C1AE537A92109E8AF637C35673B6D459082431AF7B + ) + trace = await full_node_client_testnet.trace_transaction( + tx_hash=deploy_account_tx_hash + ) + + assert type(trace) is DeployAccountTransactionTrace + assert trace.constructor_invocation is not None + + +@pytest.mark.asyncio +async def test_trace_transaction_l1_handler(full_node_client_testnet): + l1_handler_tx_hash = ( + 0x6712D5CF540C1C2E51C03D6238F71CF86607F681669AF586CD2EB8A92AF68AC + ) + trace = await full_node_client_testnet.trace_transaction(tx_hash=l1_handler_tx_hash) + + assert type(trace) is L1HandlerTransactionTrace + assert trace.function_invocation is not None + + @pytest.mark.asyncio async def test_trace_transaction_reverted(full_node_client_testnet): tx_hash = "0x604371f9414d26ad9e745301596de1d1219c1045f00c68d3be9bd195eb18632" @@ -51,22 +102,36 @@ async def test_get_block_traces(full_node_client_testnet): block = await full_node_client_testnet.get_block(block_number=block_number) assert len(block_transaction_traces) == len(block.transactions) - for i in range(0, len(block_transaction_traces)): + for i in range(len(block_transaction_traces)): assert ( block_transaction_traces[i].transaction_hash == block.transactions[i].hash ) -# TODO (#1169): remove this test? @pytest.mark.asyncio -async def test_get_block_traces_warning_on_pending(full_node_client_testnet): - with pytest.warns( - UserWarning, - match='Using "latest" block instead of "pending". "pending" blocks do not have a hash.', - ): - _ = await full_node_client_testnet.trace_block_transactions( - block_number="pending" - ) - _ = await full_node_client_testnet.trace_block_transactions( - block_hash="pending" - ) +async def test_simulate_transactions_declare_on_network( + full_node_account, full_node_client_testnet +): + testnet_account_address = TESTNET_ACCOUNT_ADDRESS() + testnet_account_private_key = TESTNET_ACCOUNT_PRIVATE_KEY() + + full_node_account = Account( + address=testnet_account_address, + client=full_node_client_testnet, + key_pair=KeyPair.from_private_key(testnet_account_private_key), + chain=StarknetChainId.TESTNET, + ) + compiled_contract = read_contract( + "map_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR + ) + declare_tx = await full_node_account.sign_declare_transaction( + compiled_contract, max_fee=int(1e16) + ) + + assert isinstance(full_node_account.client, FullNodeClient) + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[declare_tx], block_number="latest" + ) + + assert isinstance(simulated_txs[0].transaction_trace, DeclareTransactionTrace) + assert simulated_txs[0].fee_estimation.overall_fee > 0 diff --git a/starknet_py/tests/e2e/utils_functions_test.py b/starknet_py/tests/e2e/utils_functions_test.py index 107ace45c..3f8bacd4f 100644 --- a/starknet_py/tests/e2e/utils_functions_test.py +++ b/starknet_py/tests/e2e/utils_functions_test.py @@ -1,42 +1,25 @@ -from starknet_py.net.full_node_client import _is_valid_eth_address -from starknet_py.net.schemas.common import _pascal_to_screaming_upper - - -def test_pascal_to_screaming_upper_valid(): - valid_names = [ - "NOT_RECEIVED", - "RECEIVED", - "ACCEPTED_ON_L2", - "ACCEPTED_ON_L1", - "REJECTED", - "REVERTED", - "SUCCEEDED", - ] - for valid in valid_names: - assert _pascal_to_screaming_upper(valid) == valid +import pytest - -def test_pascal_to_screaming_upper_invalid(): - invalid_names = [ - ("NotReceived", "NOT_RECEIVED"), - ("Received", "RECEIVED"), - ( - "AcceptedOnL2", - "ACCEPTED_ON_L2", - ), - ( - "AcceptedOnL1", - "ACCEPTED_ON_L1", - ), - ("Rejected", "REJECTED"), - ("Reverted", "REVERTED"), - ("Succeeded", "SUCCEEDED"), - ] - for invalid, valid in invalid_names: - assert _pascal_to_screaming_upper(invalid) == valid +from starknet_py.constants import FEE_CONTRACT_ADDRESS +from starknet_py.net.full_node_client import _is_valid_eth_address +from starknet_py.net.networks import default_token_address_for_network def test_is_valid_eth_address(): assert _is_valid_eth_address("0x333333f332a06ECB5D20D35da44ba07986D6E203") assert not _is_valid_eth_address("0x1") assert not _is_valid_eth_address("123") + + +def test_default_token_address_for_network(): + res = default_token_address_for_network("mainnet") + assert res == FEE_CONTRACT_ADDRESS + + res = default_token_address_for_network("testnet") + assert res == FEE_CONTRACT_ADDRESS + + with pytest.raises( + ValueError, + match="Argument token_address must be specified when using a custom net address", + ): + _ = default_token_address_for_network("")