diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 8cec01d76..ce3c5aba9 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -227,15 +227,22 @@ jobs: uses: codecov/codecov-action@v3 # ---------------------------------------------------------- # - # ..................RUN-TESTS-INTEGRATION................... # + # ..................RUN-TESTS-ON-NETWORKS................... # # ---------------------------------------------------------- # - run-tests-integration: - name: Tests on integration + run-tests-on-networks: + name: Tests on networks (testnet and integration) needs: setup-tests runs-on: ubuntu-latest strategy: fail-fast: false + env: + INTEGRATION_RPC_URL: ${{ secrets.INTEGRATION_RPC_URL }} + TESTNET_RPC_URL: ${{ secrets.TESTNET_RPC_URL }} + INTEGRATION_ACCOUNT_PRIVATE_KEY: ${{ secrets.INTEGRATION_ACCOUNT_PRIVATE_KEY }} + INTEGRATION_ACCOUNT_ADDRESS: ${{ secrets.INTEGRATION_ACCOUNT_ADDRESS }} + TESTNET_ACCOUNT_PRIVATE_KEY: ${{ secrets.TESTNET_ACCOUNT_PRIVATE_KEY }} + TESTNET_ACCOUNT_ADDRESS: ${{ secrets.TESTNET_ACCOUNT_ADDRESS }} steps: - uses: actions/checkout@v3 @@ -281,8 +288,8 @@ jobs: - name: Run tests run: | - poetry run poe test_ci_integration_full_node - poetry run poe test_ci_integration_gateway + poetry run poe test_ci_on_networks_full_node + poetry run poe test_ci_on_networks_gateway - name: Generate coverage in XML run: | diff --git a/.gitignore b/.gitignore index 5364a1167..a80135345 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,4 @@ cython_debug/ # Cairo1 compiler manifest /starknet_py/tests/e2e/manifest-path +/starknet_py/tests/e2e/test-variables.env diff --git a/docs/development.rst b/docs/development.rst index 6e364a63e..7ffb10b51 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -15,6 +15,22 @@ Make sure running ``poetry run python --version`` returns ``Python 3.9.x``. Setup ----- +In order to run Cairo1 devnet tests and compile contracts in Cairo1 via poetry command, +you need to create ``manifest-path`` file in ``starknet_py/tests/e2e/`` directory and pass the path in it to Cairo compiler. +An example file - ``manifest-path.template`` is in the same directory. Additional info can be found in `devnet docs `_. + +In order to be able to run tests on testnet and integration networks (``starknet_py/tests/e2e/tests_on_networks/``), you must set some environmental variables: + + - ``INTEGRATION_RPC_URL`` + - ``TESTNET_RPC_URL`` + - ``INTEGRATION_ACCOUNT_PRIVATE_KEY`` + - ``INTEGRATION_ACCOUNT_ADDRESS`` + - ``TESTNET_ACCOUNT_PRIVATE_KEY`` + - ``TESTNET_ACCOUNT_ADDRESS`` + +The best way to do that is to create ``test-variables.env`` file in ``starknet_py/tests/e2e/`` directory, so they can be loaded by the ``python-dotenv`` library. +You can find an example file ``test-variables.env.template`` in the same directory with the format of how it should look like. + .. code-block:: bash # Install dependencies @@ -22,6 +38,8 @@ Setup # Compile contracts poe compile_contracts + poe compile_contracts_v1 + poe compile_contracts_v2 # Make sure everything was installed properly poe test diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 491f024cc..0878acb8f 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -1,6 +1,91 @@ Migration guide =============== +********************** +0.18.2 Migration guide +********************** + +Version 0.18.2 of **starknet.py** comes with support of `RPC v0.4.0 `_ Trace API! + +0.18.2 Targeted versions +------------------------ + +- Starknet - `0.12.2 `_ +- RPC - `0.4.0 `_ + +0.18.2 Breaking changes +----------------------- + +.. currentmodule:: starknet_py.net.client + +1. :meth:`Client.get_block_traces` has been renamed to :meth:`Client.trace_block_transactions` in order to match RPC specification. + + +0.18.2 Minor changes +-------------------- + +1. :meth:`Client.trace_block_transactions` return type has been changed from ``BlockTransactionTraces`` to ``Union[BlockTransactionTraces, List[BlockTransactionTrace]]``. + +.. currentmodule:: starknet_py.net.gateway_client + +2. ``include_block`` parameter in :meth:`GatewayClient.get_state_update` now works on gateway mainnet. + + +0.18.2 Development-related changes +---------------------------------- + +1. In order to be able to run tests, you must set some environmental variables: + + - ``INTEGRATION_RPC_URL`` + - ``TESTNET_RPC_URL`` + - ``INTEGRATION_ACCOUNT_PRIVATE_KEY`` + - ``INTEGRATION_ACCOUNT_ADDRESS`` + - ``TESTNET_ACCOUNT_PRIVATE_KEY`` + - ``TESTNET_ACCOUNT_ADDRESS`` + +The best way to do that is to create ``test-variables.env`` file in ``starknet_py/tests/e2e/`` directory, so they can be loaded by the ``python-dotenv`` library. +You can find an example file ``test-variables.env.template`` in the same directory with the format of how it should look like. + + +| + +.. raw:: html + +
+ +| + +********************** +0.18.1 Migration guide +********************** + +.. currentmodule:: starknet_py.net.gateway_client + +This version contains a quick fix to :meth:`GatewayClient.get_state_update` method (mainnet wasn't updated to 0.12.2 then). + +.. currentmodule:: starknet_py.net.account.account + +Additionally, accounts in Cairo1 are now supported! You can pass additional argument ``cairo_version`` to :meth:`Account.sign_invoke_transaction` method. + + +0.18.1 Minor changes +-------------------- + +1. Parameter ``include_block`` in :meth:`GatewayClient.get_state_update` doesn't work on mainnet gateway (an error is thrown). + +.. currentmodule:: starknet_py.net.account.account + +2. :meth:`Account.sign_invoke_transaction` now accepts additional parameter ``cairo_version``, which allows specifying which type of calldata encoding should be used. + +| + +.. raw:: html + +
+ +| + + ********************** 0.18.0 Migration guide ********************** diff --git a/poetry.lock b/poetry.lock index 585d2da82..9bdd27e88 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.5" description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -112,6 +113,7 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -126,6 +128,7 @@ frozenlist = ">=1.1.0" name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -137,6 +140,7 @@ files = [ name = "apeye" version = "1.3.0" description = "Handy tools for working with URLs and APIs." +category = "main" optional = true python-versions = ">=3.6.1" files = [ @@ -158,6 +162,7 @@ limiter = ["cachecontrol[filecache] (>=0.12.6)", "lockfile (>=0.12.2)"] name = "apeye-core" version = "1.1.1" description = "Core (offline) functionality for the apeye library." +category = "main" optional = true python-versions = ">=3.6.1" files = [ @@ -173,6 +178,7 @@ idna = ">=2.5" name = "asgiref" version = "3.7.2" description = "ASGI specs, helper code, and adapters" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -190,6 +196,7 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "astroid" version = "2.15.6" description = "An abstract syntax tree for Python with inference support." +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -209,6 +216,7 @@ wrapt = [ name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -220,6 +228,7 @@ files = [ name = "attrs" version = "22.2.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -238,6 +247,7 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy name = "autodocsumm" version = "0.2.11" description = "Extended sphinx autodoc including automatic autosummaries" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -252,6 +262,7 @@ Sphinx = ">=2.2,<8.0" name = "babel" version = "2.11.0" description = "Internationalization utilities" +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -266,6 +277,7 @@ pytz = ">=2015.7" name = "beautifulsoup4" version = "4.11.2" description = "Screen-scraping library" +category = "main" optional = true python-versions = ">=3.6.0" files = [ @@ -284,6 +296,7 @@ lxml = ["lxml"] name = "bitarray" version = "2.8.0" description = "efficient arrays of booleans -- C extension" +category = "dev" optional = false python-versions = "*" files = [ @@ -395,6 +408,7 @@ files = [ name = "black" version = "23.7.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -441,6 +455,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cachecontrol" version = "0.12.11" description = "httplib2 caching for requests" +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -461,6 +476,7 @@ redis = ["redis (>=2.10.5)"] name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -470,12 +486,13 @@ files = [ [[package]] name = "cairo-lang" -version = "0.12.0" +version = "0.12.2" description = "Compiler and runner for the Cairo language" +category = "dev" optional = false python-versions = ">=3.6" files = [ - {file = "cairo-lang-0.12.0.zip", hash = "sha256:9a02a079116e8595387beff46e82779fe43152269752063f6969742ee27ef105"}, + {file = "cairo-lang-0.12.2.zip", hash = "sha256:5dc5f2ffc1f56385f56a5db526f5008830bcc8638a084812fcbff7735f7e6e43"}, ] [package.dependencies] @@ -483,8 +500,10 @@ aiohttp = "*" cachetools = "*" ecdsa = "*" eth-hash = {version = "*", extras = ["pycryptodome"]} +execnet = "*" fastecdsa = "*" frozendict = "*" +gprof2dot = "*" lark = "*" marshmallow = ">=3.2.1" marshmallow-dataclass = ">=7.1.0" @@ -496,6 +515,8 @@ pipdeptree = "*" prometheus-client = "*" pytest = "*" pytest-asyncio = "*" +pytest-profiling = "*" +pytest-xdist = "*" PyYAML = "*" sympy = "*" typeguard = "<3.0.0" @@ -505,6 +526,7 @@ web3 = "*" name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -516,6 +538,7 @@ files = [ name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -530,6 +553,7 @@ unicode-backport = ["unicodedata2"] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -544,6 +568,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "cloudpickle" version = "2.1.0" description = "Extended pickling support for Python objects" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -555,6 +580,7 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -566,6 +592,7 @@ files = [ name = "coverage" version = "7.3.0" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -633,6 +660,7 @@ toml = ["tomli"] name = "crypto-cpp-py" version = "1.4.0" description = "This is a packaged crypto-cpp program" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -699,6 +727,7 @@ build = ["cmake (>=3.22.4)"] name = "cssutils" version = "2.6.0" description = "A CSS Cascading Style Sheets library for Python" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -714,6 +743,7 @@ testing = ["cssselect", "flake8 (<5)", "importlib-resources", "jaraco.test (>=5. name = "cytoolz" version = "0.12.2" description = "Cython implementation of Toolz: High performance functional utilities" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -822,6 +852,7 @@ cython = ["cython"] name = "dict2css" version = "0.3.0" description = "A μ-library for constructing cascading style sheets from Python dictionaries." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -837,6 +868,7 @@ domdf-python-tools = ">=2.2.0" name = "dill" version = "0.3.6" description = "serialize all of python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -851,6 +883,7 @@ graph = ["objgraph (>=1.7.2)"] name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" +category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -862,6 +895,7 @@ files = [ name = "domdf-python-tools" version = "3.5.0" description = "Helpful functions for Python 🐍 🛠️" +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -882,6 +916,7 @@ dates = ["pytz (>=2019.1)"] name = "ecdsa" version = "0.18.0" description = "ECDSA cryptographic signature library (pure python)" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -900,6 +935,7 @@ gmpy2 = ["gmpy2"] name = "enum-tools" version = "0.10.0" description = "Tools to expand Python's enum module." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -922,6 +958,7 @@ sphinx = ["sphinx (>=3.2.0)", "sphinx-jinja2-compat (>=0.1.1)", "sphinx-toolbox name = "eth-abi" version = "4.1.0" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" +category = "dev" optional = false python-versions = ">=3.7.2, <4" files = [ @@ -945,6 +982,7 @@ tools = ["hypothesis (>=4.18.2,<5.0.0)"] name = "eth-account" version = "0.9.0" description = "eth-account: Sign Ethereum transactions and messages with local private keys" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -972,6 +1010,7 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdis name = "eth-hash" version = "0.5.2" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -994,6 +1033,7 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-keyfile" version = "0.6.1" description = "A library for handling the encrypted keyfiles used to store ethereum private keys." +category = "dev" optional = false python-versions = "*" files = [ @@ -1016,6 +1056,7 @@ test = ["pytest (>=6.2.5,<7)"] name = "eth-keys" version = "0.4.0" description = "Common API for Ethereum key operations." +category = "dev" optional = false python-versions = "*" files = [ @@ -1038,6 +1079,7 @@ test = ["asn1tools (>=0.146.2,<0.147)", "eth-hash[pycryptodome]", "eth-hash[pysh name = "eth-rlp" version = "0.3.0" description = "eth-rlp: RLP definitions for common Ethereum objects in Python" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -1060,6 +1102,7 @@ test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (= name = "eth-typing" version = "3.4.0" description = "eth-typing: Common type annotations for ethereum python packages" +category = "dev" optional = false python-versions = ">=3.7.2, <4" files = [ @@ -1077,6 +1120,7 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] name = "eth-utils" version = "2.2.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -1100,6 +1144,7 @@ test = ["hypothesis (>=4.43.0)", "mypy (==0.971)", "pytest (>=7.0.0)", "pytest-x name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1114,6 +1159,7 @@ test = ["pytest (>=6)"] name = "execnet" version = "1.9.0" description = "execnet: rapid multi-Python deployment" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1128,6 +1174,7 @@ testing = ["pre-commit"] name = "fastecdsa" version = "2.3.0" description = "Fast elliptic curve digital signatures" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1147,6 +1194,7 @@ files = [ name = "flask" version = "2.0.3" description = "A simple framework for building complex web applications." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1169,6 +1217,7 @@ dotenv = ["python-dotenv"] name = "flask-cors" version = "3.0.10" description = "A Flask extension adding a decorator for CORS support" +category = "dev" optional = false python-versions = "*" files = [ @@ -1184,6 +1233,7 @@ Six = "*" name = "frozendict" version = "2.3.8" description = "A simple immutable dictionary" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1230,6 +1280,7 @@ files = [ name = "frozenlist" version = "1.3.3" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1313,6 +1364,7 @@ files = [ name = "furo" version = "2023.8.19" description = "A clean customisable Sphinx documentation theme." +category = "main" optional = true python-versions = ">=3.8" files = [ @@ -1326,10 +1378,23 @@ pygments = ">=2.7" sphinx = ">=6.0,<8.0" sphinx-basic-ng = "*" +[[package]] +name = "gprof2dot" +version = "2022.7.29" +description = "Generate a dot graph from the output of several profilers." +category = "dev" +optional = false +python-versions = ">=2.7" +files = [ + {file = "gprof2dot-2022.7.29-py2.py3-none-any.whl", hash = "sha256:f165b3851d3c52ee4915eb1bd6cca571e5759823c2cd0f71a79bda93c2dc85d6"}, + {file = "gprof2dot-2022.7.29.tar.gz", hash = "sha256:45b4d298bd36608fccf9511c3fd88a773f7a1abc04d6cd39445b11ba43133ec5"}, +] + [[package]] name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1350,6 +1415,7 @@ tornado = ["tornado (>=0.2)"] name = "hexbytes" version = "0.3.1" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -1367,6 +1433,7 @@ test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>= name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" +category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1388,6 +1455,7 @@ lxml = ["lxml"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1399,6 +1467,7 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1410,6 +1479,7 @@ files = [ name = "importlib-metadata" version = "5.1.0" description = "Read metadata from Python packages" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1429,6 +1499,7 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = "*" files = [ @@ -1440,6 +1511,7 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1457,6 +1529,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "itsdangerous" version = "2.1.2" description = "Safely pass data to untrusted environments and back." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1468,6 +1541,7 @@ files = [ name = "Jinja2" version = "3.0.3" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1485,6 +1559,7 @@ i18n = ["Babel (>=2.7)"] name = "jsonschema" version = "4.17.3" description = "An implementation of JSON Schema validation for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1504,6 +1579,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "lark" version = "1.1.7" description = "a modern parsing library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1521,6 +1597,7 @@ regex = ["regex"] name = "lazy-object-proxy" version = "1.8.0" description = "A fast and thorough lazy object proxy." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1549,6 +1626,7 @@ files = [ name = "lockfile" version = "0.12.2" description = "Platform-independent file locking module" +category = "main" optional = true python-versions = "*" files = [ @@ -1560,6 +1638,7 @@ files = [ name = "lru-dict" version = "1.2.0" description = "An Dict like LRU container." +category = "dev" optional = false python-versions = "*" files = [ @@ -1654,6 +1733,7 @@ test = ["pytest"] name = "MarkupSafe" version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1703,6 +1783,7 @@ files = [ name = "marshmallow" version = "3.17.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1723,6 +1804,7 @@ tests = ["pytest", "pytz", "simplejson"] name = "marshmallow-dataclass" version = "8.4.2" description = "Python library to convert dataclasses into marshmallow schemas." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1746,6 +1828,7 @@ union = ["typeguard"] name = "marshmallow-enum" version = "1.5.1" description = "Enum field for Marshmallow" +category = "dev" optional = false python-versions = "*" files = [ @@ -1760,6 +1843,7 @@ marshmallow = ">=2.0.0" name = "marshmallow-oneofschema" version = "3.0.1" description = "marshmallow multiplexing schema" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1779,6 +1863,7 @@ tests = ["mock", "pytest"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1790,6 +1875,7 @@ files = [ name = "mpmath" version = "1.2.1" description = "Python library for arbitrary-precision floating-point arithmetic" +category = "main" optional = false python-versions = "*" files = [ @@ -1805,6 +1891,7 @@ tests = ["pytest (>=4.6)"] name = "msgpack" version = "1.0.4" description = "MessagePack serializer" +category = "main" optional = true python-versions = "*" files = [ @@ -1866,6 +1953,7 @@ files = [ name = "multidict" version = "6.0.2" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1934,6 +2022,7 @@ files = [ name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "main" optional = false python-versions = "*" files = [ @@ -1945,6 +2034,7 @@ files = [ name = "natsort" version = "8.2.0" description = "Simple yet flexible natural sorting in Python." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -1960,6 +2050,7 @@ icu = ["PyICU (>=1.0.0)"] name = "nodeenv" version = "1.7.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1974,6 +2065,7 @@ setuptools = "*" name = "numpy" version = "1.25.2" description = "Fundamental package for array computing in Python" +category = "dev" optional = false python-versions = ">=3.9" files = [ @@ -2008,6 +2100,7 @@ files = [ name = "packaging" version = "23.0" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2019,6 +2112,7 @@ files = [ name = "parsimonious" version = "0.9.0" description = "(Soon to be) the fastest pure-Python PEG parser I could muster" +category = "dev" optional = false python-versions = "*" files = [ @@ -2032,6 +2126,7 @@ regex = ">=2022.3.15" name = "pastel" version = "0.2.1" description = "Bring colors to your terminal." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2043,6 +2138,7 @@ files = [ name = "pathspec" version = "0.10.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2054,6 +2150,7 @@ files = [ name = "pipdeptree" version = "2.12.0" description = "Command line utility to show dependency tree of packages." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2069,6 +2166,7 @@ test = ["covdefaults (>=2.3)", "diff-cover (>=7.7)", "pip (>=23.2)", "pytest (>= name = "platformdirs" version = "2.5.4" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2084,6 +2182,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2099,6 +2198,7 @@ testing = ["pytest", "pytest-benchmark"] name = "poethepoet" version = "0.22.0" description = "A task runner that works well with poetry." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2117,6 +2217,7 @@ poetry-plugin = ["poetry (>=1.0,<2.0)"] name = "poseidon-py" version = "0.1.3" description = "Python implementation of Poseidon hash" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2175,6 +2276,7 @@ files = [ name = "prometheus-client" version = "0.17.1" description = "Python client for the Prometheus monitoring system." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2189,6 +2291,7 @@ twisted = ["twisted"] name = "protobuf" version = "4.23.4" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2211,6 +2314,7 @@ files = [ name = "pycryptodome" version = "3.18.0" description = "Cryptographic library for Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2252,6 +2356,7 @@ files = [ name = "Pygments" version = "2.13.0" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -2266,6 +2371,7 @@ plugins = ["importlib-metadata"] name = "pylint" version = "2.17.5" description = "python code static checker" +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -2295,6 +2401,7 @@ testutils = ["gitpython (>3)"] name = "pyright" version = "1.1.325" description = "Command line wrapper for pyright" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2313,6 +2420,7 @@ dev = ["twine (>=3.4.1)"] name = "pyrsistent" version = "0.19.3" description = "Persistent/Functional/Immutable data structures" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2349,6 +2457,7 @@ files = [ name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2371,6 +2480,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2389,6 +2499,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2407,6 +2518,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.11.1" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2420,10 +2532,31 @@ pytest = ">=5.0" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-profiling" +version = "1.7.0" +description = "Profiling plugin for py.test" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"}, + {file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"}, +] + +[package.dependencies] +gprof2dot = "*" +pytest = "*" +six = "*" + +[package.extras] +tests = ["pytest-virtualenv"] + [[package]] name = "pytest-rerunfailures" version = "12.0" description = "pytest plugin to re-run tests to eliminate flaky failures" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2439,6 +2572,7 @@ pytest = ">=6.2" name = "pytest-xdist" version = "3.3.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2455,10 +2589,26 @@ psutil = ["psutil (>=3.0)"] setproctitle = ["setproctitle"] testing = ["filelock"] +[[package]] +name = "python-dotenv" +version = "1.0.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, + {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "pytz" version = "2022.6" description = "World timezone definitions, modern and historical" +category = "main" optional = true python-versions = "*" files = [ @@ -2470,6 +2620,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "main" optional = false python-versions = "*" files = [ @@ -2493,6 +2644,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2501,7 +2653,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2509,15 +2660,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2534,7 +2678,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2542,7 +2685,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2552,6 +2694,7 @@ files = [ name = "regex" version = "2023.6.3" description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2649,6 +2792,7 @@ files = [ name = "requests" version = "2.28.1" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -2670,6 +2814,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rlp" version = "3.0.0" description = "A package for Recursive Length Prefix encoding and decoding" +category = "dev" optional = false python-versions = "*" files = [ @@ -2691,6 +2836,7 @@ test = ["hypothesis (==5.19.0)", "pytest (>=6.2.5,<7)", "tox (>=2.9.1,<3)"] name = "ruamel.yaml" version = "0.17.21" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +category = "main" optional = true python-versions = ">=3" files = [ @@ -2709,6 +2855,7 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] name = "ruamel.yaml.clib" version = "0.2.7" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +category = "main" optional = true python-versions = ">=3.5" files = [ @@ -2719,8 +2866,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, - {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, + {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, @@ -2755,6 +2901,7 @@ files = [ name = "setuptools" version = "68.1.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2771,6 +2918,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2782,6 +2930,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "main" optional = true python-versions = "*" files = [ @@ -2793,6 +2942,7 @@ files = [ name = "soupsieve" version = "2.3.2.post1" description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -2804,6 +2954,7 @@ files = [ name = "sphinx" version = "7.1.2" description = "Python documentation generator" +category = "main" optional = true python-versions = ">=3.8" files = [ @@ -2839,6 +2990,7 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] name = "sphinx-autodoc-typehints" version = "1.19.5" description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -2858,6 +3010,7 @@ type-comment = ["typed-ast (>=1.5.4)"] name = "sphinx-basic-ng" version = "1.0.0b1" description = "A modern skeleton for Sphinx themes." +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -2875,6 +3028,7 @@ docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-ta name = "sphinx-jinja2-compat" version = "0.2.0" description = "Patches Jinja2 v3 to restore compatibility with earlier Sphinx versions." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -2890,6 +3044,7 @@ markupsafe = ">=1" name = "sphinx-prompt" version = "1.5.0" description = "Sphinx directive to add unselectable prompt" +category = "main" optional = true python-versions = "*" files = [ @@ -2904,6 +3059,7 @@ Sphinx = "*" name = "sphinx-tabs" version = "3.4.1" description = "Tabbed views for Sphinx" +category = "main" optional = true python-versions = "~=3.7" files = [ @@ -2924,6 +3080,7 @@ testing = ["bs4", "coverage", "pygments", "pytest (>=7.1,<8)", "pytest-cov", "py name = "sphinx-toolbox" version = "3.2.0" description = "Box of handy tools for Sphinx 🧰 📔" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -2958,6 +3115,7 @@ testing = ["coincidence (>=0.4.3)", "pygments (>=2.7.4)"] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "main" optional = true python-versions = ">=3.5" files = [ @@ -2973,6 +3131,7 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "main" optional = true python-versions = ">=3.5" files = [ @@ -2988,6 +3147,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -3003,6 +3163,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "main" optional = true python-versions = ">=3.5" files = [ @@ -3017,6 +3178,7 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "main" optional = true python-versions = ">=3.5" files = [ @@ -3032,6 +3194,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "main" optional = true python-versions = ">=3.5" files = [ @@ -3045,17 +3208,18 @@ test = ["pytest"] [[package]] name = "starknet-devnet" -version = "0.5.5" +version = "0.6.2" description = "A local testnet for Starknet" +category = "dev" optional = false python-versions = ">=3.9,<3.10" files = [ - {file = "starknet_devnet-0.5.5-py3-none-any.whl", hash = "sha256:1c7710c887fe4c36f620297f9ac1c39b1ebaa8809e7b982089d09de274e67a2f"}, - {file = "starknet_devnet-0.5.5.tar.gz", hash = "sha256:ccaae99ce84921f680dc6cabb6632d492860d202d154c08ff62414aab7678f97"}, + {file = "starknet_devnet-0.6.2-py3-none-any.whl", hash = "sha256:e3654c7373851512df63ea8e0d6dc92bdb9dd11be1ccd1f7246f4f3943ca046e"}, + {file = "starknet_devnet-0.6.2.tar.gz", hash = "sha256:a58b3b93fe9c5e43c538bbd0a76194c44d20fc9434193ec722d6b0daf088f518"}, ] [package.dependencies] -cairo-lang = "0.12.0" +cairo-lang = "0.12.2" cloudpickle = ">=2.1.0,<2.2.0" crypto-cpp-py = ">=1.4.0,<1.5.0" Flask = {version = ">=2.0.3,<2.1.0", extras = ["async"]} @@ -3065,6 +3229,7 @@ jsonschema = ">=4.17.0,<4.18.0" marshmallow = ">=3.17.0,<3.18.0" marshmallow-dataclass = ">=8.4,<8.5" poseidon-py = ">=0.1.3,<0.2.0" +pyyaml = ">=6.0.1,<6.1.0" typing-extensions = ">=4.3.0,<4.4.0" web3 = ">=6.0.0,<6.1.0" Werkzeug = ">=2.0.3,<2.1.0" @@ -3073,6 +3238,7 @@ Werkzeug = ">=2.0.3,<2.1.0" name = "sympy" version = "1.11.1" description = "Computer algebra system (CAS) in Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3087,6 +3253,7 @@ mpmath = ">=0.19" name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -3101,6 +3268,7 @@ widechars = ["wcwidth"] name = "tomli" version = "1.2.3" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3112,6 +3280,7 @@ files = [ name = "tomlkit" version = "0.11.6" description = "Style preserving TOML library" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3123,6 +3292,7 @@ files = [ name = "toolz" version = "0.12.0" description = "List processing tools and functional utilities" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3134,6 +3304,7 @@ files = [ name = "typeguard" version = "2.13.3" description = "Run-time type checker for Python" +category = "dev" optional = false python-versions = ">=3.5.3" files = [ @@ -3149,6 +3320,7 @@ test = ["mypy", "pytest", "typing-extensions"] name = "typing-extensions" version = "4.3.0" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3160,6 +3332,7 @@ files = [ name = "typing-inspect" version = "0.8.0" description = "Runtime inspection utilities for typing module." +category = "main" optional = false python-versions = "*" files = [ @@ -3175,6 +3348,7 @@ typing-extensions = ">=3.7.4" name = "urllib3" version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ @@ -3191,6 +3365,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "web3" version = "6.0.0" description = "web3.py" +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -3225,6 +3400,7 @@ tester = ["eth-tester[py-evm] (==v0.8.0-b.3)", "py-geth (>=3.11.0)"] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "main" optional = true python-versions = "*" files = [ @@ -3236,6 +3412,7 @@ files = [ name = "websockets" version = "11.0.3" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3315,6 +3492,7 @@ files = [ name = "werkzeug" version = "2.0.3" description = "The comprehensive WSGI web application library." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3329,6 +3507,7 @@ watchdog = ["watchdog"] name = "wrapt" version = "1.14.1" description = "Module for decorators, wrappers and monkey patching." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -3402,6 +3581,7 @@ files = [ name = "yarl" version = "1.8.1" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3474,6 +3654,7 @@ multidict = ">=4.0" name = "zipp" version = "3.10.0" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -3486,9 +3667,9 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] -docs = ["enum-tools", "furo", "sphinx"] +docs = ["sphinx", "enum-tools", "furo"] [metadata] lock-version = "2.0" python-versions = ">=3.8, <3.12" -content-hash = "ffdf5df1f61c21540e8758eb7b03aa5d767d5c9ac2d59d5878e87b5f4c8a2952" +content-hash = "b8dbf307acb1e5bc8759fb5d9eacc84a4f3c4f21e7bcf02f8b418db2d131b9cf" diff --git a/pyproject.toml b/pyproject.toml index 912829659..6cd93bb29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,25 +46,26 @@ pyright = "^1.1.298" pytest-cov = "^4.0.0" isort = "^5.11.4" pytest-rerunfailures = "^12.0" +python-dotenv = "^1.0.0" [tool.poetry.group.py39-dev.dependencies] -cairo-lang = {version = "0.12.0", python = ">=3.9, <3.10"} -starknet-devnet = {version = "0.5.5", python = ">=3.9, <3.10"} +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_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/integration_tests/client_test.py" -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/integration_tests/client_test.py" -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/integration_tests/client_test.py" -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/integration_tests/client_test.py" - -# order of tests below is important, explanation in /integration_tests/client_test.py above 'test_wait_for_tx_reverted_full_node' -test_ci_integration = ["test_ci_integration_full_node", "test_ci_integration_gateway"] -test_ci_integration_gateway = "coverage run -m pytest --client=gateway -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/integration_tests/client_test.py" -test_ci_integration_full_node = "coverage run -m pytest --client=full_node -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/integration_tests/client_test.py" +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" + +# 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_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" diff --git a/starknet_py/abi/parser_test.py b/starknet_py/abi/parser_test.py index 4a4700600..a9b6c6c27 100644 --- a/starknet_py/abi/parser_test.py +++ b/starknet_py/abi/parser_test.py @@ -5,7 +5,7 @@ import starknet_py.tests.e2e.fixtures.abi_structures as fixtures from starknet_py.abi.parser import AbiParser, AbiParsingError from starknet_py.cairo.type_parser import UnknownCairoTypeError -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract @@ -192,7 +192,7 @@ def test_missing_type_used(missing_name, input_dict): def test_deserialize_proxy_abi(): # Contains all types of ABI apart from structures abi = json.loads( - read_contract("oz_proxy_abi.json", directory=CONTRACTS_COMPILED_DIR) + read_contract("oz_proxy_abi.json", directory=CONTRACTS_COMPILED_V0_DIR) ) deserialized = AbiParser(abi).parse() @@ -202,7 +202,9 @@ def test_deserialize_proxy_abi(): def test_deserialize_balance_struct_event_abi(): # Contains all types of ABI apart from structures abi = json.loads( - read_contract("balance_struct_event_abi.json", directory=CONTRACTS_COMPILED_DIR) + read_contract( + "balance_struct_event_abi.json", directory=CONTRACTS_COMPILED_V0_DIR + ) ) deserialized = AbiParser(abi).parse() diff --git a/starknet_py/abi/schemas_test.py b/starknet_py/abi/schemas_test.py index 3edefb19d..0311ba6e1 100644 --- a/starknet_py/abi/schemas_test.py +++ b/starknet_py/abi/schemas_test.py @@ -3,13 +3,15 @@ from marshmallow import EXCLUDE from starknet_py.abi.schemas import ContractAbiEntrySchema -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract def test_deserialize_abi(): abi = json.loads( - read_contract("balance_struct_event_abi.json", directory=CONTRACTS_COMPILED_DIR) + read_contract( + "balance_struct_event_abi.json", directory=CONTRACTS_COMPILED_V0_DIR + ) ) deserialized = [ ContractAbiEntrySchema().load(entry, unknown=EXCLUDE) for entry in abi diff --git a/starknet_py/conftest.py b/starknet_py/conftest.py index 5fdec6664..107e392a8 100644 --- a/starknet_py/conftest.py +++ b/starknet_py/conftest.py @@ -12,5 +12,5 @@ "starknet_py.tests.e2e.client.fixtures.transactions", "starknet_py.tests.e2e.client.fixtures.prepare_network", "starknet_py.tests.e2e.core.fixtures", - "starknet_py.tests.e2e.integration_tests.fixtures", + "starknet_py.tests.e2e.tests_on_networks.fixtures", ] diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 6059bd9ac..409adbdba 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -338,6 +338,7 @@ async def call( result = await self.call_raw(block_hash=block_hash, block_number=block_number) return self._payload_transformer.deserialize(result) + # TODO (#1182): add `cairo_version` parameter async def invoke( self, max_fee: Optional[int] = None, diff --git a/starknet_py/contract_test.py b/starknet_py/contract_test.py index a4e33f9b6..d137d14b7 100644 --- a/starknet_py/contract_test.py +++ b/starknet_py/contract_test.py @@ -7,7 +7,7 @@ def test_compute_hash(balance_contract): assert ( Contract.compute_contract_hash(balance_contract) - == 0xF6C57433D98D26B9ADD810EFFADD20FAC9C9E716EFC882E509CD016D3A1C71 + == 0x35074A58B8897CA3A38ACDD7636CA5FC530BBDA9F4FF896AB4205C6E846FF01 ) diff --git a/starknet_py/hash/class_hash_test.py b/starknet_py/hash/class_hash_test.py index b3b3c9ec1..81dcc3510 100644 --- a/starknet_py/hash/class_hash_test.py +++ b/starknet_py/hash/class_hash_test.py @@ -6,23 +6,23 @@ from starknet_py.common import create_contract_class from starknet_py.hash.class_hash import compute_class_hash -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract @pytest.mark.parametrize( "contract_source, expected_class_hash", [ - ("balance_compiled.json", 0xf6c57433d98d26b9add810effadd20fac9c9e716efc882e509cd016d3a1c71), - ("map_compiled.json", 0x4472f76ad0241ef47087cb55e6a414a7d66619a2b77e80bc9e8e79939fd6337), - ("erc20_compiled.json", 0x1d3507676871860a57ff982bb323b7208430441d42b7792a67793f14618086b), - ("oz_proxy_compiled.json", 0x746e3066bc46bd8019c37a37690107278ebec55020fb1b81e4f98c05fc15af9), - ("argent_proxy_compiled.json", 0x401d1875f49ad9f2e833f74841db03cebf8eeaa767ed31736049a3eeef7fa7f), - ("universal_deployer_compiled.json", 0x1cd10148443965964701fd22dc252b92027ac429e61e792f9cef6771db52444), - ("precompiled/oz_proxy_address_0.8.1_compiled.json", 0x413C36C287CB410D42F9E531563F68AC60A2913B5053608D640FB9B643ACFE6), + ("balance_compiled.json", 0x35074a58b8897ca3a38acdd7636ca5fc530bbda9f4ff896ab4205c6e846ff01), + ("map_compiled.json", 0x27e20b6e9c825b8a2de1a6fae317c0c05b0a3f1bc158c68885bd0fdf74e7d8e), + ("erc20_compiled.json", 0x7abcb4a526399039d84f20956d3dd25ec21ed56ac7a58841fc6d677f76f0f5e), + ("oz_proxy_compiled.json", 0x395e64cc7304606742f955ee576c79ae1b67d93d73bfc9ffe21c1088a86de34), + ("argent_proxy_compiled.json", 0x244c972f9ebd85f8390f1a4e56d5a10444933e75ad4fb4a1fc88f16c7fed148), + ("universal_deployer_compiled.json", 0x3f9c23fab233e00720eb3acc797d8f0d2e08907eac198e74ccd2631cc982265), + ("precompiled/oz_proxy_address_0.8.1_compiled.json", 0x413c36c287cb410d42f9e531563f68ac60a2913b5053608d640fb9b643acfe6), ] ) def test_compute_class_hash(contract_source, expected_class_hash): - compiled_contract = read_contract(contract_source, directory=CONTRACTS_COMPILED_DIR) + compiled_contract = read_contract(contract_source, directory=CONTRACTS_COMPILED_V0_DIR) contract_class = create_contract_class(compiled_contract) initial_contract_class = copy.deepcopy(contract_class) class_hash = compute_class_hash(contract_class) diff --git a/starknet_py/hash/transaction_test.py b/starknet_py/hash/transaction_test.py index ce0ff141a..bb43f8a93 100644 --- a/starknet_py/hash/transaction_test.py +++ b/starknet_py/hash/transaction_test.py @@ -10,7 +10,7 @@ compute_transaction_hash, ) from starknet_py.net.models import StarknetChainId -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract @@ -73,7 +73,7 @@ def test_compute_deploy_account_transaction_hash(data, expected_hash): ], ) def test_compute_declare_transaction_hash(contract_json, data): - contract = read_contract(contract_json, directory=CONTRACTS_COMPILED_DIR) + contract = read_contract(contract_json, directory=CONTRACTS_COMPILED_V0_DIR) compiled_contract = create_compiled_contract(compiled_contract=contract) declare_hash = compute_declare_transaction_hash(compiled_contract, *data) diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 6e494746f..160e0115e 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -8,6 +8,7 @@ from starknet_py.net.client_errors import ClientError from starknet_py.net.client_models import ( BlockStateUpdate, + BlockTransactionTrace, BlockTransactionTraces, Call, ContractClass, @@ -68,11 +69,11 @@ async def get_block( """ @abstractmethod - async def get_block_traces( + async def trace_block_transactions( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> BlockTransactionTraces: + ) -> Union[BlockTransactionTraces, List[BlockTransactionTrace]]: """ Receive the traces of all the transactions within specified block diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index 86c1cc611..e49446610 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -401,6 +401,8 @@ class BlockSingleTransactionTrace: validate_invocation: Optional[dict] = None fee_transfer_invocation: Optional[dict] = None constructor_invocation: Optional[dict] = None + # Gateway-only field, information about reversion in RPC spec is returned inside "execute_invocation" + revert_error: Optional[str] = None @dataclass @@ -702,3 +704,142 @@ class SignatureOnStateDiff: block_number: int signature: List[int] signature_input: SignatureInput + + +# ------------------------------- Trace API dataclasses ------------------------------- + + +@dataclass +class EventContent: + """ + Dataclass representing contents of an event. + """ + + keys: List[int] + data: List[int] + + +class SimulationFlag(str, Enum): + """ + Enum class representing possible simulation flags for trace API. + """ + + SKIP_VALIDATE = "SKIP_VALIDATE" + SKIP_FEE_CHARGE = "SKIP_FEE_CHARGE" + + +class EntryPointType(Enum): + """ + Enum class representing entry point types. + """ + + EXTERNAL = "EXTERNAL" + L1_HANDLER = "L1_HANDLER" + CONSTRUCTOR = "CONSTRUCTOR" + + +class CallType(Enum): + """ + Enum class representing call types. + """ + + LIBRARY_CALL = "LIBRARY_CALL" + CALL = "CALL" + + +@dataclass +class FunctionInvocation: + """ + Dataclass representing an invocation of a function. + """ + + # pylint: disable=too-many-instance-attributes + contract_address: int + entry_point_selector: int + calldata: List[int] + caller_address: int + class_hash: int + entry_point_type: EntryPointType + call_type: CallType + result: List[int] + calls: List["FunctionInvocation"] + events: List[Event] + messages: List[L2toL1Message] + + +@dataclass +class RevertedFunctionInvocation: + """ + Dataclass representing revert reason for the transaction. + """ + + revert_reason: str + + +@dataclass +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] + + +@dataclass +class DeclareTransactionTrace: + """ + Dataclass representing a transaction trace of an DECLARE transaction. + """ + + validate_invocation: Optional[FunctionInvocation] + fee_transfer_invocation: Optional[FunctionInvocation] + + +@dataclass +class DeployAccountTransactionTrace: + """ + Dataclass representing a transaction trace of an DEPLOY_ACCOUNT transaction. + """ + + validate_invocation: Optional[FunctionInvocation] + constructor_invocation: FunctionInvocation + fee_transfer_invocation: Optional[FunctionInvocation] + + +@dataclass +class L1HandlerTransactionTrace: + """ + Dataclass representing a transaction trace of an L1_HANDLER transaction. + """ + + function_invocation: FunctionInvocation + + +TransactionTrace = Union[ + InvokeTransactionTrace, + DeclareTransactionTrace, + DeployAccountTransactionTrace, + L1HandlerTransactionTrace, +] + + +@dataclass +class SimulatedTransaction: + """ + Dataclass representing a simulated transaction returned by `starknet_simulateTransactions` method. + """ + + transaction_trace: TransactionTrace + fee_estimation: EstimatedFee + + +@dataclass +class BlockTransactionTrace: + """ + Dataclass representing a single transaction trace in a block. + """ + + transaction_hash: int + trace_root: TransactionTrace diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index e84b92ab7..deed7a616 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -11,7 +11,7 @@ from starknet_py.net.client_models import ( BlockHashAndNumber, BlockStateUpdate, - BlockTransactionTraces, + BlockTransactionTrace, Call, ContractClass, DeclareTransactionResponse, @@ -24,12 +24,15 @@ PendingStarknetBlockWithTxHashes, SentTransactionResponse, SierraContractClass, + SimulatedTransaction, + SimulationFlag, StarknetBlock, StarknetBlockWithTxHashes, SyncStatus, Tag, Transaction, TransactionReceipt, + TransactionTrace, TransactionType, ) from starknet_py.net.http_client import RpcHttpClient @@ -46,6 +49,7 @@ from starknet_py.net.schemas.rpc import ( BlockHashAndNumberSchema, BlockStateUpdateSchema, + BlockTransactionTraceSchema, ContractClassSchema, DeclareTransactionResponseSchema, DeployAccountTransactionResponseSchema, @@ -57,10 +61,12 @@ PendingTransactionsSchema, SentTransactionSchema, SierraContractClassSchema, + SimulatedTransactionSchema, StarknetBlockSchema, StarknetBlockWithTxHashesSchema, SyncStatusSchema, TransactionReceiptSchema, + TransactionTraceSchema, TypesOfTransactionsSchema, ) from starknet_py.transaction_errors import TransactionNotReceivedError @@ -150,13 +156,6 @@ async def get_block_with_tx_hashes( StarknetBlockWithTxHashesSchema().load(res, unknown=EXCLUDE), ) - async def get_block_traces( - self, - block_hash: Optional[Union[Hash, Tag]] = None, - block_number: Optional[Union[int, Tag]] = None, - ) -> BlockTransactionTraces: - raise NotImplementedError() - # TODO (#809): add tests with multiple emitted keys async def get_events( self, @@ -659,6 +658,111 @@ async def get_contract_nonce( res = cast(str, res) return int(res, 16) + # ------------------------------- Trace API ------------------------------- + + async def trace_transaction( + self, + tx_hash: Hash, + ) -> TransactionTrace: + """ + For a given executed transaction, returns the trace of its execution, including internal calls. + + :param tx_hash: Hash of the executed transaction. + :return: Trace of the transaction. + """ + res = await self._client.call( + method_name="traceTransaction", + params={ + "transaction_hash": tx_hash, + }, + ) + return cast( + TransactionTrace, TransactionTraceSchema().load(res, unknown=EXCLUDE) + ) + + async def simulate_transactions( + self, + transactions: List[AccountTransaction], + skip_validate: bool = False, + skip_fee_charge: bool = False, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> List[SimulatedTransaction]: + # pylint: disable=too-many-arguments + """ + Simulates a given sequence of transactions on the requested state, and generates the execution traces. + If one of the transactions is reverted, raises CONTRACT_ERROR. + + :param transactions: Transactions to be traced. + :param skip_validate: Flag checking whether the validation part of the transaction should be executed. + :param skip_fee_charge: Flag deciding whether fee should be deducted from the balance before the simulation + of the next transaction. + :param block_hash: Block's hash or literals `"pending"` or `"latest"` + :param block_number: Block's number or literals `"pending"` or `"latest"` + :return: The execution trace and consumed resources for each transaction. + """ + block_identifier = get_block_identifier( + block_hash=block_hash, block_number=block_number + ) + + simulation_flags = [] + if skip_validate: + simulation_flags.append(SimulationFlag.SKIP_VALIDATE) + if skip_fee_charge: + simulation_flags.append(SimulationFlag.SKIP_FEE_CHARGE) + + res = await self._client.call( + method_name="simulateTransactions", + params={ + **block_identifier, + "simulation_flags": simulation_flags, + "transactions": [ + _create_broadcasted_txn(transaction=t) for t in transactions + ], + }, + ) + return cast( + List[SimulatedTransaction], + SimulatedTransactionSchema().load(res, unknown=EXCLUDE, many=True), + ) + + async def trace_block_transactions( + self, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> List[BlockTransactionTrace]: + """ + Retrieve traces for all transactions in the given block. + + :param block_hash: Block's hash or literals `"pending"` or `"latest"` + :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) + + res = await self._client.call( + method_name="traceBlockTransactions", + params={ + "block_hash": _to_rpc_felt(block.block_hash), + }, + ) + return cast( + List[BlockTransactionTrace], + BlockTransactionTraceSchema().load(res, unknown=EXCLUDE, many=True), + ) + def get_block_identifier( block_hash: Optional[Union[Hash, Tag]] = None, diff --git a/starknet_py/net/gateway_client.py b/starknet_py/net/gateway_client.py index ba36eae1f..9832110fd 100644 --- a/starknet_py/net/gateway_client.py +++ b/starknet_py/net/gateway_client.py @@ -127,7 +127,7 @@ async def get_block( ) return StarknetBlockSchema().load(res, unknown=EXCLUDE) # pyright: ignore - async def get_block_traces( + async def trace_block_transactions( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, diff --git a/starknet_py/net/networks.py b/starknet_py/net/networks.py index cbc857523..1aa083de9 100644 --- a/starknet_py/net/networks.py +++ b/starknet_py/net/networks.py @@ -4,8 +4,10 @@ MAINNET = "mainnet" TESTNET = "testnet" +# TODO (#1178): remove that TESTNET2 = "testnet2" +# TODO (#1178): remove that PredefinedNetwork = Literal["mainnet", "testnet", "testnet2"] @@ -21,11 +23,13 @@ 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]: 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 f5c13af76..bdec50893 100644 --- a/starknet_py/net/schemas/common.py +++ b/starknet_py/net/schemas/common.py @@ -5,6 +5,8 @@ from starknet_py.net.client_models import ( BlockStatus, + CallType, + EntryPointType, StorageEntry, TransactionExecutionStatus, TransactionFinalityStatus, @@ -182,6 +184,46 @@ def _deserialize( return TransactionType(value) +class EntryPointTypeField(fields.Field): + def _serialize(self, value: Any, attr: str, obj: Any, **kwargs): + return value.name if value is not None else "" + + def _deserialize( + self, + value: Any, + attr: Optional[str], + data: Optional[Mapping[str, Any]], + **kwargs, + ) -> EntryPointType: + values = [v.value for v in EntryPointType] + + if value not in values: + raise ValidationError( + f"Invalid value provided for EntryPointType: {value}." + ) + + return EntryPointType(value) + + +class CallTypeField(fields.Field): + def _serialize(self, value: Any, attr: str, obj: Any, **kwargs): + return value.name if value is not None else "" + + def _deserialize( + self, + value: Any, + attr: Optional[str], + data: Optional[Mapping[str, Any]], + **kwargs, + ) -> CallType: + values = [v.value for v in CallType] + + if value not in values: + raise ValidationError(f"Invalid value provided for CallType: {value}.") + + return CallType(value) + + class StorageEntrySchema(Schema): key = Felt(data_key="key", required=True) value = Felt(data_key="value", required=True) diff --git a/starknet_py/net/schemas/gateway.py b/starknet_py/net/schemas/gateway.py index e4eecf30e..b86a2ff67 100644 --- a/starknet_py/net/schemas/gateway.py +++ b/starknet_py/net/schemas/gateway.py @@ -307,6 +307,7 @@ class BlockSingleTransactionTraceSchema(Schema): data_key="constructor_invocation", load_default=None, ) + revert_error = fields.String(data_key="revert_error", load_default=None) signature = fields.List(Felt(), data_key="signature", load_default=[]) transaction_hash = Felt(data_key="transaction_hash", required=True) @@ -315,18 +316,6 @@ def make_dataclass(self, data, **kwargs): return BlockSingleTransactionTrace(**data) -class BlockTransactionTracesSchema(Schema): - traces = fields.List( - fields.Nested(BlockSingleTransactionTraceSchema(unknown=EXCLUDE)), - data_key="traces", - required=True, - ) - - @post_load - def make_dataclass(self, data, **kwargs): - return BlockTransactionTraces(**data) - - class EstimatedFeeSchema(Schema): overall_fee = fields.Integer(data_key="overall_fee", required=True) gas_price = fields.Integer(data_key="gas_price", required=True) @@ -658,3 +647,18 @@ class SignatureOnStateDiffSchema(Schema): @post_load def make_dataclass(self, data, **kwargs) -> SignatureOnStateDiff: return SignatureOnStateDiff(**data) + + +# Trace API schemas + + +class BlockTransactionTracesSchema(Schema): + traces = fields.List( + fields.Nested(BlockSingleTransactionTraceSchema(unknown=EXCLUDE)), + data_key="traces", + required=True, + ) + + @post_load + def make_dataclass(self, data, **kwargs): + return BlockTransactionTraces(**data) diff --git a/starknet_py/net/schemas/rpc.py b/starknet_py/net/schemas/rpc.py index 0527658c7..2b428e6cf 100644 --- a/starknet_py/net/schemas/rpc.py +++ b/starknet_py/net/schemas/rpc.py @@ -5,31 +5,40 @@ from starknet_py.net.client_models import ( BlockHashAndNumber, BlockStateUpdate, + BlockTransactionTrace, ContractClass, ContractsNonce, DeclaredContractHash, DeclareTransaction, DeclareTransactionResponse, + DeclareTransactionTrace, DeployAccountTransaction, DeployAccountTransactionResponse, + DeployAccountTransactionTrace, DeployedContract, DeployTransaction, EntryPoint, EntryPointsByType, EstimatedFee, Event, + EventContent, EventsChunk, + FunctionInvocation, InvokeTransaction, + InvokeTransactionTrace, L1HandlerTransaction, + L1HandlerTransactionTrace, L2toL1Message, PendingBlockStateUpdate, PendingStarknetBlock, PendingStarknetBlockWithTxHashes, ReplacedClass, + RevertedFunctionInvocation, SentTransactionResponse, SierraContractClass, SierraEntryPoint, SierraEntryPointsByType, + SimulatedTransaction, StarknetBlock, StarknetBlockWithTxHashes, StateDiff, @@ -39,6 +48,8 @@ ) from starknet_py.net.schemas.common import ( BlockStatusField, + CallTypeField, + EntryPointTypeField, ExecutionStatusField, Felt, FinalityStatusField, @@ -481,7 +492,7 @@ class SentTransactionSchema(Schema): transaction_hash = Felt(data_key="transaction_hash", required=True) @post_load - def make_dataclass(self, data, **kwargs): + def make_dataclass(self, data, **kwargs) -> SentTransactionResponse: return SentTransactionResponse(**data) @@ -489,7 +500,7 @@ class DeclareTransactionResponseSchema(SentTransactionSchema): class_hash = Felt(data_key="class_hash", required=True) @post_load - def make_dataclass(self, data, **kwargs): + def make_dataclass(self, data, **kwargs) -> DeclareTransactionResponse: return DeclareTransactionResponse(**data) @@ -497,7 +508,7 @@ class DeployAccountTransactionResponseSchema(SentTransactionSchema): address = Felt(data_key="contract_address", required=True) @post_load - def make_dataclass(self, data, **kwargs): + def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionResponse: return DeployAccountTransactionResponse(**data) @@ -510,3 +521,170 @@ class PendingTransactionsSchema(Schema): @post_load def make_dataclass(self, data, **kwargs): return data["pending_transactions"] + + +# ------------------------------- Trace API ------------------------------- + + +class EventContentSchema(Schema): + keys = fields.List(Felt(), data_key="keys", required=True) + data = fields.List(Felt(), data_key="data", required=True) + + @post_load + def make_dataclass(self, data, **kwargs): + return EventContent(**data) + + +class FunctionInvocationSchema(Schema): + contract_address = Felt(data_key="contract_address", required=True) + entry_point_selector = Felt(data_key="entry_point_selector", required=True) + calldata = fields.List(Felt(), data_key="calldata", required=True) + caller_address = Felt(data_key="caller_address", required=True) + class_hash = Felt(data_key="class_hash", required=True) + entry_point_type = EntryPointTypeField(data_key="entry_point_type", required=True) + call_type = CallTypeField(data_key="call_type", required=True) + result = fields.List(Felt(), data_key="result", required=True) + # https://marshmallow.readthedocs.io/en/stable/nesting.html#nesting-a-schema-within-itself + calls = fields.List( + fields.Nested( + lambda: FunctionInvocationSchema() # pylint: disable=unnecessary-lambda + ), + data_key="calls", + required=True, + ) + events = fields.List( + fields.Nested(EventContentSchema()), data_key="events", required=True + ) + messages = fields.List( + fields.Nested(L2toL1MessageSchema()), data_key="messages", required=True + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> FunctionInvocation: + return FunctionInvocation(**data) + + +class RevertedFunctionInvocationSchema(Schema): + revert_reason = fields.String(data_key="revert_reason", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> RevertedFunctionInvocation: + return RevertedFunctionInvocation(**data) + + +class ExecuteInvocationSchema(OneOfSchema): + type_schemas = { + "REVERTED": RevertedFunctionInvocationSchema(), + "FUNCTION_INVOCATION": FunctionInvocationSchema(), + } + + def get_data_type(self, data): + if "revert_reason" in data: + return "REVERTED" + return "FUNCTION_INVOCATION" + + +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 + ) + fee_transfer_invocation = fields.Nested( + FunctionInvocationSchema(), + data_key="fee_transfer_invocation", + load_default=None, + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> InvokeTransactionTrace: + return InvokeTransactionTrace(**data) + + +class DeclareTransactionTraceSchema(Schema): + 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, + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> DeclareTransactionTrace: + return DeclareTransactionTrace(**data) + + +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 + ) + fee_transfer_invocation = fields.Nested( + FunctionInvocationSchema(), + data_key="fee_transfer_invocation", + load_default=None, + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionTrace: + return DeployAccountTransactionTrace(**data) + + +class L1HandlerTransactionTraceSchema(Schema): + function_invocation = fields.Nested( + FunctionInvocationSchema(), data_key="function_invocation", required=True + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> L1HandlerTransactionTrace: + return L1HandlerTransactionTrace(**data) + + +class TransactionTraceSchema(OneOfSchema): + type_schemas = { + "INVOKE": InvokeTransactionTraceSchema(), + "DECLARE": DeclareTransactionTraceSchema(), + "DEPLOY_ACCOUNT": DeployAccountTransactionTraceSchema(), + "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" + + +class SimulatedTransactionSchema(Schema): + transaction_trace = fields.Nested( + TransactionTraceSchema(), data_key="transaction_trace", required=True + ) + fee_estimation = fields.Nested( + EstimatedFeeSchema(), data_key="fee_estimation", required=True + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> SimulatedTransaction: + return SimulatedTransaction(**data) + + +class BlockTransactionTraceSchema(Schema): + transaction_hash = Felt(data_key="transaction_hash", required=True) + trace_root = fields.Nested( + TransactionTraceSchema(), data_key="trace_root", required=True + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> BlockTransactionTrace: + return BlockTransactionTrace(**data) diff --git a/starknet_py/net/signer/test_stark_curve_signer.py b/starknet_py/net/signer/test_stark_curve_signer.py index cb696a45f..4f5e7c4ca 100644 --- a/starknet_py/net/signer/test_stark_curve_signer.py +++ b/starknet_py/net/signer/test_stark_curve_signer.py @@ -4,11 +4,11 @@ from starknet_py.net.models import StarknetChainId from starknet_py.net.models.transaction import Declare, DeployAccount, Invoke from starknet_py.net.signer.stark_curve_signer import KeyPair, StarkCurveSigner -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract compiled_contract = read_contract( - "erc20_compiled.json", directory=CONTRACTS_COMPILED_DIR + "erc20_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) diff --git a/starknet_py/serialization/serialization_test.py b/starknet_py/serialization/serialization_test.py index 99a8be4bb..02411a64d 100644 --- a/starknet_py/serialization/serialization_test.py +++ b/starknet_py/serialization/serialization_test.py @@ -11,7 +11,7 @@ serializer_for_function, ) from starknet_py.serialization.tuple_dataclass import TupleDataclass -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract dog = {"name": encode_shortstring("Cooper"), "species": encode_shortstring("dog")} @@ -99,7 +99,7 @@ class Education(NamedTuple): ] abi = json.loads( - read_contract("complex_abi_abi.json", directory=CONTRACTS_COMPILED_DIR) + read_contract("complex_abi_abi.json", directory=CONTRACTS_COMPILED_V0_DIR) ) parsed_abi = AbiParser(abi).parse() diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 4698ed481..b9691e064 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -24,7 +24,6 @@ from starknet_py.net.signer.stark_curve_signer import KeyPair from starknet_py.net.udc_deployer.deployer import Deployer from starknet_py.tests.e2e.fixtures.constants import MAX_FEE -from starknet_py.transaction_errors import TransactionRejectedError @pytest.mark.run_on_devnet @@ -103,26 +102,18 @@ async def test_sending_multicall(account, map_contract, key, val): assert value == val -# TODO (#981): FullNode is not tested because we don't implement trace api (devnet does not either) @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_get_block_traces(gateway_account): - traces = await gateway_account.client.get_block_traces(block_number=2) + traces = await gateway_account.client.trace_block_transactions(block_number=2) assert traces.traces != [] @pytest.mark.asyncio -async def test_rejection_reason_in_transaction_receipt(account, map_contract): - res = await map_contract.functions["put"].invoke(key=10, value=20, max_fee=1) - - with pytest.raises(TransactionRejectedError): - await account.client.wait_for_tx(res.hash) - - transaction_receipt = await account.client.get_transaction_receipt(res.hash) - - if isinstance(account.client, GatewayClient): - assert "Actual fee exceeded max fee." in transaction_receipt.rejection_reason +async def test_rejection_reason_in_transaction_receipt(map_contract): + with pytest.raises(ClientError, match=r".*INSUFFICIENT_MAX_FEE.*"): + _ = await map_contract.functions["put"].invoke(key=10, value=20, max_fee=1) def test_sign_and_verify_offchain_message_fail(account, typed_data): @@ -383,15 +374,22 @@ async def test_deploy_account_raises_on_incorrect_address( @pytest.mark.asyncio @pytest.mark.parametrize( - "call_contract", + "call_contract, client", [ - "starknet_py.net.gateway_client.GatewayClient.call_contract", - "starknet_py.net.full_node_client.FullNodeClient.call_contract", + ( + "starknet_py.net.gateway_client.GatewayClient.call_contract", + "gateway_client", + ), + ( + "starknet_py.net.full_node_client.FullNodeClient.call_contract", + "full_node_client", + ), ], ) async def test_deploy_account_raises_on_no_enough_funds( - deploy_account_details_factory, call_contract, client + deploy_account_details_factory, call_contract, client, request ): + client = request.getfixturevalue(client) address, key_pair, salt, class_hash = await deploy_account_details_factory.get() with patch(call_contract, AsyncMock()) as mocked_balance: @@ -453,6 +451,10 @@ async def test_deploy_account_passes_on_enough_funds( ) +# TODO (#1056): change this test to braavos account +@pytest.mark.skip( + reason="'__validate_execute__' doesn't allow any other calldata than in the constructor" +) @pytest.mark.asyncio async def test_deploy_account_uses_custom_calldata( client, deploy_account_details_factory, fee_contract diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index b38c80b20..9ddf1c030 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -32,7 +32,7 @@ from starknet_py.net.gateway_client import GatewayClient from starknet_py.net.models.transaction import DeclareV2 from starknet_py.net.udc_deployer.deployer import Deployer -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR, MAX_FEE +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR, MAX_FEE from starknet_py.tests.e2e.fixtures.misc import read_contract from starknet_py.transaction_errors import ( TransactionNotReceivedError, @@ -168,7 +168,7 @@ async def test_estimate_fee_invoke(account, contract_address): async def test_estimate_fee_declare(account): declare_tx = await account.sign_declare_transaction( compiled_contract=read_contract( - "map_compiled.json", directory=CONTRACTS_COMPILED_DIR + "map_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ), max_fee=MAX_FEE, ) @@ -203,7 +203,7 @@ async def test_estimate_fee_for_multiple_transactions( declare_tx = await account.sign_declare_transaction( compiled_contract=read_contract( - "map_compiled.json", directory=CONTRACTS_COMPILED_DIR + "map_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ), max_fee=MAX_FEE, ) diff --git a/starknet_py/tests/e2e/client/fixtures/prepare_network.py b/starknet_py/tests/e2e/client/fixtures/prepare_network.py index 6d82d7c03..5e9aa4304 100644 --- a/starknet_py/tests/e2e/client/fixtures/prepare_network.py +++ b/starknet_py/tests/e2e/client/fixtures/prepare_network.py @@ -11,7 +11,7 @@ prepare_net_for_tests, ) from starknet_py.tests.e2e.fixtures.accounts import AccountToBeDeployedDetailsFactory -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract from starknet_py.tests.e2e.utils import AccountToBeDeployedDetails @@ -21,7 +21,7 @@ async def prepare_network( deploy_account_details: AccountToBeDeployedDetails, ) -> PreparedNetworkData: contract_compiled = read_contract( - "balance_compiled.json", directory=CONTRACTS_COMPILED_DIR + "balance_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) prepared_data = await prepare_net_for_tests( @@ -130,7 +130,7 @@ def fixture_balance_contract() -> str: """ Returns compiled code of the balance.cairo contract """ - return read_contract("balance_compiled.json", directory=CONTRACTS_COMPILED_DIR) + return read_contract("balance_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR) @pytest.fixture(name="class_hash") diff --git a/starknet_py/tests/e2e/client/fixtures/transactions.py b/starknet_py/tests/e2e/client/fixtures/transactions.py index d307a0cbf..7dcdbc6ea 100644 --- a/starknet_py/tests/e2e/client/fixtures/transactions.py +++ b/starknet_py/tests/e2e/client/fixtures/transactions.py @@ -11,7 +11,7 @@ from starknet_py.tests.e2e.client.fixtures.prepare_net_for_gateway_test import ( PreparedNetworkData, ) -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR, MAX_FEE +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR, MAX_FEE from starknet_py.tests.e2e.fixtures.misc import read_contract from starknet_py.tests.e2e.utils import ( get_deploy_account_details, @@ -95,7 +95,7 @@ async def replaced_class(account: Account, map_class_hash: int) -> Tuple[int, in Returns block_number, contract_address and class_hash of transaction replacing implementation. """ compiled_contract = read_contract( - "replace_class_compiled.json", directory=CONTRACTS_COMPILED_DIR + "replace_class_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) declare_result = await Contract.declare(account, compiled_contract, max_fee=MAX_FEE) diff --git a/starknet_py/tests/e2e/client/full_node_test.py b/starknet_py/tests/e2e/client/full_node_test.py index f17b97252..90134de23 100644 --- a/starknet_py/tests/e2e/client/full_node_test.py +++ b/starknet_py/tests/e2e/client/full_node_test.py @@ -1,22 +1,36 @@ +import dataclasses from unittest.mock import AsyncMock, patch import pytest +from starknet_py.common import create_casm_class from starknet_py.contract import Contract +from starknet_py.hash.address import compute_address +from starknet_py.hash.casm_class_hash import compute_casm_class_hash from starknet_py.hash.selector import get_selector_from_name from starknet_py.hash.storage import get_storage_var_address from starknet_py.net.account.account import Account from starknet_py.net.client_errors import ClientError from starknet_py.net.client_models import ( BlockHashAndNumber, + Call, ContractClass, DeclareTransaction, + DeclareTransactionTrace, + DeployAccountTransactionTrace, + InvokeTransactionTrace, SierraContractClass, + SimulatedTransaction, SyncStatus, TransactionType, ) from starknet_py.net.full_node_client import _to_rpc_felt from starknet_py.net.models import StarknetChainId +from starknet_py.tests.e2e.fixtures.constants import ( + CONTRACTS_COMPILED_V0_DIR, + CONTRACTS_COMPILED_V1_DIR, +) +from starknet_py.tests.e2e.fixtures.misc import read_contract from starknet_py.tests.e2e.utils import create_empty_block @@ -400,3 +414,183 @@ async def test_get_syncing_status(full_node_client): sync_status = await full_node_client.get_syncing_status() assert isinstance(sync_status, SyncStatus) + + +# ---------------------------- Trace API tests ---------------------------- + + +@pytest.mark.asyncio +async def test_simulate_transactions_skip_validate( + full_node_account, deployed_balance_contract +): + assert isinstance(deployed_balance_contract, Contract) + call = Call( + to_addr=deployed_balance_contract.address, + selector=get_selector_from_name("increase_balance"), + calldata=[0x10], + ) + invoke_tx = await full_node_account.sign_invoke_transaction( + calls=call, auto_estimate=True + ) + invoke_tx = dataclasses.replace(invoke_tx, signature=[]) + + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[invoke_tx], skip_validate=True, block_number="latest" + ) + assert simulated_txs[0].transaction_trace.validate_invocation is None + + with pytest.raises(ClientError, match=r".*INVALID_SIGNATURE_LENGTH.*"): + _ = await full_node_account.client.simulate_transactions( + transactions=[invoke_tx], block_number="latest" + ) + + +@pytest.mark.asyncio +async def test_simulate_transactions_skip_fee_charge( + full_node_account, deployed_balance_contract +): + assert isinstance(deployed_balance_contract, Contract) + call = Call( + to_addr=deployed_balance_contract.address, + selector=get_selector_from_name("increase_balance"), + calldata=[0x10], + ) + invoke_tx = await full_node_account.sign_invoke_transaction( + calls=call, auto_estimate=True + ) + + # TODO (#1179): change this test + # because of python devnet, the SKIP_FEE_CHARGE flag isn't accepted + with pytest.raises(ClientError, match=r".*SKIP_FEE_CHARGE.*"): + _ = await full_node_account.client.simulate_transactions( + transactions=[invoke_tx], skip_fee_charge=True, block_number="latest" + ) + + +@pytest.mark.asyncio +async def test_simulate_transactions_invoke( + full_node_account, deployed_balance_contract +): + assert isinstance(deployed_balance_contract, Contract) + call = Call( + to_addr=deployed_balance_contract.address, + selector=get_selector_from_name("increase_balance"), + calldata=[0x10], + ) + invoke_tx = await full_node_account.sign_invoke_transaction( + calls=call, auto_estimate=True + ) + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[invoke_tx], block_number="latest" + ) + + assert isinstance(simulated_txs[0], SimulatedTransaction) + assert isinstance(simulated_txs[0].transaction_trace, InvokeTransactionTrace) + assert simulated_txs[0].transaction_trace.execute_invocation is not None + + invoke_tx = await full_node_account.sign_invoke_transaction( + calls=[call, call], auto_estimate=True + ) + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[invoke_tx], block_number="latest" + ) + + assert isinstance(simulated_txs[0].transaction_trace, InvokeTransactionTrace) + assert simulated_txs[0].transaction_trace.validate_invocation is not None + assert simulated_txs[0].transaction_trace.execute_invocation is not None + + +@pytest.mark.asyncio +async def test_simulate_transactions_declare(full_node_account): + 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) + ) + + 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 + assert simulated_txs[0].transaction_trace.validate_invocation is not None + + +@pytest.mark.asyncio +async def test_simulate_transactions_two_txs( + full_node_account, deployed_balance_contract +): + assert isinstance(deployed_balance_contract, Contract) + call = Call( + to_addr=deployed_balance_contract.address, + selector=get_selector_from_name("increase_balance"), + calldata=[0x10], + ) + invoke_tx = await full_node_account.sign_invoke_transaction( + calls=call, auto_estimate=True + ) + + compiled_v2_contract = read_contract( + "test_contract_declare_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + compiled_v2_contract_casm = read_contract( + "test_contract_declare_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ) + casm_class = create_casm_class(compiled_v2_contract_casm) + casm_class_hash = compute_casm_class_hash(casm_class) + + declare_v2_tx = await full_node_account.sign_declare_v2_transaction( + compiled_contract=compiled_v2_contract, + compiled_class_hash=casm_class_hash, + # because raw calls do not increment nonce, it needs to be done manually + nonce=invoke_tx.nonce + 1, + max_fee=int(1e16), + ) + + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[invoke_tx, declare_v2_tx], block_number="latest" + ) + + assert isinstance(simulated_txs[0].transaction_trace, InvokeTransactionTrace) + assert simulated_txs[0].fee_estimation.overall_fee > 0 + assert simulated_txs[0].transaction_trace.validate_invocation is not None + assert simulated_txs[0].transaction_trace.execute_invocation is not None + + assert isinstance(simulated_txs[1].transaction_trace, DeclareTransactionTrace) + assert simulated_txs[1].fee_estimation.overall_fee > 0 + assert simulated_txs[1].transaction_trace.validate_invocation is not None + + +@pytest.mark.asyncio +async def test_simulate_transactions_deploy_account( + full_node_client, deploy_account_details_factory +): + address, key_pair, salt, class_hash = await deploy_account_details_factory.get() + address = compute_address( + salt=salt, + class_hash=class_hash, + constructor_calldata=[key_pair.public_key], + deployer_address=0, + ) + account = Account( + address=address, + client=full_node_client, + key_pair=key_pair, + chain=StarknetChainId.TESTNET, + ) + deploy_account_tx = await account.sign_deploy_account_transaction( + class_hash=class_hash, + contract_address_salt=salt, + constructor_calldata=[key_pair.public_key], + max_fee=int(1e16), + ) + + simulated_txs = await full_node_client.simulate_transactions( + transactions=[deploy_account_tx], block_number="latest" + ) + + assert isinstance(simulated_txs[0].transaction_trace, DeployAccountTransactionTrace) + assert simulated_txs[0].fee_estimation.overall_fee > 0 + assert simulated_txs[0].transaction_trace.constructor_invocation is not None diff --git a/starknet_py/tests/e2e/client/gateway_test.py b/starknet_py/tests/e2e/client/gateway_test.py index dc1005c1d..f62545059 100644 --- a/starknet_py/tests/e2e/client/gateway_test.py +++ b/starknet_py/tests/e2e/client/gateway_test.py @@ -98,7 +98,6 @@ async def test_get_transaction_status(invoke_transaction_hash, gateway_client): ) assert isinstance(tx_status_resp, TransactionStatusResponse) assert tx_status_resp.transaction_status == TransactionStatus.ACCEPTED_ON_L2 - assert isinstance(tx_status_resp.block_hash, int) def test_gateway_client_warn_deprecation(): @@ -111,6 +110,7 @@ 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/contract_interaction/interaction_test.py b/starknet_py/tests/e2e/contract_interaction/interaction_test.py index 91493f1b8..4c78bc7b1 100644 --- a/starknet_py/tests/e2e/contract_interaction/interaction_test.py +++ b/starknet_py/tests/e2e/contract_interaction/interaction_test.py @@ -7,7 +7,7 @@ from starknet_py.net.client_models import Call from starknet_py.net.gateway_client import GatewayClient from starknet_py.tests.e2e.fixtures.constants import MAX_FEE -from starknet_py.transaction_errors import TransactionRejectedError +from starknet_py.transaction_errors import TransactionRevertedError @pytest.mark.asyncio @@ -15,18 +15,25 @@ async def test_max_fee_is_set_in_sent_invoke(map_contract): key = 2 value = 3 - prepared_call = map_contract.functions["put"].prepare(key, value, max_fee=100) - assert prepared_call.max_fee == 100 + max_fee_for_invoke = 248400000000 + prepared_call = map_contract.functions["put"].prepare( + key, value, max_fee=max_fee_for_invoke + ) + assert prepared_call.max_fee == max_fee_for_invoke invocation = await prepared_call.invoke() - assert invocation.invoke_transaction.max_fee == 100 + assert invocation.invoke_transaction.max_fee == max_fee_for_invoke - invocation = await map_contract.functions["put"].invoke(key, value, max_fee=200) - assert invocation.invoke_transaction.max_fee == 200 + invocation = await map_contract.functions["put"].invoke( + key, value, max_fee=max_fee_for_invoke + 100 + ) + assert invocation.invoke_transaction.max_fee == max_fee_for_invoke + 100 - prepared_call = map_contract.functions["put"].prepare(key, value, max_fee=300) - assert prepared_call.max_fee == 300 - invocation = await prepared_call.invoke(max_fee=400) - assert invocation.invoke_transaction.max_fee == 400 + prepared_call = map_contract.functions["put"].prepare( + key, value, max_fee=max_fee_for_invoke + 200 + ) + assert prepared_call.max_fee == max_fee_for_invoke + 200 + invocation = await prepared_call.invoke(max_fee=max_fee_for_invoke + 300) + assert invocation.invoke_transaction.max_fee == max_fee_for_invoke + 300 @pytest.mark.asyncio @@ -82,10 +89,13 @@ async def test_latest_max_fee_takes_precedence(map_contract): key = 2 value = 3 - prepared_function = map_contract.functions["put"].prepare(key, value, max_fee=20) - invocation = await prepared_function.invoke(max_fee=50) + max_fee = 248400000000 + prepared_function = map_contract.functions["put"].prepare( + key, value, max_fee=max_fee + ) + invocation = await prepared_function.invoke(max_fee=max_fee + 30) - assert invocation.invoke_transaction.max_fee == 50 + assert invocation.invoke_transaction.max_fee == max_fee + 30 @pytest.mark.asyncio @@ -134,14 +144,15 @@ async def test_wait_for_tx(client, map_contract): @pytest.mark.asyncio -async def test_wait_for_tx_throws_on_transaction_rejected(client, map_contract): +async def test_wait_for_tx_throws_on_transaction_reverted(gateway_client, map_contract): + client = gateway_client invoke = map_contract.functions["put"].prepare(key=0x1, value=0x1, max_fee=MAX_FEE) # modify selector so that transaction will get rejected invoke.selector = 0x0123 transaction = await invoke.invoke() - with pytest.raises(TransactionRejectedError) as err: + with pytest.raises(TransactionRevertedError) as err: await client.wait_for_tx(transaction.hash) if isinstance(client, GatewayClient): diff --git a/starknet_py/tests/e2e/core/fixtures.py b/starknet_py/tests/e2e/core/fixtures.py index bb57a8929..1cf50af32 100644 --- a/starknet_py/tests/e2e/core/fixtures.py +++ b/starknet_py/tests/e2e/core/fixtures.py @@ -14,7 +14,7 @@ from starknet_py.net.signer.stark_curve_signer import KeyPair from starknet_py.net.udc_deployer.deployer import Deployer from starknet_py.tests.e2e.fixtures.constants import ( - CONTRACTS_COMPILED_DIR, + CONTRACTS_COMPILED_V0_DIR, INTEGRATION_ACCOUNT_ADDRESS, INTEGRATION_ACCOUNT_PRIVATE_KEY, MAX_FEE, @@ -44,8 +44,9 @@ def core_pre_deployed_account(pytestconfig, core_gateway_client) -> Account: "0x53262B95AE54005C9BE3ECC743008BAB81C0D7DED641FFDC27F253E7E6D2872", ), "integration": ( - INTEGRATION_ACCOUNT_ADDRESS, - INTEGRATION_ACCOUNT_PRIVATE_KEY, + # because TESTNET and INTEGRATION constants are lambdas + INTEGRATION_ACCOUNT_ADDRESS(), + INTEGRATION_ACCOUNT_PRIVATE_KEY(), ), } @@ -60,7 +61,7 @@ def core_pre_deployed_account(pytestconfig, core_gateway_client) -> Account: async def declare_contract( file_name: str, account: Account ) -> DeclareTransactionResponse: - compiled_contract = read_contract(file_name, directory=CONTRACTS_COMPILED_DIR) + compiled_contract = read_contract(file_name, directory=CONTRACTS_COMPILED_V0_DIR) declare_tx = await account.sign_declare_transaction( compiled_contract, max_fee=MAX_FEE @@ -181,7 +182,7 @@ async def core_map_contract( abi = create_compiled_contract( compiled_contract=read_contract( - "map_compiled.json", directory=CONTRACTS_COMPILED_DIR + "map_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) ).abi 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 d9d1b4128..da86bceb8 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 @@ -1,6 +1,7 @@ # pylint: disable=unused-variable import pytest +from starknet_py.contract import Contract from starknet_py.hash.selector import get_selector_from_name from starknet_py.hash.storage import get_storage_var_address from starknet_py.net.client_models import Call @@ -185,3 +186,75 @@ async def test_get_events(full_node_client, contract_address): chunk_size=47, ) # docs-end: get_events + + +# TODO (#1179): fix that +@pytest.mark.xfail( + reason="Passing devnet client without implemented methods - test simply for a code example." +) +@pytest.mark.asyncio +async def test_trace_block_transactions(full_node_client): + # docs-start: trace_block_transactions + block_number = 800002 + block_transaction_traces = await full_node_client.trace_block_transactions( + block_number=block_number + ) + # docs-end: trace_block_transactions + + +# TODO (#1179): fix that +@pytest.mark.xfail( + reason="Passing devnet client without implemented methods - test simply for a code example." +) +@pytest.mark.asyncio +async def test_trace_transaction(full_node_client): + # docs-start: trace_transaction + transaction_hash = "0x123" + # docs-end: trace_transaction + transaction_hash = ( + "0x31e9adddefb28fab4d2ef9a6907e5805f5f793f5198618119a5347e6fc4af57" + ) + # docs-start: trace_transaction + transaction_trace = await full_node_client.trace_transaction( + tx_hash=transaction_hash + ) + # docs-end: trace_transaction + + +@pytest.mark.asyncio +async def test_simulate_transactions( + full_node_account, deployed_balance_contract, deploy_account_transaction +): + assert isinstance(deployed_balance_contract, Contract) + contract_address = deployed_balance_contract.address + second_transaction = deploy_account_transaction + # docs-start: simulate_transactions + call = Call( + to_addr=contract_address, + selector=get_selector_from_name("method_name"), + calldata=[0xCA11DA7A], + ) + first_transaction = await full_node_account.sign_invoke_transaction( + calls=call, max_fee=int(1e16) + ) + # docs-end: simulate_transactions + + call = Call( + to_addr=deployed_balance_contract.address, + selector=get_selector_from_name("increase_balance"), + calldata=[0x10], + ) + first_transaction = await full_node_account.sign_invoke_transaction( + calls=call, auto_estimate=True + ) + + # docs-start: simulate_transactions + # one transaction + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[first_transaction], block_number="latest" + ) + # or multiple + simulated_txs = await full_node_account.client.simulate_transactions( + transactions=[first_transaction, second_transaction], block_number="latest" + ) + # docs-end: simulate_transactions 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 f99c6714a..1430389f1 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 @@ -39,10 +39,10 @@ async def test_get_block(gateway_client): @pytest.mark.asyncio async def test_get_block_traces(gateway_client): # docs-start: get_block_traces - block_traces = await gateway_client.get_block_traces(block_number="latest") - block_traces = await gateway_client.get_block_traces(block_number=0) + block_traces = await gateway_client.trace_block_transactions(block_number="latest") + block_traces = await gateway_client.trace_block_transactions(block_number=0) # or - block_traces = await gateway_client.get_block_traces(block_hash="0x0") + block_traces = await gateway_client.trace_block_transactions(block_hash="0x0") # docs-end: get_block_traces diff --git a/starknet_py/tests/e2e/docs/guide/test_account_sign_without_execute.py b/starknet_py/tests/e2e/docs/guide/test_account_sign_without_execute.py index 085f93cc3..482825c56 100644 --- a/starknet_py/tests/e2e/docs/guide/test_account_sign_without_execute.py +++ b/starknet_py/tests/e2e/docs/guide/test_account_sign_without_execute.py @@ -1,11 +1,13 @@ import pytest +from starknet_py.net.account.account import Account from starknet_py.net.models.transaction import Declare, DeployAccount, Invoke @pytest.mark.asyncio async def test_account_sign_without_execute(account, map_compiled_contract): # pylint: disable=import-outside-toplevel + assert isinstance(account, Account) address = selector = class_hash = salt = 0x1 calldata = [] compiled_contract = map_compiled_contract @@ -17,6 +19,10 @@ async def test_account_sign_without_execute(account, map_compiled_contract): # Create a signed Invoke transaction call = Call(to_addr=address, selector=selector, calldata=calldata) invoke_transaction = await account.sign_invoke_transaction(call, max_fee=max_fee) + # Or if you're using Cairo1 account with new calldata encoding + invoke_transaction = await account.sign_invoke_transaction( + call, max_fee=max_fee, cairo_version=1 + ) # Create a signed Declare transaction declare_transaction = await account.sign_declare_transaction( diff --git a/starknet_py/tests/e2e/docs/guide/test_serializing.py b/starknet_py/tests/e2e/docs/guide/test_serializing.py index a3145c5f0..dc3035735 100644 --- a/starknet_py/tests/e2e/docs/guide/test_serializing.py +++ b/starknet_py/tests/e2e/docs/guide/test_serializing.py @@ -1,7 +1,7 @@ # pylint: disable=import-outside-toplevel, pointless-string-statement import json -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_DIR +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V0_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract @@ -20,7 +20,9 @@ def test_short_strings(): def test_abi_parsing(): - raw_abi_string = read_contract("erc20_abi.json", directory=CONTRACTS_COMPILED_DIR) + raw_abi_string = read_contract( + "erc20_abi.json", directory=CONTRACTS_COMPILED_V0_DIR + ) # docs-serializer: start from starknet_py.abi import AbiParser diff --git a/starknet_py/tests/e2e/fixtures/accounts.py b/starknet_py/tests/e2e/fixtures/accounts.py index 7b56c6307..3fdac9521 100644 --- a/starknet_py/tests/e2e/fixtures/accounts.py +++ b/starknet_py/tests/e2e/fixtures/accounts.py @@ -105,7 +105,10 @@ async def address_and_private_key( account_with_validate_deploy_class_hash, network, ) - return account_details[net] + + # because TESTNET and INTEGRATION constants are lambdas + exact_account_details = account_details[net] + return exact_account_details[0](), exact_account_details[1]() @pytest.fixture(scope="package") @@ -218,6 +221,11 @@ def pre_deployed_account_with_validate_deploy( net = pytestconfig.getoption("--net") address, private_key = address_and_priv_key[net] + if net != "devnet": + # because TESTNET and INTEGRATION constants are lambdas + address = address() + private_key = private_key() + return Account( address=address, client=GatewayClient(net=network), diff --git a/starknet_py/tests/e2e/fixtures/constants.py b/starknet_py/tests/e2e/fixtures/constants.py index eeed73bbd..40b959ff7 100644 --- a/starknet_py/tests/e2e/fixtures/constants.py +++ b/starknet_py/tests/e2e/fixtures/constants.py @@ -1,24 +1,41 @@ import os from pathlib import Path +from dotenv import load_dotenv + +load_dotenv(dotenv_path=Path(os.path.dirname(__file__)) / "../test-variables.env") + + +def _get_env_or_throw(env_name: str) -> str: + env = os.getenv(key=env_name) + if env is None: + raise ValueError( + f"{env_name} environmental variable is not set. " + f"Update it manually or set it in `starknet_py/tests/e2e/test-variables.env` file. " + f"More info here: https://starknetpy.readthedocs.io/en/latest/development.html#setup" + ) + return env + + +def _get_env_lambda(env_name): + return lambda: _get_env_or_throw(env_name) + + # -------------------------------- TESTNET ------------------------------------- -TESTNET_ACCOUNT_PRIVATE_KEY = ( - "0x61910356c5adf66efb65ec3df5d07a6e5e6e7c8b59f15a13eda7a34c8d1ecc4" -) -TESTNET_ACCOUNT_ADDRESS = ( - "0x59083382aadec25d7616a7f48942d72d469b0ac581f2e935ec26b68f66bd600" -) +TESTNET_ACCOUNT_ADDRESS = _get_env_lambda("TESTNET_ACCOUNT_ADDRESS") + +TESTNET_ACCOUNT_PRIVATE_KEY = _get_env_lambda("TESTNET_ACCOUNT_PRIVATE_KEY") + +TESTNET_RPC_URL = _get_env_lambda("TESTNET_RPC_URL") # -------------------------------- INTEGRATION --------------------------------- -INTEGRATION_ACCOUNT_PRIVATE_KEY = "0x1234" +INTEGRATION_ACCOUNT_PRIVATE_KEY = _get_env_lambda("INTEGRATION_ACCOUNT_PRIVATE_KEY") -INTEGRATION_ACCOUNT_ADDRESS = ( - "0x4321647559947e9109acecb329e57594bcc3981a6118bbbfeaa9f698874bcd5" -) +INTEGRATION_ACCOUNT_ADDRESS = _get_env_lambda("INTEGRATION_ACCOUNT_ADDRESS") -INTEGRATION_NODE_URL = "http://188.34.188.184:9545/rpc/v0.4" +INTEGRATION_RPC_URL = _get_env_lambda("INTEGRATION_RPC_URL") INTEGRATION_GATEWAY_URL = "https://external.integration.starknet.io" @@ -37,14 +54,14 @@ ) DEVNET_PRE_DEPLOYED_ACCOUNT_PRIVATE_KEY = "0xcd613e30d8f16adf91b7584a2265b1f5" -MAX_FEE = int(1e20) +MAX_FEE = int(1e16) MOCK_DIR = Path(os.path.dirname(__file__)) / "../mock" TYPED_DATA_DIR = MOCK_DIR / "typed_data" CONTRACTS_DIR = MOCK_DIR / "contracts" CONTRACTS_V1_DIR = MOCK_DIR / "contracts_v1" -CONTRACTS_COMPILED_DIR = MOCK_DIR / "contracts_compiled" +CONTRACTS_COMPILED_V0_DIR = MOCK_DIR / "contracts_compiled" CONTRACTS_COMPILED_V1_DIR = MOCK_DIR / "contracts_compiled_v1" CONTRACTS_COMPILED_V2_DIR = MOCK_DIR / "contracts_compiled_v2" -CONTRACTS_PRECOMPILED_DIR = CONTRACTS_COMPILED_DIR / "precompiled" +CONTRACTS_PRECOMPILED_DIR = CONTRACTS_COMPILED_V0_DIR / "precompiled" ACCOUNT_DIR = MOCK_DIR / "account" diff --git a/starknet_py/tests/e2e/fixtures/contracts.py b/starknet_py/tests/e2e/fixtures/contracts.py index ed3f47078..621442db1 100644 --- a/starknet_py/tests/e2e/fixtures/contracts.py +++ b/starknet_py/tests/e2e/fixtures/contracts.py @@ -16,7 +16,7 @@ from starknet_py.net.account.base_account import BaseAccount from starknet_py.net.udc_deployer.deployer import Deployer from starknet_py.tests.e2e.fixtures.constants import ( - CONTRACTS_COMPILED_DIR, + CONTRACTS_COMPILED_V0_DIR, CONTRACTS_DIR, MAX_FEE, ) @@ -36,7 +36,7 @@ def map_compiled_contract() -> str: """ Returns compiled map contract. """ - return read_contract("map_compiled.json", directory=CONTRACTS_COMPILED_DIR) + return read_contract("map_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR) @pytest.fixture(scope="package") @@ -46,6 +46,7 @@ def sierra_minimal_compiled_contract_and_class_hash() -> Tuple[str, int]: """ compiled_contract = read_contract("minimal_contract_compiled.json") compiled_contract_casm = read_contract("minimal_contract_compiled.casm") + return ( compiled_contract, compute_casm_class_hash(create_casm_class(compiled_contract_casm)), @@ -58,7 +59,7 @@ def simple_storage_with_event_compiled_contract() -> str: Returns compiled simple storage contract that emits an event. """ return read_contract( - "simple_storage_with_event_compiled.json", directory=CONTRACTS_COMPILED_DIR + "simple_storage_with_event_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) @@ -67,7 +68,7 @@ def erc20_compiled_contract() -> str: """ Returns compiled erc20 contract. """ - return read_contract("erc20_compiled.json", directory=CONTRACTS_COMPILED_DIR) + return read_contract("erc20_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR) @pytest.fixture(scope="package") @@ -76,7 +77,7 @@ def constructor_with_arguments_compiled_contract() -> str: Returns compiled constructor_with_arguments contract. """ return read_contract( - "constructor_with_arguments_compiled.json", directory=CONTRACTS_COMPILED_DIR + "constructor_with_arguments_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) @@ -86,7 +87,8 @@ def constructor_without_arguments_compiled_contract() -> str: Returns compiled constructor_without_arguments contract. """ return read_contract( - "constructor_without_arguments_compiled.json", directory=CONTRACTS_COMPILED_DIR + "constructor_without_arguments_compiled.json", + directory=CONTRACTS_COMPILED_V0_DIR, ) @@ -251,7 +253,7 @@ def fixture_balance_contract() -> str: """ Returns compiled code of the balance.cairo contract. """ - return read_contract("balance_compiled.json", directory=CONTRACTS_COMPILED_DIR) + return read_contract("balance_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR) async def declare_account(account: BaseAccount, compiled_account_contract: str) -> int: @@ -274,7 +276,8 @@ async def account_with_validate_deploy_class_hash( pre_deployed_account_with_validate_deploy: BaseAccount, ) -> int: compiled_contract = read_contract( - "account_with_validate_deploy_compiled.json", directory=CONTRACTS_COMPILED_DIR + "account_with_validate_deploy_compiled.json", + directory=CONTRACTS_COMPILED_V0_DIR, ) return await declare_account( pre_deployed_account_with_validate_deploy, compiled_contract @@ -341,7 +344,8 @@ def constructor_with_arguments_abi() -> List: """ compiled_contract = create_compiled_contract( compiled_contract=read_contract( - "constructor_with_arguments_compiled.json", directory=CONTRACTS_COMPILED_DIR + "constructor_with_arguments_compiled.json", + directory=CONTRACTS_COMPILED_V0_DIR, ) ) assert compiled_contract.abi is not None @@ -354,7 +358,7 @@ def constructor_with_arguments_compiled() -> str: Returns a compiled constructor_with_arguments.cairo. """ return read_contract( - "constructor_with_arguments_compiled.json", directory=CONTRACTS_COMPILED_DIR + "constructor_with_arguments_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) diff --git a/starknet_py/tests/e2e/fixtures/devnet.py b/starknet_py/tests/e2e/fixtures/devnet.py index 1b665d620..63bf9b597 100644 --- a/starknet_py/tests/e2e/fixtures/devnet.py +++ b/starknet_py/tests/e2e/fixtures/devnet.py @@ -2,6 +2,7 @@ import socket import subprocess import time +import warnings from contextlib import closing from pathlib import Path from typing import Generator, List @@ -29,6 +30,10 @@ def get_compiler_manifest() -> List[str]: return ["--cairo-compiler-manifest", manifest] except (IndexError, FileNotFoundError): + warnings.warn( + "File 'manifest-path' was not found in directory 'starknet_py/tests/e2e'. More info " + "here: https://starknetpy.readthedocs.io/en/latest/development.html#setup" + ) return [] diff --git a/starknet_py/tests/e2e/fixtures/misc.py b/starknet_py/tests/e2e/fixtures/misc.py index 7c855f25b..0d8270974 100644 --- a/starknet_py/tests/e2e/fixtures/misc.py +++ b/starknet_py/tests/e2e/fixtures/misc.py @@ -9,7 +9,7 @@ from starknet_py.net.models.typed_data import TypedData from starknet_py.tests.e2e.fixtures.constants import ( - CONTRACTS_COMPILED_DIR, + CONTRACTS_COMPILED_V0_DIR, CONTRACTS_COMPILED_V1_DIR, CONTRACTS_COMPILED_V2_DIR, TYPED_DATA_DIR, @@ -104,7 +104,7 @@ def read_contract(file_name: str, *, directory: Optional[Path] = None) -> str: Return contents of file_name from directory. """ if directory is None: - directory = CONTRACTS_COMPILED_DIR + directory = CONTRACTS_COMPILED_V0_DIR if "--contract_dir=v1" in sys.argv: directory = CONTRACTS_COMPILED_V1_DIR if "--contract_dir=v2" in sys.argv: diff --git a/starknet_py/tests/e2e/fixtures/proxy.py b/starknet_py/tests/e2e/fixtures/proxy.py index c5759039c..ac9c9af1a 100644 --- a/starknet_py/tests/e2e/fixtures/proxy.py +++ b/starknet_py/tests/e2e/fixtures/proxy.py @@ -7,7 +7,7 @@ from starknet_py.hash.selector import get_selector_from_name from starknet_py.net.account.account import Account from starknet_py.tests.e2e.fixtures.constants import ( - CONTRACTS_COMPILED_DIR, + CONTRACTS_COMPILED_V0_DIR, CONTRACTS_PRECOMPILED_DIR, MAX_FEE, ) @@ -22,7 +22,7 @@ def compiled_proxy(request) -> str: """ Returns source code of compiled proxy contract. """ - return read_contract(request.param, directory=CONTRACTS_COMPILED_DIR) + return read_contract(request.param, directory=CONTRACTS_COMPILED_V0_DIR) @pytest.fixture(scope="session") @@ -31,7 +31,7 @@ def custom_proxy() -> str: Returns compiled source code of a custom proxy. """ return read_contract( - "oz_proxy_custom_compiled.json", directory=CONTRACTS_COMPILED_DIR + "oz_proxy_custom_compiled.json", directory=CONTRACTS_COMPILED_V0_DIR ) @@ -108,10 +108,10 @@ async def deploy_proxy_to_contract( Declares a contract and deploys a proxy pointing to that contract. """ compiled_proxy = read_contract( - compiled_proxy_name, directory=CONTRACTS_COMPILED_DIR + compiled_proxy_name, directory=CONTRACTS_COMPILED_V0_DIR ) compiled_contract = read_contract( - compiled_contract_name, directory=CONTRACTS_COMPILED_DIR + compiled_contract_name, directory=CONTRACTS_COMPILED_V0_DIR ) declare_tx = await gateway_account.sign_declare_transaction( diff --git a/starknet_py/tests/e2e/mock/compile_contracts_v1.sh b/starknet_py/tests/e2e/mock/compile_contracts_v1.sh index 388c2d1fe..bfcc40c68 100644 --- a/starknet_py/tests/e2e/mock/compile_contracts_v1.sh +++ b/starknet_py/tests/e2e/mock/compile_contracts_v1.sh @@ -6,6 +6,11 @@ CONTRACTS_COMPILED_DIRECTORY="$MOCK_DIRECTORY"/contracts_compiled_v1 # get path to Cargo.toml MANIFEST_PATH=`cat starknet_py/tests/e2e/manifest-path` +if [ -z "$MANIFEST_PATH" ] +then + echo "File 'manifest-path' was not found in directory 'starknet_py/tests/e2e'. More info here: https://starknetpy.readthedocs.io/en/latest/development.html#setup" + exit 1 +fi # delete all artifacts except precompiled ones mkdir -p $CONTRACTS_COMPILED_DIRECTORY diff --git a/starknet_py/tests/e2e/mock/compile_contracts_v2.sh b/starknet_py/tests/e2e/mock/compile_contracts_v2.sh index 9480b66e1..57b1e99d8 100644 --- a/starknet_py/tests/e2e/mock/compile_contracts_v2.sh +++ b/starknet_py/tests/e2e/mock/compile_contracts_v2.sh @@ -6,6 +6,11 @@ CONTRACTS_COMPILED_DIRECTORY="$MOCK_DIRECTORY"/contracts_compiled_v2 # get path to Cargo.toml MANIFEST_PATH=`cat starknet_py/tests/e2e/manifest-path` +if [ -z "$MANIFEST_PATH" ] +then + echo "File 'manifest-path' was not found in directory 'starknet_py/tests/e2e'. More info here: https://starknetpy.readthedocs.io/en/latest/development.html#setup" + exit 1 +fi # delete all artifacts except precompiled ones mkdir -p $CONTRACTS_COMPILED_DIRECTORY diff --git a/starknet_py/tests/e2e/test-variables.env.template b/starknet_py/tests/e2e/test-variables.env.template new file mode 100644 index 000000000..16765f300 --- /dev/null +++ b/starknet_py/tests/e2e/test-variables.env.template @@ -0,0 +1,6 @@ +INTEGRATION_RPC_URL=URL +TESTNET_RPC_URL=URL +INTEGRATION_ACCOUNT_PRIVATE_KEY=0x1234567890 +INTEGRATION_ACCOUNT_ADDRESS=0x9876543210 +TESTNET_ACCOUNT_PRIVATE_KEY=0xabcdef +TESTNET_ACCOUNT_ADDRESS=0xfedcba diff --git a/starknet_py/tests/e2e/integration_tests/client_test.py b/starknet_py/tests/e2e/tests_on_networks/client_test.py similarity index 100% rename from starknet_py/tests/e2e/integration_tests/client_test.py rename to starknet_py/tests/e2e/tests_on_networks/client_test.py diff --git a/starknet_py/tests/e2e/integration_tests/fixtures.py b/starknet_py/tests/e2e/tests_on_networks/fixtures.py similarity index 72% rename from starknet_py/tests/e2e/integration_tests/fixtures.py rename to starknet_py/tests/e2e/tests_on_networks/fixtures.py index c57a5fe8a..acde9f3dd 100644 --- a/starknet_py/tests/e2e/integration_tests/fixtures.py +++ b/starknet_py/tests/e2e/tests_on_networks/fixtures.py @@ -13,7 +13,8 @@ INTEGRATION_ACCOUNT_ADDRESS, INTEGRATION_ACCOUNT_PRIVATE_KEY, INTEGRATION_GATEWAY_URL, - INTEGRATION_NODE_URL, + INTEGRATION_RPC_URL, + TESTNET_RPC_URL, ) @@ -30,7 +31,8 @@ def full_node_client_integration() -> FullNodeClient: """ A fixture returning a FullNodeClient with our integration network node URL. """ - return FullNodeClient(node_url=INTEGRATION_NODE_URL) + # because TESTNET and INTEGRATION constants are lambdas + return FullNodeClient(node_url=INTEGRATION_RPC_URL()) def net_to_integration_clients() -> List[str]: @@ -58,9 +60,11 @@ def gateway_account_integration(gateway_client_integration) -> Account: A fixture returning an Account with GatewayClient. """ return Account( - address=INTEGRATION_ACCOUNT_ADDRESS, + # because TESTNET and INTEGRATION constants are lambdas + address=INTEGRATION_ACCOUNT_ADDRESS(), client=gateway_client_integration, - key_pair=KeyPair.from_private_key(int(INTEGRATION_ACCOUNT_PRIVATE_KEY, 0)), + # because TESTNET and INTEGRATION constants are lambdas + key_pair=KeyPair.from_private_key(int(INTEGRATION_ACCOUNT_PRIVATE_KEY(), 0)), chain=StarknetChainId.TESTNET, ) @@ -71,9 +75,11 @@ def full_node_account_integration(full_node_client_integration) -> Account: A fixture returning an Account with FullNodeClient. """ return Account( - address=INTEGRATION_ACCOUNT_ADDRESS, + # because TESTNET and INTEGRATION constants are lambdas + address=INTEGRATION_ACCOUNT_ADDRESS(), client=full_node_client_integration, - key_pair=KeyPair.from_private_key(int(INTEGRATION_ACCOUNT_PRIVATE_KEY, 0)), + # because TESTNET and INTEGRATION constants are lambdas + key_pair=KeyPair.from_private_key(int(INTEGRATION_ACCOUNT_PRIVATE_KEY(), 0)), chain=StarknetChainId.TESTNET, ) @@ -96,3 +102,20 @@ def account_integration(request) -> Account: FullNodeClient). """ return request.getfixturevalue(request.param) + + +@pytest.fixture(scope="package") +def gateway_client_testnet() -> GatewayClient: + """ + A fixture returning a GatewayClient with a public integration network URL. + """ + return GatewayClient(net="testnet") + + +@pytest.fixture(scope="package") +def full_node_client_testnet() -> FullNodeClient: + """ + A fixture returning a FullNodeClient with our integration network node URL. + """ + # because TESTNET and INTEGRATION constants are lambdas + return FullNodeClient(node_url=TESTNET_RPC_URL()) 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 new file mode 100644 index 000000000..16936a1a7 --- /dev/null +++ b/starknet_py/tests/e2e/tests_on_networks/trace_api_test.py @@ -0,0 +1,72 @@ +import pytest + +from starknet_py.net.client_models import ( + DeclareTransaction, + DeclareTransactionTrace, + DeployAccountTransaction, + DeployAccountTransactionTrace, + InvokeTransaction, + InvokeTransactionTrace, + L1HandlerTransaction, + L1HandlerTransactionTrace, + RevertedFunctionInvocation, + Transaction, + TransactionTrace, +) + +# TODO (#1179): move those tests to full_node_test.py + + +@pytest.mark.asyncio +async def test_trace_transaction(full_node_client_testnet): + tx_to_trace: dict[type[Transaction], type[TransactionTrace]] = { + InvokeTransaction: InvokeTransactionTrace, + DeclareTransaction: DeclareTransactionTrace, + DeployAccountTransaction: DeployAccountTransactionTrace, + L1HandlerTransaction: L1HandlerTransactionTrace, + } + block = await full_node_client_testnet.get_block(block_number=600000) + + for tx in block.transactions: + trace = await full_node_client_testnet.trace_transaction(tx_hash=tx.hash) + tx = await full_node_client_testnet.get_transaction(tx_hash=tx.hash) + assert tx_to_trace[type(tx)] == type(trace) + + +@pytest.mark.asyncio +async def test_trace_transaction_reverted(full_node_client_testnet): + tx_hash = "0x604371f9414d26ad9e745301596de1d1219c1045f00c68d3be9bd195eb18632" + trace = await full_node_client_testnet.trace_transaction(tx_hash=tx_hash) + + assert isinstance(trace.execute_invocation, RevertedFunctionInvocation) + + +@pytest.mark.asyncio +async def test_get_block_traces(full_node_client_testnet): + # 800002 because I guess sometimes juno doesn't return valid transactions/parses input wrong + block_number = 800006 + block_transaction_traces = await full_node_client_testnet.trace_block_transactions( + block_number=block_number + ) + 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)): + 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" + ) diff --git a/starknet_py/tests/e2e/utils.py b/starknet_py/tests/e2e/utils.py index 0e3263294..8200e8c82 100644 --- a/starknet_py/tests/e2e/utils.py +++ b/starknet_py/tests/e2e/utils.py @@ -39,7 +39,7 @@ async def get_deploy_account_details( ) res = await fee_contract.functions["transfer"].invoke( - recipient=address, amount=int(1e17), max_fee=MAX_FEE + recipient=address, amount=int(1e19), max_fee=MAX_FEE ) await res.wait_for_acceptance() @@ -76,7 +76,7 @@ async def get_deploy_account_transaction( class_hash=class_hash, contract_address_salt=salt, constructor_calldata=[key_pair.public_key], - max_fee=MAX_FEE, + max_fee=int(1e16), )