From 642a62f9a5fbb64773405cb9e545c7e3d7faa372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 09:35:15 +0200 Subject: [PATCH 01/17] Bump coverage from 7.2.6 to 7.2.7 (#1049) Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.6 to 7.2.7. - [Release notes](https://github.com/nedbat/coveragepy/releases) - [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst) - [Commits](https://github.com/nedbat/coveragepy/compare/7.2.6...7.2.7) --- updated-dependencies: - dependency-name: coverage dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 120 ++++++++++++++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/poetry.lock b/poetry.lock index 65d6e7e39..9f30541ba 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.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" @@ -574,63 +574,72 @@ files = [ [[package]] name = "coverage" -version = "7.2.6" +version = "7.2.7" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "coverage-7.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:496b86f1fc9c81a1cd53d8842ef712e950a4611bba0c42d33366a7b91ba969ec"}, - {file = "coverage-7.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fbe6e8c0a9a7193ba10ee52977d4d5e7652957c1f56ccefed0701db8801a2a3b"}, - {file = "coverage-7.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d06b721c2550c01a60e5d3093f417168658fb454e5dfd9a23570e9bffe39a1"}, - {file = "coverage-7.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77a04b84d01f0e12c66f16e69e92616442dc675bbe51b90bfb074b1e5d1c7fbd"}, - {file = "coverage-7.2.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35db06450272473eab4449e9c2ad9bc6a0a68dab8e81a0eae6b50d9c2838767e"}, - {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6727a0d929ff0028b1ed8b3e7f8701670b1d7032f219110b55476bb60c390bfb"}, - {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aac1d5fdc5378f6bac2c0c7ebe7635a6809f5b4376f6cf5d43243c1917a67087"}, - {file = "coverage-7.2.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1c9e4a5eb1bbc3675ee57bc31f8eea4cd7fb0cbcbe4912cf1cb2bf3b754f4a80"}, - {file = "coverage-7.2.6-cp310-cp310-win32.whl", hash = "sha256:71f739f97f5f80627f1fee2331e63261355fd1e9a9cce0016394b6707ac3f4ec"}, - {file = "coverage-7.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:fde5c7a9d9864d3e07992f66767a9817f24324f354caa3d8129735a3dc74f126"}, - {file = "coverage-7.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc7b667f8654376e9353dd93e55e12ce2a59fb6d8e29fce40de682273425e044"}, - {file = "coverage-7.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:697f4742aa3f26c107ddcb2b1784a74fe40180014edbd9adaa574eac0529914c"}, - {file = "coverage-7.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:541280dde49ce74a4262c5e395b48ea1207e78454788887118c421cb4ffbfcac"}, - {file = "coverage-7.2.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7f1a8328eeec34c54f1d5968a708b50fc38d31e62ca8b0560e84a968fbf9a9"}, - {file = "coverage-7.2.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbd58eb5a2371bf160590f4262109f66b6043b0b991930693134cb617bc0169"}, - {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae82c5f168d2a39a5d69a12a69d4dc23837a43cf2ca99be60dfe59996ea6b113"}, - {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f5440cdaf3099e7ab17a5a7065aed59aff8c8b079597b61c1f8be6f32fe60636"}, - {file = "coverage-7.2.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a6f03f87fea579d55e0b690d28f5042ec1368650466520fbc400e7aeaf09e995"}, - {file = "coverage-7.2.6-cp311-cp311-win32.whl", hash = "sha256:dc4d5187ef4d53e0d4c8eaf530233685667844c5fb0b855fea71ae659017854b"}, - {file = "coverage-7.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:c93d52c3dc7b9c65e39473704988602300e3cc1bad08b5ab5b03ca98bbbc68c1"}, - {file = "coverage-7.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42c692b55a647a832025a4c048007034fe77b162b566ad537ce65ad824b12a84"}, - {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7786b2fa7809bf835f830779ad285215a04da76293164bb6745796873f0942d"}, - {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25bad4196104761bc26b1dae9b57383826542ec689ff0042f7f4f4dd7a815cba"}, - {file = "coverage-7.2.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2692306d3d4cb32d2cceed1e47cebd6b1d2565c993d6d2eda8e6e6adf53301e6"}, - {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:392154d09bd4473b9d11351ab5d63391f3d5d24d752f27b3be7498b0ee2b5226"}, - {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fa079995432037b5e2ef5ddbb270bcd2ded9f52b8e191a5de11fe59a00ea30d8"}, - {file = "coverage-7.2.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d712cefff15c712329113b01088ba71bbcef0f7ea58478ca0bbec63a824844cb"}, - {file = "coverage-7.2.6-cp37-cp37m-win32.whl", hash = "sha256:004948e296149644d208964300cb3d98affc5211e9e490e9979af4030b0d6473"}, - {file = "coverage-7.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:c1d7a31603c3483ac49c1726723b0934f88f2c011c660e6471e7bd735c2fa110"}, - {file = "coverage-7.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3436927d1794fa6763b89b60c896f9e3bd53212001026ebc9080d23f0c2733c1"}, - {file = "coverage-7.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44c9b9f1a245f3d0d202b1a8fa666a80b5ecbe4ad5d0859c0fb16a52d9763224"}, - {file = "coverage-7.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e3783a286d5a93a2921396d50ce45a909aa8f13eee964465012f110f0cbb611"}, - {file = "coverage-7.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cff6980fe7100242170092bb40d2b1cdad79502cd532fd26b12a2b8a5f9aee0"}, - {file = "coverage-7.2.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c534431153caffc7c495c3eddf7e6a6033e7f81d78385b4e41611b51e8870446"}, - {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3062fd5c62df988cea9f2972c593f77fed1182bfddc5a3b12b1e606cb7aba99e"}, - {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6284a2005e4f8061c58c814b1600ad0074ccb0289fe61ea709655c5969877b70"}, - {file = "coverage-7.2.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:97729e6828643f168a2a3f07848e1b1b94a366b13a9f5aba5484c2215724edc8"}, - {file = "coverage-7.2.6-cp38-cp38-win32.whl", hash = "sha256:dc11b42fa61ff1e788dd095726a0aed6aad9c03d5c5984b54cb9e1e67b276aa5"}, - {file = "coverage-7.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:cbcc874f454ee51f158afd604a315f30c0e31dff1d5d5bf499fc529229d964dd"}, - {file = "coverage-7.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d3cacc6a665221108ecdf90517a8028d07a2783df3417d12dcfef1c517e67478"}, - {file = "coverage-7.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:272ab31228a9df857ab5df5d67936d8861464dc89c5d3fab35132626e9369379"}, - {file = "coverage-7.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a8723ccec4e564d4b9a79923246f7b9a8de4ec55fa03ec4ec804459dade3c4f"}, - {file = "coverage-7.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5906f6a84b47f995cd1bf0aca1c72d591c55ee955f98074e93660d64dfc66eb9"}, - {file = "coverage-7.2.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c139b7ab3f0b15f9aad0a3fedef5a1f8c0b2bdc291d88639ca2c97d3682416"}, - {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a5ffd45c6b93c23a8507e2f436983015c6457aa832496b6a095505ca2f63e8f1"}, - {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4f3c7c19581d471af0e9cb49d928172cd8492cd78a2b7a4e82345d33662929bb"}, - {file = "coverage-7.2.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e8c0e79820cdd67978e1120983786422d279e07a381dbf89d03bbb23ec670a6"}, - {file = "coverage-7.2.6-cp39-cp39-win32.whl", hash = "sha256:13cde6bb0e58fb67d09e2f373de3899d1d1e866c5a9ff05d93615f2f54fbd2bb"}, - {file = "coverage-7.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:6b9f64526286255735847aed0221b189486e0b9ed943446936e41b7e44b08783"}, - {file = "coverage-7.2.6-pp37.pp38.pp39-none-any.whl", hash = "sha256:6babcbf1e66e46052442f10833cfc4a0d3554d8276aa37af8531a83ed3c1a01d"}, - {file = "coverage-7.2.6.tar.gz", hash = "sha256:2025f913f2edb0272ef15d00b1f335ff8908c921c8eb2013536fcaf61f5a683d"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, ] [package.dependencies] @@ -2697,7 +2706,8 @@ 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_12_6_arm64.whl", hash = "sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5"}, + {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-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"}, @@ -3501,7 +3511,7 @@ 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" From 29bfaf855242f8a77469e9efefd1ea003daf11db Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Wed, 7 Jun 2023 13:10:59 +0200 Subject: [PATCH 02/17] Use stale action (#1051) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add stale action * Update .github/workflows/stale.yml Co-authored-by: Tomasz Miśkowicz * Update stale messages --------- Co-authored-by: Tomasz Miśkowicz --- .github/stale.yml | 17 ----------------- .github/workflows/stale.yml | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 17 deletions(-) delete mode 100644 .github/stale.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 01cb21d43..000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Number of days of inactivity before an issue becomes stale -daysUntilStale: 30 -# Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 -# Issues with these labels will never be considered stale -exemptLabels: - - pinned - - security -# Label to use when marking an issue as stale -staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..022f5fb56 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,24 @@ +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v8 + with: + exempt-all-pr-assignees: true + exempt-pr-labels: 'pinned' + stale-issue-message: 'This issue is stale because it has not received any activity in the last 30 days. Remove stale label or add a comment, otherwise it will be closed in 5 days.' + close-issue-message: 'This issue was closed because it has been stale for 5 days with no activity.' + days-before-issue-stale: 30 + days-before-issue-close: 5 + stale-pr-message: 'This PR is stale because it has not received any activity in the last 30 days. Remove stale label or add a comment, otherwise it will be closed in 5 days.' + close-pr-message: 'This PR was closed because it has been stale for 5 days with no activity.' + days-before-pr-stale: 30 + days-before-pr-close: 5 From 43b437f2752af94bc2173266c20c35371b4331f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:53:55 +0200 Subject: [PATCH 03/17] Bump pytest from 7.2.2 to 7.3.2 (#1058) Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.2.2 to 7.3.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.2.2...7.3.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 153 +++------------------------------------------------- 1 file changed, 6 insertions(+), 147 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9f30541ba..356b195bf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.4" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -113,7 +112,6 @@ 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 = [ @@ -128,7 +126,6 @@ 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 = [ @@ -140,7 +137,6 @@ 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 = [ @@ -162,7 +158,6 @@ 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 = [ @@ -178,7 +173,6 @@ 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 = [ @@ -196,7 +190,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] name = "astroid" version = "2.15.4" description = "An abstract syntax tree for Python with inference support." -category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -216,7 +209,6 @@ wrapt = [ name = "async-timeout" version = "4.0.2" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -228,7 +220,6 @@ files = [ name = "attrs" version = "22.2.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -247,7 +238,6 @@ 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 = [ @@ -262,7 +252,6 @@ Sphinx = ">=2.2,<8.0" name = "babel" version = "2.11.0" description = "Internationalization utilities" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -277,7 +266,6 @@ pytz = ">=2015.7" name = "beautifulsoup4" version = "4.11.2" description = "Screen-scraping library" -category = "main" optional = true python-versions = ">=3.6.0" files = [ @@ -296,7 +284,6 @@ lxml = ["lxml"] name = "bitarray" version = "2.7.3" description = "efficient arrays of booleans -- C extension" -category = "dev" optional = false python-versions = "*" files = [ @@ -393,7 +380,6 @@ files = [ name = "black" version = "23.3.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -443,7 +429,6 @@ 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 = [ @@ -464,7 +449,6 @@ redis = ["redis (>=2.10.5)"] name = "cachetools" version = "5.3.0" description = "Extensible memoizing collections and decorators" -category = "dev" optional = false python-versions = "~=3.7" files = [ @@ -476,7 +460,6 @@ files = [ name = "cairo-lang" version = "0.11.2" description = "Compiler and runner for the Cairo language" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -510,7 +493,6 @@ 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 = [ @@ -522,7 +504,6 @@ 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 = [ @@ -537,7 +518,6 @@ unicode-backport = ["unicodedata2"] name = "click" version = "8.1.3" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -552,7 +532,6 @@ 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 = [ @@ -564,7 +543,6 @@ 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 = [ @@ -576,7 +554,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -652,7 +629,6 @@ 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 = [ @@ -719,7 +695,6 @@ 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 = [ @@ -735,7 +710,6 @@ testing = ["cssselect", "flake8 (<5)", "importlib-resources", "jaraco.test (>=5. name = "cytoolz" version = "0.12.0" description = "Cython implementation of Toolz: High performance functional utilities" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -835,7 +809,6 @@ 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 = [ @@ -851,7 +824,6 @@ 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 = [ @@ -866,7 +838,6 @@ 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 = [ @@ -878,7 +849,6 @@ files = [ name = "domdf-python-tools" version = "3.5.0" description = "Helpful functions for Python 🐍 🛠️" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -899,7 +869,6 @@ 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 = [ @@ -918,7 +887,6 @@ gmpy2 = ["gmpy2"] name = "enum-tools" version = "0.9.0.post1" description = "Tools to expand Python's enum module." -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -940,7 +908,6 @@ sphinx = ["sphinx (>=3.2.0)", "sphinx-toolbox (>=2.16.0)"] name = "eth-abi" version = "4.0.0" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" -category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -964,7 +931,6 @@ tools = ["hypothesis (>=4.18.2,<5.0.0)"] name = "eth-account" version = "0.8.0" description = "eth-account: Sign Ethereum transactions and messages with local private keys" -category = "dev" optional = false python-versions = ">=3.6, <4" files = [ @@ -992,7 +958,6 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-x name = "eth-hash" version = "0.5.1" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -1015,7 +980,6 @@ test = ["pytest (>=6.2.5,<7)", "pytest-xdist (>=2.4.0,<3)", "tox (>=3.14.6,<4)"] 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 = [ @@ -1038,7 +1002,6 @@ 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 = [ @@ -1061,7 +1024,6 @@ 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 = [ @@ -1084,7 +1046,6 @@ test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (= name = "eth-typing" version = "3.3.0" description = "eth-typing: Common type annotations for ethereum python packages" -category = "dev" optional = false python-versions = ">=3.7.2, <4" files = [ @@ -1102,7 +1063,6 @@ test = ["pytest (>=6.2.5,<7)", "pytest-xdist", "tox (>=2.9.1,<3)"] name = "eth-utils" version = "2.1.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -1126,7 +1086,6 @@ test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (>=6.2.5,<7)", "pytest-xdist", " name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1141,7 +1100,6 @@ 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 = [ @@ -1156,7 +1114,6 @@ testing = ["pre-commit"] name = "fastecdsa" version = "2.3.0" description = "Fast elliptic curve digital signatures" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1176,7 +1133,6 @@ 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 = [ @@ -1199,7 +1155,6 @@ 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 = [ @@ -1215,7 +1170,6 @@ Six = "*" name = "frozendict" version = "1.2" description = "An immutable dictionary" -category = "dev" optional = false python-versions = "*" files = [ @@ -1226,7 +1180,6 @@ 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 = [ @@ -1310,7 +1263,6 @@ files = [ name = "furo" version = "2023.5.20" description = "A clean customisable Sphinx documentation theme." -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1328,7 +1280,6 @@ sphinx-basic-ng = "*" name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1349,7 +1300,6 @@ tornado = ["tornado (>=0.2)"] name = "hexbytes" version = "0.3.0" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" -category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -1367,7 +1317,6 @@ 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 = [ @@ -1389,7 +1338,6 @@ lxml = ["lxml"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1401,7 +1349,6 @@ 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 = [ @@ -1413,7 +1360,6 @@ files = [ name = "importlib-metadata" version = "5.1.0" description = "Read metadata from Python packages" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1433,7 +1379,6 @@ 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 = [ @@ -1445,7 +1390,6 @@ 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 = [ @@ -1463,7 +1407,6 @@ 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 = [ @@ -1475,7 +1418,6 @@ files = [ name = "Jinja2" version = "3.0.3" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1493,7 +1435,6 @@ 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 = [ @@ -1513,7 +1454,6 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "lark" version = "1.1.5" description = "a modern parsing library" -category = "main" optional = false python-versions = "*" files = [ @@ -1530,7 +1470,6 @@ 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 = [ @@ -1559,7 +1498,6 @@ files = [ name = "lockfile" version = "0.12.2" description = "Platform-independent file locking module" -category = "main" optional = true python-versions = "*" files = [ @@ -1571,7 +1509,6 @@ files = [ name = "lru-dict" version = "1.1.8" description = "An Dict like LRU container." -category = "dev" optional = false python-versions = "*" files = [ @@ -1632,7 +1569,6 @@ 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 = [ @@ -1682,7 +1618,6 @@ 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 = [ @@ -1703,7 +1638,6 @@ 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 = [ @@ -1727,7 +1661,6 @@ union = ["typeguard"] name = "marshmallow-enum" version = "1.5.1" description = "Enum field for Marshmallow" -category = "dev" optional = false python-versions = "*" files = [ @@ -1742,7 +1675,6 @@ marshmallow = ">=2.0.0" name = "marshmallow-oneofschema" version = "3.0.1" description = "marshmallow multiplexing schema" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1762,7 +1694,6 @@ tests = ["mock", "pytest"] name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1774,7 +1705,6 @@ files = [ name = "mpmath" version = "1.2.1" description = "Python library for arbitrary-precision floating-point arithmetic" -category = "main" optional = false python-versions = "*" files = [ @@ -1790,7 +1720,6 @@ tests = ["pytest (>=4.6)"] name = "msgpack" version = "1.0.4" description = "MessagePack serializer" -category = "main" optional = true python-versions = "*" files = [ @@ -1852,7 +1781,6 @@ files = [ name = "multidict" version = "6.0.2" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1921,7 +1849,6 @@ 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 = [ @@ -1933,7 +1860,6 @@ files = [ name = "natsort" version = "8.2.0" description = "Simple yet flexible natural sorting in Python." -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -1949,7 +1875,6 @@ 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 = [ @@ -1964,7 +1889,6 @@ setuptools = "*" name = "numpy" version = "1.23.5" description = "NumPy is the fundamental package for array computing with Python." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2002,7 +1926,6 @@ files = [ name = "packaging" version = "23.0" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2014,7 +1937,6 @@ 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 = [ @@ -2028,7 +1950,6 @@ 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 = [ @@ -2040,7 +1961,6 @@ 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 = [ @@ -2052,7 +1972,6 @@ files = [ name = "pipdeptree" version = "2.3.3" description = "Command line utility to show dependency tree of packages." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2068,7 +1987,6 @@ test = ["covdefaults (>=2.2)", "diff-cover (>=7.0.1)", "pip (>=22.3)", "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,7 +2002,6 @@ 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 = [ @@ -2100,7 +2017,6 @@ testing = ["pytest", "pytest-benchmark"] name = "poethepoet" version = "0.20.0" description = "A task runner that works well with poetry." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2119,7 +2035,6 @@ 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 = [ @@ -2178,7 +2093,6 @@ files = [ name = "prometheus-client" version = "0.15.0" description = "Python client for the Prometheus monitoring system." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2193,7 +2107,6 @@ twisted = ["twisted"] name = "protobuf" version = "4.22.1" description = "" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2216,7 +2129,6 @@ 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 = [ @@ -2258,7 +2170,6 @@ 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 = [ @@ -2273,7 +2184,6 @@ plugins = ["importlib-metadata"] name = "pylint" version = "2.17.4" description = "python code static checker" -category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -2303,7 +2213,6 @@ testutils = ["gitpython (>3)"] name = "pyright" version = "1.1.311" description = "Command line wrapper for pyright" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2322,7 +2231,6 @@ 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 = [ @@ -2357,18 +2265,16 @@ files = [ [[package]] name = "pytest" -version = "7.2.2" +version = "7.3.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, - {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, + {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, + {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" @@ -2377,13 +2283,12 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" version = "0.20.3" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2402,7 +2307,6 @@ 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 = [ @@ -2421,7 +2325,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2439,7 +2342,6 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-rerunfailures" version = "11.1.2" description = "pytest plugin to re-run tests to eliminate flaky failures" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2455,7 +2357,6 @@ pytest = ">=5.3" 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 = [ @@ -2476,7 +2377,6 @@ testing = ["filelock"] name = "pytz" version = "2022.6" description = "World timezone definitions, modern and historical" -category = "main" optional = true python-versions = "*" files = [ @@ -2488,7 +2388,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "main" optional = false python-versions = "*" files = [ @@ -2512,7 +2411,6 @@ files = [ name = "PyYAML" version = "6.0" description = "YAML parser and emitter for Python" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2562,7 +2460,6 @@ files = [ name = "regex" version = "2023.3.23" description = "Alternative regular expression module, to replace re." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2632,7 +2529,6 @@ files = [ name = "requests" version = "2.28.1" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -2654,7 +2550,6 @@ 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 = [ @@ -2676,7 +2571,6 @@ 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 = [ @@ -2695,7 +2589,6 @@ 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 = [ @@ -2742,7 +2635,6 @@ files = [ name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2759,7 +2651,6 @@ 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 = [ @@ -2771,7 +2662,6 @@ 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 = [ @@ -2783,7 +2673,6 @@ 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 = [ @@ -2795,7 +2684,6 @@ files = [ name = "sphinx" version = "7.0.1" description = "Python documentation generator" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -2831,7 +2719,6 @@ 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 = [ @@ -2851,7 +2738,6 @@ 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 = [ @@ -2869,7 +2755,6 @@ 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 = [ @@ -2885,7 +2770,6 @@ markupsafe = ">=1" name = "sphinx-prompt" version = "1.5.0" description = "Sphinx directive to add unselectable prompt" -category = "main" optional = true python-versions = "*" files = [ @@ -2900,7 +2784,6 @@ Sphinx = "*" name = "sphinx-tabs" version = "3.4.1" description = "Tabbed views for Sphinx" -category = "main" optional = true python-versions = "~=3.7" files = [ @@ -2921,7 +2804,6 @@ 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 = [ @@ -2956,7 +2838,6 @@ 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 = [ @@ -2972,7 +2853,6 @@ 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,7 +2868,6 @@ 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 = [ @@ -3004,7 +2883,6 @@ 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 = [ @@ -3019,7 +2897,6 @@ 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 = [ @@ -3035,7 +2912,6 @@ 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 = [ @@ -3051,7 +2927,6 @@ test = ["pytest"] name = "starknet-devnet" version = "0.5.2" description = "A local testnet for Starknet" -category = "dev" optional = false python-versions = ">=3.9,<3.10" files = [] @@ -3082,7 +2957,6 @@ resolved_reference = "744e9b3bd5fb9e856287158d87673e090df69d73" name = "sympy" version = "1.11.1" description = "Computer algebra system (CAS) in Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -3097,7 +2971,6 @@ mpmath = ">=0.19" name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -3112,7 +2985,6 @@ widechars = ["wcwidth"] name = "tomli" version = "1.2.3" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3124,7 +2996,6 @@ files = [ name = "tomlkit" version = "0.11.6" description = "Style preserving TOML library" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3136,7 +3007,6 @@ files = [ name = "toolz" version = "0.12.0" description = "List processing tools and functional utilities" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3148,7 +3018,6 @@ files = [ name = "typeguard" version = "2.13.3" description = "Run-time type checker for Python" -category = "dev" optional = false python-versions = ">=3.5.3" files = [ @@ -3164,7 +3033,6 @@ 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 = [ @@ -3176,7 +3044,6 @@ files = [ name = "typing-inspect" version = "0.8.0" description = "Runtime inspection utilities for typing module." -category = "main" optional = false python-versions = "*" files = [ @@ -3192,7 +3059,6 @@ 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 = [ @@ -3209,7 +3075,6 @@ 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 = [ @@ -3244,7 +3109,6 @@ 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 = [ @@ -3256,7 +3120,6 @@ files = [ name = "websockets" version = "11.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3336,7 +3199,6 @@ files = [ name = "werkzeug" version = "2.0.3" description = "The comprehensive WSGI web application library." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3351,7 +3213,6 @@ 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 = [ @@ -3425,7 +3286,6 @@ files = [ name = "yarl" version = "1.8.1" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3498,7 +3358,6 @@ 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 = [ @@ -3511,7 +3370,7 @@ 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 = ["sphinx", "enum-tools", "furo"] +docs = ["enum-tools", "furo", "sphinx"] [metadata] lock-version = "2.0" From 75be8d5e9f174bcf15fb950be08bdb3a9f80a714 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:57:03 +0200 Subject: [PATCH 04/17] Bump pyright from 1.1.311 to 1.1.314 (#1059) Bumps [pyright](https://github.com/RobertCraigie/pyright-python) from 1.1.311 to 1.1.314. - [Release notes](https://github.com/RobertCraigie/pyright-python/releases) - [Commits](https://github.com/RobertCraigie/pyright-python/compare/v1.1.311...v1.1.314) --- updated-dependencies: - dependency-name: pyright dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 356b195bf..db020306a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2211,13 +2211,13 @@ testutils = ["gitpython (>3)"] [[package]] name = "pyright" -version = "1.1.311" +version = "1.1.314" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.311-py3-none-any.whl", hash = "sha256:04df30c6b31d05068effe5563411291c876f5e4221d0af225a267b61dce1ca85"}, - {file = "pyright-1.1.311.tar.gz", hash = "sha256:554b555d3f770e8da2e76d6bb94e2ac63b3edc7dcd5fb8de202f9dd53e36689a"}, + {file = "pyright-1.1.314-py3-none-any.whl", hash = "sha256:5008a2e04b71e35c5f1b78b16adae9d012601197442ae6c798e9bb3456d1eecb"}, + {file = "pyright-1.1.314.tar.gz", hash = "sha256:bd104c206fe40eaf5f836efa9027f07cc0efcbc452e6d22dfae36759c5fd28b3"}, ] [package.dependencies] From 2cf793284fe87343fc729c4edaca6739b26325f4 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Wed, 14 Jun 2023 14:37:48 +0200 Subject: [PATCH 05/17] Support custom nonces (#1053) * Allow for custom nonce in Account methods * Allow for custom nonce in Contract methods * Add custom nonce test * Add nonce to Account.deploy_account * Disable too-many-locals * Fix typecheck --- starknet_py/contract.py | 28 +++++++++-- starknet_py/net/account/account.py | 50 +++++++++++++++---- starknet_py/net/account/base_account.py | 10 ++++ starknet_py/tests/e2e/account/account_test.py | 35 +++++++++++++ 4 files changed, 109 insertions(+), 14 deletions(-) diff --git a/starknet_py/contract.py b/starknet_py/contract.py index a6292532a..2380be177 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -164,6 +164,7 @@ async def deploy( salt: Optional[int] = None, unique: bool = True, constructor_args: Optional[Union[List, Dict]] = None, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> "DeployResult": @@ -176,6 +177,7 @@ async def deploy( :param salt: Optional salt. Random value is selected if it is not provided. :param unique: Determines if the contract should be salted with the account address. :param constructor_args: a ``list`` or ``dict`` of arguments for the constructor. + :param nonce: Nonce of the transaction with call to deployer. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :return: DeployResult instance. @@ -191,7 +193,7 @@ async def deploy( class_hash=self.class_hash, salt=salt, abi=abi, calldata=constructor_args ) res = await self._account.execute( - calls=deploy_call, max_fee=max_fee, auto_estimate=auto_estimate + calls=deploy_call, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate ) deployed_contract = Contract( @@ -290,12 +292,15 @@ async def invoke( self, max_fee: Optional[int] = None, auto_estimate: bool = False, + *, + nonce: Optional[int] = None, ) -> InvokeResult: """ Invokes a method. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param nonce: Nonce of the transaction. :return: InvokeResult. """ if max_fee is not None: @@ -303,6 +308,7 @@ async def invoke( transaction = await self._account.sign_invoke_transaction( calls=self, + nonce=nonce, max_fee=self.max_fee, auto_estimate=auto_estimate, ) @@ -322,6 +328,8 @@ async def estimate_fee( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + *, + nonce: Optional[int] = None, ) -> EstimatedFee: """ Estimate fee for prepared function call. @@ -329,9 +337,12 @@ async def estimate_fee( :param block_hash: Estimate fee at specific block hash. :param block_number: Estimate fee at given block number (or "latest" / "pending" for the latest / pending block), default is "pending". + :param nonce: Nonce of the transaction. :return: Estimated amount of Wei executing specified transaction will cost. """ - tx = await self._account.sign_invoke_transaction(calls=self, max_fee=0) + tx = await self._account.sign_invoke_transaction( + calls=self, nonce=nonce, max_fee=0 + ) estimated_fee = await self._client.estimate_fee( tx=tx, @@ -414,6 +425,7 @@ async def invoke( *args, max_fee: Optional[int] = None, auto_estimate: bool = False, + nonce: Optional[int] = None, **kwargs, ) -> InvokeResult: """ @@ -422,9 +434,12 @@ async def invoke( :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param nonce: Nonce of the transaction. """ prepared_call = self.prepare(*args, **kwargs) - return await prepared_call.invoke(max_fee=max_fee, auto_estimate=auto_estimate) + return await prepared_call.invoke( + max_fee=max_fee, nonce=nonce, auto_estimate=auto_estimate + ) @staticmethod def get_selector(function_name: str): @@ -535,6 +550,7 @@ async def declare( account: BaseAccount, compiled_contract: str, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> DeclareResult: @@ -543,6 +559,7 @@ async def declare( :param account: BaseAccount used to sign and send declare transaction. :param compiled_contract: String containing compiled contract. + :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :return: DeclareResult instance. @@ -550,6 +567,7 @@ async def declare( declare_tx = await account.sign_declare_transaction( compiled_contract=compiled_contract, + nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate, ) @@ -571,6 +589,7 @@ async def deploy_contract( constructor_args: Optional[Union[List, Dict]] = None, *, deployer_address: AddressRepresentation = DEFAULT_DEPLOYER_ADDRESS, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> "DeployResult": @@ -581,6 +600,7 @@ async def deploy_contract( :param class_hash: The class_hash of the contract to be deployed. :param abi: An abi of the contract to be deployed. :param constructor_args: a ``list`` or ``dict`` of arguments for the constructor. + :param nonce: Nonce of the transaction. :param deployer_address: Address of the UDC. Is set to the address of the default UDC (same address on mainnet/testnet/devnet) by default. Must be set when using custom network other than ones listed above. @@ -596,7 +616,7 @@ async def deploy_contract( class_hash=class_hash, abi=abi, calldata=constructor_args ) res = await account.execute( - calls=deploy_call, max_fee=max_fee, auto_estimate=auto_estimate + calls=deploy_call, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate ) deployed_contract = Contract( diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index d94e21669..f677c0b9b 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -133,6 +133,8 @@ async def _get_max_fee( async def _prepare_invoke( self, calls: Calls, + *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> Invoke: @@ -144,7 +146,8 @@ async def _prepare_invoke( :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :return: Invoke created from the calls (without the signature). """ - nonce = await self.get_nonce() + if nonce is None: + nonce = await self.get_nonce() call_descriptions, calldata = _merge_calls(ensure_iterable(calls)) wrapped_calldata = _execute_payload_serializer.serialize( @@ -229,10 +232,13 @@ async def sign_invoke_transaction( self, calls: Calls, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> Invoke: - execute_tx = await self._prepare_invoke(calls, max_fee, auto_estimate) + execute_tx = await self._prepare_invoke( + calls, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate + ) signature = self.signer.sign_transaction(execute_tx) return _add_signature_to_transaction(execute_tx, signature) @@ -240,6 +246,7 @@ async def sign_declare_transaction( self, compiled_contract: str, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> Declare: @@ -248,7 +255,9 @@ async def sign_declare_transaction( "Signing sierra contracts requires using `sign_declare_v2_transaction` method." ) - declare_tx = await self._make_declare_transaction(compiled_contract) + declare_tx = await self._make_declare_transaction( + compiled_contract, nonce=nonce + ) max_fee = await self._get_max_fee( transaction=declare_tx, max_fee=max_fee, auto_estimate=auto_estimate @@ -262,11 +271,12 @@ async def sign_declare_v2_transaction( compiled_contract: str, compiled_class_hash: int, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> DeclareV2: declare_tx = await self._make_declare_v2_transaction( - compiled_contract, compiled_class_hash + compiled_contract, compiled_class_hash, nonce=nonce ) max_fee = await self._get_max_fee( transaction=declare_tx, max_fee=max_fee, auto_estimate=auto_estimate @@ -275,31 +285,45 @@ async def sign_declare_v2_transaction( signature = self.signer.sign_transaction(declare_tx) return _add_signature_to_transaction(declare_tx, signature) - async def _make_declare_transaction(self, compiled_contract: str) -> Declare: + async def _make_declare_transaction( + self, compiled_contract: str, *, nonce: Optional[int] = None + ) -> Declare: contract_class = create_compiled_contract(compiled_contract=compiled_contract) + + if nonce is None: + nonce = await self.get_nonce() + declare_tx = Declare( contract_class=contract_class, sender_address=self.address, max_fee=0, signature=[], - nonce=await self.get_nonce(), + nonce=nonce, version=1, ) return declare_tx async def _make_declare_v2_transaction( - self, compiled_contract: str, compiled_class_hash: int + self, + compiled_contract: str, + compiled_class_hash: int, + *, + nonce: Optional[int] = None, ) -> DeclareV2: contract_class = create_sierra_compiled_contract( compiled_contract=compiled_contract ) + + if nonce is None: + nonce = await self.get_nonce() + declare_tx = DeclareV2( contract_class=contract_class, compiled_class_hash=compiled_class_hash, sender_address=self.address, max_fee=0, signature=[], - nonce=await self.get_nonce(), + nonce=nonce, version=2, ) return declare_tx @@ -310,6 +334,7 @@ async def sign_deploy_account_transaction( contract_address_salt: int, constructor_calldata: Optional[List[int]] = None, *, + nonce: int = 0, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> DeployAccount: @@ -322,7 +347,7 @@ async def sign_deploy_account_transaction( version=1, max_fee=0, signature=[], - nonce=0, + nonce=nonce, ) max_fee = await self._get_max_fee( @@ -336,11 +361,12 @@ async def execute( self, calls: Calls, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> SentTransactionResponse: execute_transaction = await self.sign_invoke_transaction( - calls, max_fee=max_fee, auto_estimate=auto_estimate + calls, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate ) return await self._client.send_transaction(execute_transaction) @@ -363,9 +389,11 @@ async def deploy_account( client: Client, chain: StarknetChainId, constructor_calldata: Optional[List[int]] = None, + nonce: int = 0, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> AccountDeploymentResult: + # pylint: disable=too-many-locals """ Deploys an account contract with provided class_hash on Starknet and returns an AccountDeploymentResult that allows waiting for transaction acceptance. @@ -383,6 +411,7 @@ async def deploy_account( :param chain: id of the Starknet chain used. :param constructor_calldata: optional calldata to account contract constructor. If ``None`` is passed, ``[key_pair.public_key]`` will be used as calldata. + :param nonce: Nonce of the transaction. :param max_fee: max fee to be paid for deployment, must be less or equal to the amount of tokens prefunded. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. """ @@ -414,6 +443,7 @@ async def deploy_account( class_hash=class_hash, contract_address_salt=salt, constructor_calldata=calldata, + nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate, ) diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index 7fe5df5db..a7145868a 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -87,6 +87,7 @@ async def sign_invoke_transaction( self, calls: Calls, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> Invoke: @@ -94,6 +95,7 @@ async def sign_invoke_transaction( Takes calls and creates signed Invoke. :param calls: Single call or list of calls. + :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :return: Invoke created from the calls. @@ -104,6 +106,7 @@ async def sign_declare_transaction( self, compiled_contract: str, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> Declare: @@ -111,6 +114,7 @@ async def sign_declare_transaction( Create and sign declare transaction. :param compiled_contract: string containing a compiled Starknet contract. Supports old contracts. + :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :return: Signed Declare transaction. @@ -122,6 +126,7 @@ async def sign_declare_v2_transaction( compiled_contract: str, compiled_class_hash: int, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> DeclareV2: @@ -132,6 +137,7 @@ async def sign_declare_v2_transaction( Supports new contracts (compiled to sierra). :param compiled_class_hash: a class hash of the sierra compiled contract used in the declare transaction. Computed from casm compiled contract. + :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :return: Signed DeclareV2 transaction. @@ -144,6 +150,7 @@ async def sign_deploy_account_transaction( contract_address_salt: int, constructor_calldata: Optional[List[int]] = None, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> DeployAccount: @@ -154,6 +161,7 @@ async def sign_deploy_account_transaction( :param contract_address_salt: A salt used to calculate deployed contract address. :param constructor_calldata: Calldata to be ed to contract constructor and used to calculate deployed contract address. + :param nonce: Nonce of the transaction. :param max_fee: Max fee to be paid for deploying account transaction. Enough tokens must be prefunded before sending the transaction for it to succeed. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. @@ -165,6 +173,7 @@ async def execute( self, calls: Calls, *, + nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, ) -> SentTransactionResponse: @@ -172,6 +181,7 @@ async def execute( Takes calls and executes transaction. :param calls: Single call or list of calls. + :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :return: SentTransactionResponse. diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 1bd26a7c2..865faa400 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -21,6 +21,7 @@ from starknet_py.net.models import StarknetChainId from starknet_py.net.models.transaction import Declare, DeclareV2 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 @@ -546,3 +547,37 @@ async def test_sign_deploy_account_tx_for_fee_estimation( # Verify that original transaction can be sent result = await account.client.deploy_account(transaction) await account.client.wait_for_tx(result.transaction_hash) + + +@pytest.mark.asyncio +async def test_sign_transaction_custom_nonce( + account, hello_starknet_class_hash_tx_hash +): + deployment = Deployer().create_contract_deployment( + hello_starknet_class_hash_tx_hash[0] + ) + deploy_tx = await account.sign_invoke_transaction(deployment.call, max_fee=MAX_FEE) + + new_balance = 30 + invoke_tx = await account.sign_invoke_transaction( + Call( + deployment.address, + get_selector_from_name("increase_balance"), + [new_balance], + ), + nonce=deploy_tx.nonce + 1, + max_fee=MAX_FEE, + ) + + deploy_res = await account.client.send_transaction(deploy_tx) + invoke_res = await account.client.send_transaction(invoke_tx) + + await account.client.wait_for_tx(deploy_res.transaction_hash) + await account.client.wait_for_tx(invoke_res.transaction_hash) + + result = await account.client.call_contract( + Call(deployment.address, get_selector_from_name("get_balance"), []) + ) + + assert invoke_tx.nonce == deploy_tx.nonce + 1 + assert result == [new_balance] From b5de0d83d97d27241226e7268262766ef9939b32 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Wed, 14 Jun 2023 15:34:57 +0200 Subject: [PATCH 06/17] Disable Windows checks on PRs (#1063) --- .github/workflows/checks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 49d5bdeab..cc06eab9d 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -242,7 +242,7 @@ jobs: # ---------------------------------------------------------- # run-tests-windows: - # if: ${{ github.event_name != 'pull_request' }} + if: ${{ github.event_name != 'pull_request' }} name: Tests Windows needs: setup-tests runs-on: windows-latest @@ -387,7 +387,7 @@ jobs: # ---------------------------------------------------------- # run-docs-tests-windows: - # if: ${{ github.event_name != 'pull_request' }} + if: ${{ github.event_name != 'pull_request' }} name: Docs Tests Windows needs: setup-tests runs-on: windows-latest From 7723f8888f6001086c0bec7ce7967e2be6c7e88a Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Tue, 20 Jun 2023 11:37:04 +0200 Subject: [PATCH 07/17] Do not mutate contract_class (#1061) --- starknet_py/hash/class_hash.py | 3 ++- starknet_py/hash/class_hash_test.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/starknet_py/hash/class_hash.py b/starknet_py/hash/class_hash.py index 50b35bc00..27050ba87 100644 --- a/starknet_py/hash/class_hash.py +++ b/starknet_py/hash/class_hash.py @@ -1,3 +1,4 @@ +import copy import json import re from typing import List @@ -31,7 +32,7 @@ def compute_class_hash(contract_class: ContractClass) -> int: ] builtins_hash = compute_hash_on_elements(_encoded_builtins) - hinted_class_hash = _compute_hinted_class_hash(contract_class) + hinted_class_hash = _compute_hinted_class_hash(copy.deepcopy(contract_class)) program_data_hash = compute_hash_on_elements( [int(data_, 0) for data_ in contract_class.program["data"]] diff --git a/starknet_py/hash/class_hash_test.py b/starknet_py/hash/class_hash_test.py index 1da113f80..08a3603de 100644 --- a/starknet_py/hash/class_hash_test.py +++ b/starknet_py/hash/class_hash_test.py @@ -1,5 +1,7 @@ # pylint: disable=line-too-long # fmt: off +import copy + import pytest from starknet_py.common import create_contract_class @@ -21,6 +23,8 @@ def test_compute_class_hash(contract_source, expected_class_hash): compiled_contract = read_contract(contract_source) contract_class = create_contract_class(compiled_contract) + initial_contract_class = copy.deepcopy(contract_class) class_hash = compute_class_hash(contract_class) assert class_hash == expected_class_hash + assert contract_class == initial_contract_class From 1d6f85be5c0ef739135e22839dd129ec829505e5 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Tue, 20 Jun 2023 12:06:52 +0200 Subject: [PATCH 08/17] Parametrize block in Account.get_nonce (#1062) * Add block_number, block_hash to get_nonce * Update get_nonce test --- starknet_py/net/account/account.py | 11 +++++++++-- starknet_py/net/account/base_account.py | 13 ++++++++++--- starknet_py/tests/e2e/account/account_test.py | 9 ++++++++- .../tests/e2e/docs/guide/test_custom_nonce.py | 11 ++++++++--- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index f677c0b9b..20ef91c99 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -190,14 +190,21 @@ async def _estimate_fee( return estimated_fee - async def get_nonce(self) -> int: + async def get_nonce( + self, + *, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> int: """ Get the current nonce of the account. + :param block_hash: Block's hash or literals `"pending"` or `"latest"` + :param block_number: Block's number or literals `"pending"` or `"latest"` :return: nonce. """ return await self._client.get_contract_nonce( - self.address, block_number="pending" + self.address, block_hash=block_hash, block_number=block_number ) async def get_balance( diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index a7145868a..837e5e15d 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod -from typing import List, Optional +from typing import List, Optional, Union from starknet_py.net.client import Client -from starknet_py.net.client_models import Calls, SentTransactionResponse +from starknet_py.net.client_models import Calls, Hash, SentTransactionResponse, Tag from starknet_py.net.models import AddressRepresentation, StarknetChainId from starknet_py.net.models.transaction import ( Declare, @@ -46,10 +46,17 @@ def supported_transaction_version(self) -> int: """ @abstractmethod - async def get_nonce(self) -> int: + async def get_nonce( + self, + *, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> int: """ Get the current nonce of the account. + :param block_hash: Block's hash or literals `"pending"` or `"latest"` + :param block_number: Block's number or literals `"pending"` or `"latest"` :return: nonce of the account. """ diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 865faa400..fecb49464 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -152,6 +152,7 @@ async def test_get_class_hash_at(map_contract, account): async def test_get_nonce(account, map_contract): nonce = await account.get_nonce() address = map_contract.address + block = await account.client.get_block() tx = await account.execute( Call( @@ -162,9 +163,15 @@ async def test_get_nonce(account, map_contract): await account.client.wait_for_tx(tx.transaction_hash) new_nonce = await account.get_nonce() + new_nonce_latest_block = await account.get_nonce(block_number="latest") + + old_nonce = await account.get_nonce(block_number=block.block_number) assert isinstance(nonce, int) and isinstance(new_nonce, int) - assert new_nonce > nonce + assert new_nonce == nonce + 1 + + assert old_nonce == nonce + assert new_nonce_latest_block == new_nonce @pytest.mark.asyncio diff --git a/starknet_py/tests/e2e/docs/guide/test_custom_nonce.py b/starknet_py/tests/e2e/docs/guide/test_custom_nonce.py index 2f8aa4e4f..092cc77c8 100644 --- a/starknet_py/tests/e2e/docs/guide/test_custom_nonce.py +++ b/starknet_py/tests/e2e/docs/guide/test_custom_nonce.py @@ -1,8 +1,8 @@ -from typing import Optional +from typing import Optional, Union import pytest -from starknet_py.net.client_models import Call +from starknet_py.net.client_models import Call, Hash, Tag @pytest.mark.asyncio @@ -39,7 +39,12 @@ def __init__( # Create a simple counter that will store a nonce self.nonce_counter = 0 - async def get_nonce(self) -> int: + async def get_nonce( + self, + *, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> int: # Increment the counter and return the nonce. # This is just an example custom nonce logic and is not meant # to be a recommended solution. From fa6f299fdd4d6851d30b2375bd8638e12741e920 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Tue, 20 Jun 2023 13:16:54 +0200 Subject: [PATCH 09/17] Parametrize block in Account.get_balance (#1064) * Add block_hash, block_number to Account.get_balance * Add get_balance test --- starknet_py/net/account/account.py | 6 +++++- starknet_py/net/account/account_test.py | 16 ++++++++++++++++ starknet_py/net/account/base_account.py | 5 +++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index 20ef91c99..dd5b668f5 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -211,6 +211,9 @@ async def get_balance( self, token_address: Optional[AddressRepresentation] = None, chain_id: Optional[StarknetChainId] = None, + *, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, ) -> int: if token_address is None: token_address = self._default_token_address_for_chain(chain_id) @@ -221,7 +224,8 @@ async def get_balance( selector=get_selector_from_name("balanceOf"), calldata=[self.address], ), - block_hash="pending", + block_hash=block_hash, + block_number=block_number, ) return (high << 128) + low diff --git a/starknet_py/net/account/account_test.py b/starknet_py/net/account/account_test.py index e014e87a7..ddee7e092 100644 --- a/starknet_py/net/account/account_test.py +++ b/starknet_py/net/account/account_test.py @@ -9,6 +9,7 @@ from starknet_py.net.models import StarknetChainId, parse_address from starknet_py.net.networks import MAINNET, TESTNET, TESTNET2 from starknet_py.net.signer.stark_curve_signer import KeyPair, StarkCurveSigner +from starknet_py.tests.e2e.fixtures.constants import MAX_FEE @pytest.mark.asyncio @@ -48,6 +49,21 @@ async def test_get_balance_default_token_address(net, call_contract): assert call.to_addr == parse_address(FEE_CONTRACT_ADDRESS) +@pytest.mark.asyncio +async def test_account_get_balance(account, map_contract): + balance = await account.get_balance() + block = await account.client.get_block() + + await map_contract.functions["put"].invoke(key=10, value=10, max_fee=MAX_FEE) + + new_balance = await account.get_balance() + old_balance = await account.get_balance(block_number=block.block_number) + + assert balance > 0 + assert new_balance < balance + assert old_balance == balance + + def test_create_account(): key_pair = KeyPair.from_private_key(0x111) account = Account( diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index 837e5e15d..a75ba50a6 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -65,6 +65,9 @@ async def get_balance( self, token_address: Optional[AddressRepresentation] = None, chain_id: Optional[StarknetChainId] = None, + *, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, ) -> int: """ Checks account's balance of specified token. @@ -73,6 +76,8 @@ async def get_balance( :param chain_id: Identifier of the Starknet chain used. If token_address is not specified it will be used to determine network's payment token address. If token_address is provided, chain_id will be ignored. + :param block_hash: Block's hash or literals `"pending"` or `"latest"` + :param block_number: Block's number or literals `"pending"` or `"latest"` :return: Token balance. """ From 8f49dc368c47df50053fd7d78415b9ac1f53f0b3 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Wed, 21 Jun 2023 13:23:48 +0200 Subject: [PATCH 10/17] Remove `compute_invoke_hash` (#1060) * Remove compute_invoke_hash * Fix docs --- docs/api/models.rst | 2 - docs/migration_guide.rst | 2 +- starknet_py/hash/transaction.py | 51 +++++++++- starknet_py/hash/transaction_test.py | 108 +++++++++++++-------- starknet_py/net/models/__init__.py | 1 - starknet_py/net/models/transaction.py | 61 +----------- starknet_py/net/models/transaction_test.py | 91 +---------------- 7 files changed, 120 insertions(+), 196 deletions(-) diff --git a/docs/api/models.rst b/docs/api/models.rst index 344d4d8de..3450c8dc8 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -27,5 +27,3 @@ Module containing base models and functions to operate on them. .. autoenum:: StarknetChainId :members: - -.. autofunction:: compute_invoke_hash diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index dd6950261..c02c5e561 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -207,7 +207,7 @@ The only supported Python version is 3.9. - :class:`starknet_py.net.client_models.InvokeTransaction` - :class:`starknet_py.net.models.transaction.Invoke` - - :func:`starknet_py.net.models.transaction.compute_invoke_hash` + - ``compute_invoke_hash`` 13. Replaced ``BlockStateUpdate.state_diff.declared_contract_hashes`` is now a list of ``DeclaredContractHash`` representing new Cairo classes. Old declared contract classes are still available at ``BlockStateUpdate.state_diff.deprecated_declared_contract_hashes``. 14. Removed ``version`` property from ``PreparedFunctionCall`` class. 15. Removed deprecated ``max_steps`` in :class:`~starknet_py.proxy.contract_abi_resolver.ProxyConfig`. diff --git a/starknet_py/hash/transaction.py b/starknet_py/hash/transaction.py index 143d4d911..d0f572740 100644 --- a/starknet_py/hash/transaction.py +++ b/starknet_py/hash/transaction.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import Sequence +from typing import Optional, Sequence from starknet_py.common import int_from_bytes from starknet_py.constants import DEFAULT_ENTRY_POINT_SELECTOR @@ -30,7 +30,7 @@ def compute_transaction_hash( calldata: Sequence[int], max_fee: int, chain_id: int, - additional_data: Sequence[int], + additional_data: Optional[Sequence[int]] = None, ) -> int: """ Calculates the transaction hash in the Starknet network - a unique identifier of the @@ -59,6 +59,8 @@ def compute_transaction_hash( :param additional_data: Additional data, required for some transactions (e.g. DeployAccount, Declare). :return: Hash of the transaction. """ + if additional_data is None: + additional_data = [] calldata_hash = compute_hash_on_elements(data=calldata) data_to_hash = [ tx_hash_prefix.value, @@ -76,6 +78,38 @@ def compute_transaction_hash( ) +def compute_invoke_transaction_hash( + *, + version: int, + sender_address: int, + calldata: Sequence[int], + max_fee: int, + chain_id: int, + nonce: int, +) -> int: + """ + Computes hash of the Invoke transaction. + + :param version: The transaction's version. + :param sender_address: Sender address. + :param calldata: Calldata of the function. + :param max_fee: The transaction's maximum fee. + :param chain_id: The network's chain ID. + :param nonce: Nonce of the transaction. + :return: Hash of the transaction. + """ + return compute_transaction_hash( + tx_hash_prefix=TransactionHashPrefix.INVOKE, + version=version, + contract_address=sender_address, + entry_point_selector=DEFAULT_ENTRY_POINT_SELECTOR, + calldata=calldata, + max_fee=max_fee, + chain_id=chain_id, + additional_data=[nonce], + ) + + def compute_deploy_account_transaction_hash( version: int, contract_address: int, @@ -146,7 +180,8 @@ def compute_declare_transaction_hash( def compute_declare_v2_transaction_hash( *, - contract_class: SierraContractClass, + contract_class: Optional[SierraContractClass] = None, + class_hash: Optional[int] = None, compiled_class_hash: int, chain_id: int, sender_address: int, @@ -158,6 +193,7 @@ def compute_declare_v2_transaction_hash( Computes class hash of declare transaction version 2. :param contract_class: SierraContractClass of the contract. + :param class_hash: Class hash of the contract. :param compiled_class_hash: compiled class hash of the program. :param chain_id: The network's chain ID. :param sender_address: Address which sends the transaction. @@ -166,7 +202,14 @@ def compute_declare_v2_transaction_hash( :param nonce: Nonce of the transaction. :return: Hash of the transaction. """ - class_hash = compute_sierra_class_hash(contract_class) + if contract_class is None and class_hash is None: + raise ValueError("Either contract_class or class_hash is required.") + if contract_class is not None and class_hash is not None: + raise ValueError("Both contract_class and class_hash passed.") + + if class_hash is None: + assert contract_class is not None + class_hash = compute_sierra_class_hash(contract_class) return compute_transaction_hash( tx_hash_prefix=TransactionHashPrefix.DECLARE, diff --git a/starknet_py/hash/transaction_test.py b/starknet_py/hash/transaction_test.py index 1bb844356..680196935 100644 --- a/starknet_py/hash/transaction_test.py +++ b/starknet_py/hash/transaction_test.py @@ -1,25 +1,20 @@ import pytest -from starknet_py.common import ( - create_casm_class, - create_compiled_contract, - create_sierra_compiled_contract, -) -from starknet_py.hash.casm_class_hash import compute_casm_class_hash +from starknet_py.common import create_compiled_contract from starknet_py.hash.transaction import ( TransactionHashPrefix, compute_declare_transaction_hash, compute_declare_v2_transaction_hash, compute_deploy_account_transaction_hash, + compute_invoke_transaction_hash, compute_transaction_hash, ) from starknet_py.net.models import StarknetChainId -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract @pytest.mark.parametrize( - "data, calculated_hash", + "data, expected_hash", ( ( [TransactionHashPrefix.INVOKE, 2, 3, 4, [5], 6, 7, [8]], @@ -29,27 +24,44 @@ [TransactionHashPrefix.L1_HANDLER, 15, 39, 74, [74], 39, 15, [28]], 1226653506056503634668815848352741482067480791322607584496401451909331743178, ), + ( + [ + TransactionHashPrefix.INVOKE, + 0x0, + 0x2A, + 0x64, + [], + 0x0, + StarknetChainId.TESTNET.value, + ], + 0x7D260744DE9D8C55E7675A34512D1951A7B262C79E685D26599EDD2948DE959, + ), ), ) -def test_compute_transaction_hash(data, calculated_hash): - assert compute_transaction_hash(*data) == calculated_hash +def test_compute_transaction_hash(data, expected_hash): + assert compute_transaction_hash(*data) == expected_hash @pytest.mark.parametrize( - "data, calculated_hash", + "data, expected_hash", ( ( - [2, 3, 4, [5], 6, 7, 8, 9], - 3319639522829811634906602140344071246050815451799261765214603967516640029516, - ), - ( - [12, 23, 34, [45], 56, 67, 78, 89], - 1704331554042454954615983716430494560849200211800542196314933915246556687567, + { + "contract_address": 2, + "class_hash": 3, + "constructor_calldata": [4], + "salt": 5, + "version": 6, + "max_fee": 7, + "chain_id": 8, + "nonce": 9, + }, + 0x6199E956E541CBB06589C4A63C2578A8ED6B697C0FA35B002F48923DFE648EE, ), ), ) -def test_compute_deploy_account_transaction_hash(data, calculated_hash): - assert compute_deploy_account_transaction_hash(*data) == calculated_hash +def test_compute_deploy_account_transaction_hash(data, expected_hash): + assert compute_deploy_account_transaction_hash(**data) == expected_hash @pytest.mark.parametrize( @@ -69,29 +81,41 @@ def test_compute_declare_transaction_hash(contract_json, data): @pytest.mark.parametrize( - "sierra_contract_class_source", - ["account_compiled", "erc20_compiled", "minimal_contract_compiled"], + "data, expected_hash", + ( + ( + { + "class_hash": 2, + "sender_address": 3, + "version": 4, + "max_fee": 5, + "chain_id": 6, + "nonce": 7, + "compiled_class_hash": 8, + }, + 0x67EA411072DD2EF3BA36D9680F040A02E599F80F4770E204ECBB2C47C226793, + ), + ), ) -def test_compute_declare_v2_transaction_hash(sierra_contract_class_source): - compiled_contract = read_contract( - f"{sierra_contract_class_source}.json", directory=CONTRACTS_COMPILED_V1_DIR - ) - compiled_contract_casm = read_contract( - f"{sierra_contract_class_source}.casm", directory=CONTRACTS_COMPILED_V1_DIR - ) - casm_class = create_casm_class(compiled_contract_casm) - casm_class_hash = compute_casm_class_hash(casm_class) - - compiled_contract = create_sierra_compiled_contract(compiled_contract) +def test_compute_declare_v2_transaction_hash(data, expected_hash): + assert compute_declare_v2_transaction_hash(**data) == expected_hash - declare_v2_hash = compute_declare_v2_transaction_hash( - contract_class=compiled_contract, - compiled_class_hash=casm_class_hash, - chain_id=StarknetChainId.TESTNET.value, - sender_address=0x1, - max_fee=2000, - version=2, - nonce=23, - ) - assert declare_v2_hash > 0 +@pytest.mark.parametrize( + "data, expected_hash", + ( + ( + { + "sender_address": 3, + "version": 4, + "calldata": [5], + "max_fee": 6, + "chain_id": 7, + "nonce": 8, + }, + 0x505BBF7CD810531C53526631078DAA314BFD036C80C7C6E3A02C608DB8E31DE, + ), + ), +) +def test_compute_invoke_transaction_hash(data, expected_hash): + assert compute_invoke_transaction_hash(**data) == expected_hash diff --git a/starknet_py/net/models/__init__.py b/starknet_py/net/models/__init__.py index 4b78d8f5c..736b9b9a8 100644 --- a/starknet_py/net/models/__init__.py +++ b/starknet_py/net/models/__init__.py @@ -7,5 +7,4 @@ DeployAccount, Invoke, Transaction, - compute_invoke_hash, ) diff --git a/starknet_py/net/models/transaction.py b/starknet_py/net/models/transaction.py index 90e6ca271..81884d8a3 100644 --- a/starknet_py/net/models/transaction.py +++ b/starknet_py/net/models/transaction.py @@ -7,24 +7,20 @@ import base64 import gzip import json -import warnings from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import Any, Dict, List, Sequence, TypeVar, Union +from typing import Any, Dict, List, TypeVar import marshmallow import marshmallow_dataclass from marshmallow import fields -from starknet_py.constants import DEFAULT_ENTRY_POINT_SELECTOR from starknet_py.hash.address import compute_address -from starknet_py.hash.selector import get_selector_from_name from starknet_py.hash.transaction import ( - TransactionHashPrefix, compute_declare_transaction_hash, compute_declare_v2_transaction_hash, compute_deploy_account_transaction_hash, - compute_transaction_hash, + compute_invoke_transaction_hash, ) from starknet_py.net.client_models import ( ContractClass, @@ -213,15 +209,13 @@ def calculate_hash(self, chain_id: StarknetChainId) -> int: """ Calculates the transaction hash in the Starknet network. """ - return compute_transaction_hash( - tx_hash_prefix=TransactionHashPrefix.INVOKE, + return compute_invoke_transaction_hash( version=self.version, - contract_address=self.sender_address, - entry_point_selector=DEFAULT_ENTRY_POINT_SELECTOR, + sender_address=self.sender_address, calldata=self.calldata, max_fee=self.max_fee, chain_id=chain_id.value, - additional_data=[self.nonce], + nonce=self.nonce, ) @@ -231,51 +225,6 @@ def calculate_hash(self, chain_id: StarknetChainId) -> int: DeployAccountSchema = marshmallow_dataclass.class_schema(DeployAccount) -def compute_invoke_hash( - sender_address: int, - entry_point_selector: Union[int, str], - calldata: Sequence[int], - chain_id: StarknetChainId, - max_fee: int, - version: int, -) -> int: - # pylint: disable=too-many-arguments - """ - Computes invocation hash. - - .. deprecated:: 0.15.0 - To compute hash of an invoke transaction use - :py:meth:`~starknet_py.hash.transaction.compute_transaction_hash`. - - :param sender_address: int - :param entry_point_selector: Union[int, str] - :param calldata: Sequence[int] - :param chain_id: StarknetChainId - :param max_fee: Max fee - :param version: Contract version - :return: calculated hash - """ - warnings.warn( - "Function compute_invoke_hash is deprecated." - "To compute hash of an invoke transaction use compute_transaction_hash.", - category=DeprecationWarning, - ) - - if isinstance(entry_point_selector, str): - entry_point_selector = get_selector_from_name(entry_point_selector) - - return compute_transaction_hash( - tx_hash_prefix=TransactionHashPrefix.INVOKE, - contract_address=sender_address, - entry_point_selector=entry_point_selector, - calldata=calldata, - chain_id=chain_id.value, - additional_data=[], - max_fee=max_fee, - version=version, - ) - - def compress_program(data: dict, program_name: str = "program") -> dict: program = data["contract_class"][program_name] compressed_program = json.dumps(program) diff --git a/starknet_py/net/models/transaction_test.py b/starknet_py/net/models/transaction_test.py index 03997cf70..982c3107e 100644 --- a/starknet_py/net/models/transaction_test.py +++ b/starknet_py/net/models/transaction_test.py @@ -2,46 +2,18 @@ import typing from typing import cast -import pytest - -from starknet_py.common import ( - create_compiled_contract, - create_contract_class, - create_sierra_compiled_contract, -) +from starknet_py.common import create_contract_class from starknet_py.net.client_models import TransactionType -from starknet_py.net.models import StarknetChainId from starknet_py.net.models.transaction import ( Declare, DeclareSchema, - DeclareV2, - DeployAccount, Invoke, InvokeSchema, - compute_invoke_hash, ) from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR from starknet_py.tests.e2e.fixtures.misc import read_contract -def test_invoke_hash(): - for selector in [ - "increase_balance", - 1530486729947006463063166157847785599120665941190480211966374137237989315360, - ]: - assert ( - compute_invoke_hash( - entry_point_selector=selector, - sender_address=0x03606DB92E563E41F4A590BC01C243E8178E9BA8C980F8E464579F862DA3537C, - calldata=[1234], - chain_id=StarknetChainId.TESTNET, - version=0, - max_fee=0, - ) - == 0xD0A52D6E77B836613B9F709AD7F4A88297697FEFBEF1ADA3C59692FF46702C - ) - - def test_declare_compress_program(balance_contract): contract_class = create_contract_class(balance_contract) declare_transaction = Declare( @@ -73,67 +45,6 @@ def test_declare_compress_program(balance_contract): ) -@pytest.mark.parametrize( - "transaction, calculated_hash", - [ - ( - Invoke( - sender_address=0x1, - calldata=[1, 2, 3], - max_fee=10000, - signature=[], - nonce=23, - version=1, - ), - 3484767022419258107070028252604380065385354331198975073942248877262069264133, - ), - ( - DeployAccount( - class_hash=0x1, - contract_address_salt=0x2, - constructor_calldata=[1, 2, 3, 4], - max_fee=10000, - signature=[], - nonce=23, - version=1, - ), - 1258460340144554539989794559757396219553018532617589681714052999991876798273, - ), - ( - Declare( - contract_class=create_compiled_contract( - compiled_contract=compiled_contract - ), - sender_address=123, - max_fee=10000, - signature=[], - nonce=23, - version=1, - ), - 548241482519463597399416578757678814995754071952538857702978733086902207659, - ), - ( - DeclareV2( - contract_class=create_sierra_compiled_contract( - compiled_contract=sierra_compiled_contract - ), - compiled_class_hash=0x1, - max_fee=1000, - nonce=20, - sender_address=0x1234, - signature=[0x1, 0x2], - version=2, - ), - 2391287073123315831211443928603796208441862227055564920937005298570351208379, - ), - ], -) -def test_calculate_transaction_hash(transaction, calculated_hash): - assert ( - transaction.calculate_hash(chain_id=StarknetChainId.TESTNET) == calculated_hash - ) - - def test_serialize_deserialize_invoke(): data = { "sender_address": "0x1", From 5200646719c1a8629e809d53882dc68b6d6daedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C5=9Bkowicz?= Date: Fri, 23 Jun 2023 09:20:42 +0200 Subject: [PATCH 11/17] RPC v0.3.0 (#1065) * set devnet version to 0.5.3 * update get_events to match RPCv0.3.0 * Declare now matches RPC * ABI entries match RPC * remove comments * transaction receipts matching RPC * lint * fixed parser * poetry.lock update * another comment removal * import dependent of python version * added missing parameter to transaction receipt * get_events without breaking change * set devnet version to 0.5.4 * changes to parser.py * removed GatewayTransactionReceipt * formatting * add codecov test * fix getEvents and add docs example * parser fix and test * lint, format * changed test, added one * docs test fix --- poetry.lock | 187 +++++++++++++++--- pyproject.toml | 2 +- starknet_py/abi/parser.py | 15 +- starknet_py/abi/parser_test.py | 32 +++ starknet_py/abi/schemas.py | 2 +- starknet_py/abi/shape.py | 11 +- starknet_py/constants.py | 2 +- starknet_py/net/client_models.py | 3 + starknet_py/net/full_node_client.py | 24 ++- starknet_py/net/schemas/rpc.py | 4 + starknet_py/proxy/contract_abi_resolver.py | 4 +- starknet_py/tests/e2e/client/client_test.py | 36 +++- .../tests/e2e/client/full_node_test.py | 45 ++++- .../contract_interaction/interaction_test.py | 9 +- .../code_examples/test_full_node_client.py | 18 ++ .../tests/e2e/fixtures/abi_structures.py | 32 +++ 16 files changed, 373 insertions(+), 53 deletions(-) diff --git a/poetry.lock b/poetry.lock index db020306a..4de27ac6a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.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.4" 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.4" 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.7.3" description = "efficient arrays of booleans -- C extension" +category = "dev" optional = false python-versions = "*" files = [ @@ -380,6 +393,7 @@ files = [ name = "black" version = "23.3.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -429,6 +443,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 = [ @@ -449,6 +464,7 @@ redis = ["redis (>=2.10.5)"] name = "cachetools" version = "5.3.0" description = "Extensible memoizing collections and decorators" +category = "dev" optional = false python-versions = "~=3.7" files = [ @@ -460,6 +476,7 @@ files = [ name = "cairo-lang" version = "0.11.2" description = "Compiler and runner for the Cairo language" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -493,6 +510,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 = [ @@ -504,6 +522,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 = [ @@ -518,6 +537,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 = [ @@ -532,6 +552,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 = [ @@ -543,6 +564,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 = [ @@ -554,6 +576,7 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -629,6 +652,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 = [ @@ -695,6 +719,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 = [ @@ -710,6 +735,7 @@ testing = ["cssselect", "flake8 (<5)", "importlib-resources", "jaraco.test (>=5. name = "cytoolz" version = "0.12.0" description = "Cython implementation of Toolz: High performance functional utilities" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -809,6 +835,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 = [ @@ -824,6 +851,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 = [ @@ -838,6 +866,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 = [ @@ -849,6 +878,7 @@ files = [ name = "domdf-python-tools" version = "3.5.0" description = "Helpful functions for Python 🐍 🛠️" +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -869,6 +899,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 = [ @@ -887,6 +918,7 @@ gmpy2 = ["gmpy2"] name = "enum-tools" version = "0.9.0.post1" description = "Tools to expand Python's enum module." +category = "main" optional = true python-versions = ">=3.6" files = [ @@ -908,6 +940,7 @@ sphinx = ["sphinx (>=3.2.0)", "sphinx-toolbox (>=2.16.0)"] name = "eth-abi" version = "4.0.0" description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -931,6 +964,7 @@ tools = ["hypothesis (>=4.18.2,<5.0.0)"] name = "eth-account" version = "0.8.0" description = "eth-account: Sign Ethereum transactions and messages with local private keys" +category = "dev" optional = false python-versions = ">=3.6, <4" files = [ @@ -958,6 +992,7 @@ test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=6.2.5,<7)", "pytest-x name = "eth-hash" version = "0.5.1" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -980,6 +1015,7 @@ test = ["pytest (>=6.2.5,<7)", "pytest-xdist (>=2.4.0,<3)", "tox (>=3.14.6,<4)"] 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 = [ @@ -1002,6 +1038,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 = [ @@ -1024,6 +1061,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 = [ @@ -1046,6 +1084,7 @@ test = ["eth-hash[pycryptodome]", "pytest (>=6.2.5,<7)", "pytest-xdist", "tox (= name = "eth-typing" version = "3.3.0" description = "eth-typing: Common type annotations for ethereum python packages" +category = "dev" optional = false python-versions = ">=3.7.2, <4" files = [ @@ -1063,6 +1102,7 @@ test = ["pytest (>=6.2.5,<7)", "pytest-xdist", "tox (>=2.9.1,<3)"] name = "eth-utils" version = "2.1.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +category = "dev" optional = false python-versions = ">=3.7,<4" files = [ @@ -1086,6 +1126,7 @@ test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (>=6.2.5,<7)", "pytest-xdist", " name = "exceptiongroup" version = "1.1.1" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1100,6 +1141,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 = [ @@ -1114,6 +1156,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 = [ @@ -1133,6 +1176,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 = [ @@ -1155,6 +1199,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 = [ @@ -1170,6 +1215,7 @@ Six = "*" name = "frozendict" version = "1.2" description = "An immutable dictionary" +category = "dev" optional = false python-versions = "*" files = [ @@ -1180,6 +1226,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 = [ @@ -1263,6 +1310,7 @@ files = [ name = "furo" version = "2023.5.20" description = "A clean customisable Sphinx documentation theme." +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1280,6 +1328,7 @@ sphinx-basic-ng = "*" name = "gunicorn" version = "20.1.0" description = "WSGI HTTP Server for UNIX" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1300,6 +1349,7 @@ tornado = ["tornado (>=0.2)"] name = "hexbytes" version = "0.3.0" description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +category = "dev" optional = false python-versions = ">=3.7, <4" files = [ @@ -1317,6 +1367,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 = [ @@ -1338,6 +1389,7 @@ lxml = ["lxml"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1349,6 +1401,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 = [ @@ -1360,6 +1413,7 @@ files = [ name = "importlib-metadata" version = "5.1.0" description = "Read metadata from Python packages" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1379,6 +1433,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 = [ @@ -1390,6 +1445,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 = [ @@ -1407,6 +1463,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 = [ @@ -1418,6 +1475,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 = [ @@ -1435,6 +1493,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 = [ @@ -1454,6 +1513,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "lark" version = "1.1.5" description = "a modern parsing library" +category = "main" optional = false python-versions = "*" files = [ @@ -1470,6 +1530,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 = [ @@ -1498,6 +1559,7 @@ files = [ name = "lockfile" version = "0.12.2" description = "Platform-independent file locking module" +category = "main" optional = true python-versions = "*" files = [ @@ -1509,6 +1571,7 @@ files = [ name = "lru-dict" version = "1.1.8" description = "An Dict like LRU container." +category = "dev" optional = false python-versions = "*" files = [ @@ -1569,6 +1632,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 = [ @@ -1618,6 +1682,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 = [ @@ -1638,6 +1703,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 = [ @@ -1661,6 +1727,7 @@ union = ["typeguard"] name = "marshmallow-enum" version = "1.5.1" description = "Enum field for Marshmallow" +category = "dev" optional = false python-versions = "*" files = [ @@ -1675,6 +1742,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 = [ @@ -1694,6 +1762,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 = [ @@ -1705,6 +1774,7 @@ files = [ name = "mpmath" version = "1.2.1" description = "Python library for arbitrary-precision floating-point arithmetic" +category = "main" optional = false python-versions = "*" files = [ @@ -1720,6 +1790,7 @@ tests = ["pytest (>=4.6)"] name = "msgpack" version = "1.0.4" description = "MessagePack serializer" +category = "main" optional = true python-versions = "*" files = [ @@ -1781,6 +1852,7 @@ files = [ name = "multidict" version = "6.0.2" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1849,6 +1921,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 = [ @@ -1860,6 +1933,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 = [ @@ -1875,6 +1949,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 = [ @@ -1889,6 +1964,7 @@ setuptools = "*" name = "numpy" version = "1.23.5" description = "NumPy is the fundamental package for array computing with Python." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1926,6 +2002,7 @@ files = [ name = "packaging" version = "23.0" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1937,6 +2014,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 = [ @@ -1950,6 +2028,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 = [ @@ -1961,6 +2040,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 = [ @@ -1972,6 +2052,7 @@ files = [ name = "pipdeptree" version = "2.3.3" description = "Command line utility to show dependency tree of packages." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1987,6 +2068,7 @@ test = ["covdefaults (>=2.2)", "diff-cover (>=7.0.1)", "pip (>=22.3)", "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 = [ @@ -2002,6 +2084,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 = [ @@ -2017,6 +2100,7 @@ testing = ["pytest", "pytest-benchmark"] name = "poethepoet" version = "0.20.0" description = "A task runner that works well with poetry." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2035,6 +2119,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 = [ @@ -2093,6 +2178,7 @@ files = [ name = "prometheus-client" version = "0.15.0" description = "Python client for the Prometheus monitoring system." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2107,6 +2193,7 @@ twisted = ["twisted"] name = "protobuf" version = "4.22.1" description = "" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2129,6 +2216,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 = [ @@ -2170,6 +2258,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 = [ @@ -2184,6 +2273,7 @@ plugins = ["importlib-metadata"] name = "pylint" version = "2.17.4" description = "python code static checker" +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -2213,6 +2303,7 @@ testutils = ["gitpython (>3)"] name = "pyright" version = "1.1.314" description = "Command line wrapper for pyright" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2231,6 +2322,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 = [ @@ -2267,6 +2359,7 @@ files = [ name = "pytest" version = "7.3.2" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2289,6 +2382,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.20.3" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2307,6 +2401,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 = [ @@ -2325,6 +2420,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-mock" version = "3.10.0" description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2342,6 +2438,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"] name = "pytest-rerunfailures" version = "11.1.2" description = "pytest plugin to re-run tests to eliminate flaky failures" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2357,6 +2454,7 @@ pytest = ">=5.3" 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 = [ @@ -2377,6 +2475,7 @@ testing = ["filelock"] name = "pytz" version = "2022.6" description = "World timezone definitions, modern and historical" +category = "main" optional = true python-versions = "*" files = [ @@ -2388,6 +2487,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "main" optional = false python-versions = "*" files = [ @@ -2411,6 +2511,7 @@ files = [ name = "PyYAML" version = "6.0" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2460,6 +2561,7 @@ files = [ name = "regex" version = "2023.3.23" description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2529,6 +2631,7 @@ files = [ name = "requests" version = "2.28.1" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7, <4" files = [ @@ -2550,6 +2653,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 = [ @@ -2571,6 +2675,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 = [ @@ -2589,6 +2694,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 = [ @@ -2599,8 +2705,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"}, @@ -2635,6 +2740,7 @@ files = [ name = "setuptools" version = "67.8.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2651,6 +2757,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 = [ @@ -2662,6 +2769,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 = [ @@ -2673,6 +2781,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 = [ @@ -2684,6 +2793,7 @@ files = [ name = "sphinx" version = "7.0.1" description = "Python documentation generator" +category = "main" optional = true python-versions = ">=3.8" files = [ @@ -2719,6 +2829,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 = [ @@ -2738,6 +2849,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 = [ @@ -2755,6 +2867,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 = [ @@ -2770,6 +2883,7 @@ markupsafe = ">=1" name = "sphinx-prompt" version = "1.5.0" description = "Sphinx directive to add unselectable prompt" +category = "main" optional = true python-versions = "*" files = [ @@ -2784,6 +2898,7 @@ Sphinx = "*" name = "sphinx-tabs" version = "3.4.1" description = "Tabbed views for Sphinx" +category = "main" optional = true python-versions = "~=3.7" files = [ @@ -2804,6 +2919,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 = [ @@ -2838,6 +2954,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 = [ @@ -2853,6 +2970,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 = [ @@ -2868,6 +2986,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 = [ @@ -2883,6 +3002,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 = [ @@ -2897,6 +3017,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 = [ @@ -2912,6 +3033,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 = [ @@ -2925,38 +3047,36 @@ test = ["pytest"] [[package]] name = "starknet-devnet" -version = "0.5.2" +version = "0.5.4" description = "A local testnet for Starknet" +category = "dev" optional = false python-versions = ">=3.9,<3.10" -files = [] -develop = false +files = [ + {file = "starknet_devnet-0.5.4-py3-none-any.whl", hash = "sha256:cd22efa4ab222057dd602aee23b0bc37d979d7aceb038937eb17b64ae6fb9117"}, + {file = "starknet_devnet-0.5.4.tar.gz", hash = "sha256:b76cc9c128f3625b48e57c6d8f0e8a189fc894c628d458ad25b904831828ae46"}, +] [package.dependencies] cairo-lang = "0.11.2" -cloudpickle = "~2.1.0" -crypto-cpp-py = "~1.4.0" -Flask = {version = "~2.0.3", extras = ["async"]} -flask-cors = "~3.0.10" -gunicorn = "~20.1.0" -jsonschema = "~4.17.0" -marshmallow = "~3.17.0" -marshmallow-dataclass = "~8.4" -poseidon-py = "~0.1.3" -typing-extensions = "~4.3.0" -web3 = "~6.0.0" -Werkzeug = "~2.0.3" - -[package.source] -type = "git" -url = "https://github.com/0xSpaceShard/starknet-devnet.git" -reference = "744e9b3bd5fb9e856287158d87673e090df69d73" -resolved_reference = "744e9b3bd5fb9e856287158d87673e090df69d73" +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"]} +flask-cors = ">=3.0.10,<3.1.0" +gunicorn = ">=20.1.0,<20.2.0" +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" +typing-extensions = ">=4.3.0,<4.4.0" +web3 = ">=6.0.0,<6.1.0" +Werkzeug = ">=2.0.3,<2.1.0" [[package]] name = "sympy" version = "1.11.1" description = "Computer algebra system (CAS) in Python" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2971,6 +3091,7 @@ mpmath = ">=0.19" name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" +category = "main" optional = true python-versions = ">=3.7" files = [ @@ -2985,6 +3106,7 @@ widechars = ["wcwidth"] name = "tomli" version = "1.2.3" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2996,6 +3118,7 @@ files = [ name = "tomlkit" version = "0.11.6" description = "Style preserving TOML library" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3007,6 +3130,7 @@ files = [ name = "toolz" version = "0.12.0" description = "List processing tools and functional utilities" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3018,6 +3142,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 = [ @@ -3033,6 +3158,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 = [ @@ -3044,6 +3170,7 @@ files = [ name = "typing-inspect" version = "0.8.0" description = "Runtime inspection utilities for typing module." +category = "main" optional = false python-versions = "*" files = [ @@ -3059,6 +3186,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 = [ @@ -3075,6 +3203,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 = [ @@ -3109,6 +3238,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 = [ @@ -3120,6 +3250,7 @@ files = [ name = "websockets" version = "11.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3199,6 +3330,7 @@ files = [ name = "werkzeug" version = "2.0.3" description = "The comprehensive WSGI web application library." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3213,6 +3345,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 = [ @@ -3286,6 +3419,7 @@ files = [ name = "yarl" version = "1.8.1" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3358,6 +3492,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 = [ @@ -3370,9 +3505,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 = "5761bfcc8b95b4d6845a94efbc6a3298bfb0c78b1dee2bdcd1d25a70ae140f5c" +content-hash = "6b79107f3f74b1f8a0bcd6728449a183dd7265156be6b569407e0832d7fa13fb" diff --git a/pyproject.toml b/pyproject.toml index 2c0b44412..7fb38e22f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ pytest-rerunfailures = "^11.1" [tool.poetry.group.py39-dev.dependencies] cairo-lang = { version = "^0.11.2", python= ">=3.9, <3.10" } -starknet-devnet = { git = "https://github.com/0xSpaceShard/starknet-devnet.git", rev = "744e9b3bd5fb9e856287158d87673e090df69d73", python= ">=3.9, <3.10" } +starknet-devnet = {version = "0.5.4", 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" diff --git a/starknet_py/abi/parser.py b/starknet_py/abi/parser.py index 0d6a3e036..742124a7a 100644 --- a/starknet_py/abi/parser.py +++ b/starknet_py/abi/parser.py @@ -125,9 +125,22 @@ def _parse_structures(self) -> Dict[str, StructType]: # topological sorting with an additional "unresolved type", so this flow is much easier. for name, struct in structs_dict.items(): structs[name] = StructType(name, OrderedDict()) + without_offset = [ + member for member in struct["members"] if member.get("offset") is None + ] + with_offset = [ + member for member in struct["members"] if member not in without_offset + ] struct_members[name] = sorted( - struct["members"], key=lambda member: member["offset"] + with_offset, key=lambda member: member["offset"] # pyright: ignore ) + for member in without_offset: + member["offset"] = ( + struct_members[name][-1].get("offset", 0) + 1 + if struct_members[name] + else 0 + ) + struct_members[name].append(member) # Now parse the types of members and save them. self._type_parser = TypeParser(structs) diff --git a/starknet_py/abi/parser_test.py b/starknet_py/abi/parser_test.py index 65de7011a..a112273e0 100644 --- a/starknet_py/abi/parser_test.py +++ b/starknet_py/abi/parser_test.py @@ -37,6 +37,38 @@ def test_parsing_types_abi(): } +def test_parsing_types_abi_missing_offset(): + abi = AbiParser( + [ + fixtures.user_missing_offset_dict, + fixtures.uint256_dict, + fixtures.pool_id_dict, + ] + ).parse() + + assert abi.defined_structures == { + "Uint256": fixtures.uint256_struct, + "PoolId": fixtures.pool_id_struct, + "User": fixtures.user_struct, + } + + +def test_parsing_types_abi_partial_missing_offset(): + abi = AbiParser( + [ + fixtures.user_partial_missing_offset_dict, + fixtures.uint256_dict, + fixtures.pool_id_dict, + ] + ).parse() + + assert abi.defined_structures == { + "Uint256": fixtures.uint256_struct, + "PoolId": fixtures.pool_id_struct, + "User": fixtures.user_partial_missing_offset_struct, + } + + def test_self_cycle(): self_referencing_struct = { "type": "struct", diff --git a/starknet_py/abi/schemas.py b/starknet_py/abi/schemas.py index 62c6e8c32..3bfa50353 100644 --- a/starknet_py/abi/schemas.py +++ b/starknet_py/abi/schemas.py @@ -16,7 +16,7 @@ class TypedParameterSchema(Schema): class StructMemberSchema(TypedParameterSchema): - offset = fields.Integer(data_key="offset", required=True) + offset = fields.Integer(data_key="offset", required=False) class FunctionBaseSchema(Schema): diff --git a/starknet_py/abi/shape.py b/starknet_py/abi/shape.py index 90da8765b..b2b8a52cd 100644 --- a/starknet_py/abi/shape.py +++ b/starknet_py/abi/shape.py @@ -1,4 +1,10 @@ -from typing import List, Literal, TypedDict, Union +import sys +from typing import List, Literal, Union + +if sys.version_info < (3, 11): + from typing_extensions import NotRequired, TypedDict +else: + from typing import NotRequired, TypedDict STRUCT_ENTRY = "struct" FUNCTION_ENTRY = "function" @@ -13,7 +19,7 @@ class TypedMemberDict(TypedDict): class StructMemberDict(TypedMemberDict): - offset: int + offset: NotRequired[int] class StructDict(TypedDict): @@ -27,6 +33,7 @@ class FunctionBaseDict(TypedDict): name: str inputs: List[TypedMemberDict] outputs: List[TypedMemberDict] + stateMutability: NotRequired[Literal["view"]] class FunctionDict(FunctionBaseDict): diff --git a/starknet_py/constants.py b/starknet_py/constants.py index 62d35484f..07bd3e5b7 100644 --- a/starknet_py/constants.py +++ b/starknet_py/constants.py @@ -12,8 +12,8 @@ API_VERSION = 0 RPC_CONTRACT_NOT_FOUND_ERROR = 20 -RPC_INVALID_MESSAGE_SELECTOR_ERROR = 21 RPC_CLASS_HASH_NOT_FOUND_ERROR = 28 +RPC_CONTRACT_ERROR = 40 DEFAULT_ENTRY_POINT_NAME = "__default__" DEFAULT_L1_ENTRY_POINT_NAME = "__l1_default__" diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index bc8fee608..0a3c0a03e 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -68,6 +68,7 @@ class L2toL1Message: Dataclass representing a L2->L1 message. """ + from_address: int payload: List[int] l1_address: int l2_address: Optional[int] = None @@ -183,6 +184,8 @@ class TransactionReceipt: hash: int status: TransactionStatus + type: Optional[TransactionType] = None + contract_address: Optional[int] = None block_number: Optional[int] = None block_hash: Optional[int] = None actual_fee: int = 0 diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index ab5796d9b..8dd3868cb 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -113,8 +113,8 @@ async def get_block_traces( # TODO (#809): add tests with multiple emitted keys async def get_events( self, - address: Hash, - keys: List[List[str]], + address: Optional[Hash] = None, + keys: Optional[List[List[Hash]]] = None, *, from_block_number: Optional[Union[int, Tag]] = None, from_block_hash: Optional[Union[Hash, Tag]] = None, @@ -132,12 +132,16 @@ async def get_events( the first key, any value for their second key and 3 for their third key. :param from_block_number: Number of the block from which events searched for **starts** or literals `"pending"` or `"latest"`. Mutually exclusive with ``from_block_hash`` parameter. + If not provided, query starts from block 0. :param from_block_hash: Hash of the block from which events searched for **starts** or literals `"pending"` or `"latest"`. Mutually exclusive with ``from_block_number`` parameter. + If not provided, query starts from block 0. :param to_block_number: Number of the block to which events searched for **end** or literals `"pending"` or `"latest"`. Mutually exclusive with ``to_block_hash`` parameter. + If not provided, query ends at block `"pending"`. :param to_block_hash: Hash of the block to which events searched for **end** or literals `"pending"` or `"latest"`. Mutually exclusive with ``to_block_number`` parameter. + If not provided, query ends at block `"pending"`. :param follow_continuation_token: Flag deciding whether all events should be collected during one function call, defaults to False. :param continuation_token: Continuation token from which the returned events start. @@ -149,9 +153,16 @@ async def get_events( if chunk_size <= 0: raise ValueError("Argument chunk_size must be greater than 0.") + if keys is None: + keys = [] + if address is not None: + address = _to_rpc_felt(address) + if from_block_number is None and from_block_hash is None: + from_block_number = 0 + from_block = _get_raw_block_identifier(from_block_hash, from_block_number) to_block = _get_raw_block_identifier(to_block_hash, to_block_number) - address = _to_rpc_felt(address) + keys = [[_to_rpc_felt(key) for key in inner_list] for inner_list in keys] events_list = [] while True: @@ -180,9 +191,9 @@ async def _get_events_chunk( self, from_block: Union[dict, Hash, Tag, None], to_block: Union[dict, Hash, Tag, None], - address: Hash, - keys: List[List[str]], + keys: List[List[Hash]], chunk_size: int, + address: Optional[Hash] = None, continuation_token: Optional[str] = None, ) -> Tuple[list, Optional[str]]: # pylint: disable=too-many-arguments @@ -190,11 +201,12 @@ async def _get_events_chunk( "chunk_size": chunk_size, "from_block": from_block, "to_block": to_block, - "address": address, "keys": keys, } if continuation_token is not None: params["continuation_token"] = continuation_token + if address is not None: + params["address"] = address res = await self._client.call( method_name="getEvents", diff --git a/starknet_py/net/schemas/rpc.py b/starknet_py/net/schemas/rpc.py index b2723cf17..3a7cb43fc 100644 --- a/starknet_py/net/schemas/rpc.py +++ b/starknet_py/net/schemas/rpc.py @@ -39,6 +39,7 @@ NonPrefixedHex, StatusField, StorageEntrySchema, + TransactionTypeField, ) from starknet_py.net.schemas.utils import ( _replace_invoke_contract_address_with_sender_address, @@ -104,6 +105,8 @@ class TransactionReceiptSchema(Schema): block_number = fields.Integer(data_key="block_number", load_default=None) block_hash = Felt(data_key="block_hash", load_default=None) actual_fee = Felt(data_key="actual_fee", required=True) + type = TransactionTypeField(data_key="type", load_default=None) + contract_address = Felt(data_key="contract_address", load_default=None) rejection_reason = fields.String(data_key="status_data", load_default=None) events = fields.List( fields.Nested(EventSchema()), data_key="events", load_default=[] @@ -154,6 +157,7 @@ class DeclareTransactionSchema(TransactionSchema): class_hash = Felt(data_key="class_hash", required=True) sender_address = Felt(data_key="sender_address", required=True) nonce = Felt(data_key="nonce", load_default=None) + compiled_class_hash = Felt(data_key="compiled_class_hash", load_default=None) @post_load def make_dataclass(self, data, **kwargs) -> DeclareTransaction: diff --git a/starknet_py/proxy/contract_abi_resolver.py b/starknet_py/proxy/contract_abi_resolver.py index 466d81425..e841e86fd 100644 --- a/starknet_py/proxy/contract_abi_resolver.py +++ b/starknet_py/proxy/contract_abi_resolver.py @@ -5,8 +5,8 @@ from starknet_py.abi.shape import AbiDictList from starknet_py.constants import ( RPC_CLASS_HASH_NOT_FOUND_ERROR, + RPC_CONTRACT_ERROR, RPC_CONTRACT_NOT_FOUND_ERROR, - RPC_INVALID_MESSAGE_SELECTOR_ERROR, ) from starknet_py.net.client import Client from starknet_py.net.client_errors import ClientError, ContractNotFoundError @@ -164,9 +164,9 @@ async def _get_implementation_from_proxy( re.search(err_msg, err.message, re.IGNORECASE) or err.code in [ - RPC_INVALID_MESSAGE_SELECTOR_ERROR, RPC_CLASS_HASH_NOT_FOUND_ERROR, RPC_CONTRACT_NOT_FOUND_ERROR, + RPC_CONTRACT_ERROR, ] ): raise err diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index 1b7320f5a..ee496e421 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -23,6 +23,7 @@ SierraEntryPointsByType, TransactionReceipt, TransactionStatus, + TransactionType, ) from starknet_py.net.full_node_client import FullNodeClient from starknet_py.net.gateway_client import GatewayClient @@ -129,6 +130,8 @@ async def test_get_transaction_receipt( assert receipt.hash == invoke_transaction_hash assert receipt.block_number == block_with_invoke_number + if isinstance(client, FullNodeClient): + assert receipt.type == TransactionType.INVOKE @pytest.mark.asyncio @@ -224,6 +227,8 @@ async def test_add_transaction(map_contract, client, account): transaction_receipt = await client.get_transaction_receipt(result.transaction_hash) assert transaction_receipt.status != TransactionStatus.NOT_RECEIVED + if isinstance(client, FullNodeClient): + assert transaction_receipt.type == TransactionType.INVOKE @pytest.mark.asyncio @@ -266,7 +271,10 @@ async def test_wait_for_tx_accepted(client, get_tx_receipt, request): AsyncMock(), ) as mocked_receipt: mocked_receipt.return_value = TransactionReceipt( - hash=0x1, status=TransactionStatus.ACCEPTED_ON_L2, block_number=1 + hash=0x1, + status=TransactionStatus.ACCEPTED_ON_L2, + block_number=1, + type=TransactionType.INVOKE, ) client = request.getfixturevalue(client) block_number, tx_status = await client.wait_for_tx(tx_hash=0x1) @@ -296,7 +304,10 @@ async def test_wait_for_tx_pending(client, get_tx_receipt, request): AsyncMock(), ) as mocked_receipt: mocked_receipt.return_value = TransactionReceipt( - hash=0x1, status=TransactionStatus.PENDING, block_number=1 + hash=0x1, + status=TransactionStatus.PENDING, + block_number=1, + type=TransactionType.INVOKE, ) client = request.getfixturevalue(client) @@ -344,7 +355,11 @@ async def test_wait_for_tx_rejected( AsyncMock(), ) as mocked_receipt: mocked_receipt.return_value = TransactionReceipt( - hash=0x1, status=status, block_number=1, rejection_reason=exc_message + hash=0x1, + status=status, + block_number=1, + rejection_reason=exc_message, + type=TransactionType.INVOKE, ) client = request.getfixturevalue(client) with pytest.raises(exception) as err: @@ -375,7 +390,10 @@ async def test_wait_for_tx_cancelled(client, get_tx_receipt, request): AsyncMock(), ) as mocked_receipt: mocked_receipt.return_value = TransactionReceipt( - hash=0x1, status=TransactionStatus.PENDING, block_number=1 + hash=0x1, + status=TransactionStatus.PENDING, + block_number=1, + type=TransactionType.INVOKE, ) client = request.getfixturevalue(client) task = asyncio.create_task( @@ -402,6 +420,8 @@ async def test_declare_contract(map_compiled_contract, account): assert transaction_receipt.status != TransactionStatus.NOT_RECEIVED assert transaction_receipt.hash assert 0 < transaction_receipt.actual_fee <= MAX_FEE + if isinstance(client, FullNodeClient): + assert transaction_receipt.type == TransactionType.DECLARE @pytest.mark.asyncio @@ -577,9 +597,7 @@ async def test_get_declare_v2_transaction( assert isinstance(transaction, DeclareTransaction) assert transaction == DeclareTransaction( class_hash=class_hash, - compiled_class_hash=declare_v2_hello_starknet.compiled_class_hash - if isinstance(client, GatewayClient) - else None, + compiled_class_hash=declare_v2_hello_starknet.compiled_class_hash, sender_address=declare_v2_hello_starknet.sender_address, hash=tx_hash, max_fee=declare_v2_hello_starknet.max_fee, @@ -603,9 +621,7 @@ async def test_get_block_with_declare_v2( assert ( DeclareTransaction( class_hash=class_hash, - compiled_class_hash=declare_v2_hello_starknet.compiled_class_hash - if isinstance(client, GatewayClient) - else None, + compiled_class_hash=declare_v2_hello_starknet.compiled_class_hash, sender_address=declare_v2_hello_starknet.sender_address, hash=tx_hash, max_fee=declare_v2_hello_starknet.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 9520a0779..02a6475ce 100644 --- a/starknet_py/tests/e2e/client/full_node_test.py +++ b/starknet_py/tests/e2e/client/full_node_test.py @@ -6,13 +6,16 @@ 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.account.account import Account from starknet_py.net.client_errors import ClientError from starknet_py.net.client_models import ( ContractClass, DeclareTransaction, SierraContractClass, + TransactionType, ) from starknet_py.net.full_node_client import _to_rpc_felt +from starknet_py.net.models import StarknetChainId def _parse_event_name(event: str) -> str: @@ -123,6 +126,27 @@ async def test_pending_transactions(full_node_client): assert pending_transactions[0].max_fee == 0 +@pytest.mark.asyncio +async def test_get_transaction_receipt_deploy_account( + full_node_client, deploy_account_details_factory +): + address, key_pair, salt, class_hash = await deploy_account_details_factory.get() + deploy_result = await Account.deploy_account( + address=address, + class_hash=class_hash, + salt=salt, + key_pair=key_pair, + client=full_node_client, + chain=StarknetChainId.TESTNET, + max_fee=int(1e16), + ) + await deploy_result.wait_for_acceptance() + + receipt = await full_node_client.get_transaction_receipt(tx_hash=deploy_result.hash) + assert receipt.type == TransactionType.DEPLOY_ACCOUNT + assert receipt.contract_address == deploy_result.account.address + + @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_get_storage_at_incorrect_address_full_node_client(full_node_client): @@ -242,7 +266,7 @@ async def test_get_events_with_two_events( from_block_number=0, to_block_hash="latest", address=simple_storage_with_event_contract.address, - keys=[[EVENT_ONE_PARSED_NAME, EVENT_TWO_PARSED_NAME]], + keys=[[int(EVENT_ONE_PARSED_NAME, 0), int(EVENT_TWO_PARSED_NAME, 0)]], follow_continuation_token=True, ) @@ -283,6 +307,25 @@ async def test_get_events_start_from_continuation_token( assert events_response.continuation_token == expected_continuation_token +@pytest.mark.run_on_devnet +@pytest.mark.asyncio +async def test_get_events_no_params( + full_node_client, + simple_storage_with_event_contract: Contract, +): + default_chunk_size = 1 + for i in range(3): + await simple_storage_with_event_contract.functions[FUNCTION_ONE_NAME].invoke( + i, i + 1, auto_estimate=True + ) + await simple_storage_with_event_contract.functions[FUNCTION_TWO_NAME].invoke( + i, i + 1, auto_estimate=True + ) + events_response = await full_node_client.get_events() + + assert len(events_response.events) == default_chunk_size + + @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_get_events_nonexistent_starting_block( diff --git a/starknet_py/tests/e2e/contract_interaction/interaction_test.py b/starknet_py/tests/e2e/contract_interaction/interaction_test.py index 35f9be24a..4336b0f4d 100644 --- a/starknet_py/tests/e2e/contract_interaction/interaction_test.py +++ b/starknet_py/tests/e2e/contract_interaction/interaction_test.py @@ -6,7 +6,12 @@ from starknet_py.contract import Contract from starknet_py.hash.selector import get_selector_from_name from starknet_py.net.client_errors import ClientError -from starknet_py.net.client_models import Call, TransactionReceipt, TransactionStatus +from starknet_py.net.client_models import ( + Call, + TransactionReceipt, + TransactionStatus, + TransactionType, +) from starknet_py.net.gateway_client import GatewayClient from starknet_py.tests.e2e.fixtures.constants import MAX_FEE from starknet_py.transaction_errors import ( @@ -157,7 +162,7 @@ async def test_wait_for_tx_throws_on_transaction_rejected(client, map_contract): @patch("starknet_py.net.gateway_client.GatewayClient.get_transaction_receipt") async def test_transaction_not_received_error(mocked_get_receipt, map_contract): mocked_get_receipt.return_value = TransactionReceipt( - hash=1, status=TransactionStatus.NOT_RECEIVED + hash=1, status=TransactionStatus.NOT_RECEIVED, type=TransactionType.INVOKE ) result = await map_contract.functions["put"].invoke(10, 20, max_fee=MAX_FEE) 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 70c09927f..5b60483bb 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 @@ -158,3 +158,21 @@ async def test_get_contract_nonce(full_node_client, contract_address): contract_address=address, block_number="latest" ) # docs-end: get_contract_nonce + + +@pytest.mark.asyncio +async def test_get_events(full_node_client, contract_address): + # docs-start: get_events + address = 0x1 or 1 or "0x1" + # docs-end: get_events + address = contract_address + # docs-start: get_events + events_response = await full_node_client.get_events( + address=address, + keys=[[1, 2], [], [3]], + from_block_number=0, + to_block_number="latest", + follow_continuation_token=True, + chunk_size=47, + ) + # docs-end: get_events diff --git a/starknet_py/tests/e2e/fixtures/abi_structures.py b/starknet_py/tests/e2e/fixtures/abi_structures.py index 5e5a1e479..13016d313 100644 --- a/starknet_py/tests/e2e/fixtures/abi_structures.py +++ b/starknet_py/tests/e2e/fixtures/abi_structures.py @@ -38,6 +38,17 @@ {"name": "pool_id", "offset": 3, "type": "PoolId"}, ], } +user_missing_offset_dict = { + "type": "struct", + "name": "User", + "size": 4, + "members": [ + {"name": "id", "type": "Uint256"}, + {"name": "name_len", "type": "felt"}, + {"name": "name", "type": "felt*"}, + {"name": "pool_id", "type": "PoolId"}, + ], +} user_struct = StructType( "User", OrderedDict( @@ -48,6 +59,27 @@ ), ) +user_partial_missing_offset_dict = { + "type": "struct", + "name": "User", + "size": 4, + "members": [ + {"name": "id", "offset": 0, "type": "Uint256"}, + {"name": "name_len", "type": "felt"}, + {"name": "name", "type": "felt*"}, + {"name": "pool_id", "offset": 1, "type": "PoolId"}, + ], +} +user_partial_missing_offset_struct = StructType( + "User", + OrderedDict( + id=uint256_struct, + pool_id=pool_id_struct, + name_len=FeltType(), + name=ArrayType(FeltType()), + ), +) + user_added_dict = { "type": "event", "name": "UserAdded", From 0fe3138efba4caa7788337271095a160c8b7cad6 Mon Sep 17 00:00:00 2001 From: Igor Urbanik <39826471+Radinyn@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:01:01 +0200 Subject: [PATCH 12/17] Embedded `cairo.ebnf` in code (#1073) * Embedded `cairo.ebnf` in code * Lint --------- Co-authored-by: Kamil Jankowski --- starknet_py/cairo/deprecated_parse/cairo.ebnf | 20 ------------ starknet_py/cairo/deprecated_parse/parser.py | 31 +++++++++++++++---- 2 files changed, 25 insertions(+), 26 deletions(-) delete mode 100644 starknet_py/cairo/deprecated_parse/cairo.ebnf diff --git a/starknet_py/cairo/deprecated_parse/cairo.ebnf b/starknet_py/cairo/deprecated_parse/cairo.ebnf deleted file mode 100644 index 71e2d71e2..000000000 --- a/starknet_py/cairo/deprecated_parse/cairo.ebnf +++ /dev/null @@ -1,20 +0,0 @@ -%import common.WS_INLINE -%ignore WS_INLINE - -IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/ -_DBL_STAR: "**" -COMMA: "," - -?type: non_identifier_type - | identifier -> type_struct - -comma_separated{item}: item? (COMMA item)* COMMA? - -named_type: identifier (":" type)? | non_identifier_type -non_identifier_type: "felt" -> type_felt - | "codeoffset" -> type_codeoffset - | type "*" -> type_pointer - | type _DBL_STAR -> type_pointer2 - | "(" comma_separated{named_type} ")" -> type_tuple - -identifier: IDENTIFIER ("." IDENTIFIER)* diff --git a/starknet_py/cairo/deprecated_parse/parser.py b/starknet_py/cairo/deprecated_parse/parser.py index 39d382144..9111bc0bf 100644 --- a/starknet_py/cairo/deprecated_parse/parser.py +++ b/starknet_py/cairo/deprecated_parse/parser.py @@ -1,19 +1,38 @@ -import os - import lark from starknet_py.cairo.deprecated_parse.cairo_types import CairoType from starknet_py.cairo.deprecated_parse.parser_transformer import ParserTransformer +CAIRO_EBNF = """ + %import common.WS_INLINE + %ignore WS_INLINE + + IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/ + _DBL_STAR: "**" + COMMA: "," + + ?type: non_identifier_type + | identifier -> type_struct + + comma_separated{item}: item? (COMMA item)* COMMA? + + named_type: identifier (":" type)? | non_identifier_type + non_identifier_type: "felt" -> type_felt + | "codeoffset" -> type_codeoffset + | type "*" -> type_pointer + | type _DBL_STAR -> type_pointer2 + | "(" comma_separated{named_type} ")" -> type_tuple + + identifier: IDENTIFIER ("." IDENTIFIER)* +""" + def parse(code: str) -> CairoType: """ Parses the given string and returns a CairoType. """ - with open( - os.path.join(os.path.dirname(__file__), "cairo.ebnf"), "r", encoding="utf-8" - ) as grammar_file: - grammar = grammar_file.read() + + grammar = CAIRO_EBNF grammar_parser = lark.Lark( grammar=grammar, From 46d800aa07f0f25d7cc1161366851d1df0b5f6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C5=9Bkowicz?= Date: Fri, 23 Jun 2023 11:45:45 +0200 Subject: [PATCH 13/17] Change Windows installation section in docs, add migration guide (#1077) * changed installation section * docs fix * docs fix * docs fix * docs fix * docs fix * docs fix * docs fix * migration guide updated * docs fix * docs fix * docs fix * docs fix * docs fix * docs fix * docs fix * Apply review --------- Co-authored-by: drknzz --- docs/installation.rst | 12 +++++- docs/migration_guide.rst | 90 +++++++++++++++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index bbebf4161..96116dbb5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -46,5 +46,13 @@ Apple silicon Windows ------- -This library is incompatible with Windows devices. -Use virtual machine with Linux, `Windows Subsystem for Linux 2 `_ (WSL2) or other solution. +You can install starknet.py on Windows in two ways: + +1. Install it just like you would on Linux. + +You might encounter problems related to ``libcrypto_c_exports``. Make sure that you have `MinGW `_ installed and up-to-date. + +If you encounter any further problems related to installation, you can create an `issue at our GitHub `_ +or ask for help in ``#🐍 | starknet-py`` channel on `Starknet Discord server `_. + +2. Use virtual machine with Linux, `Windows Subsystem for Linux 2 `_ (WSL2). diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index c02c5e561..f7c3d2b72 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -1,6 +1,74 @@ Migration guide =============== +**************************** +0.17.0-alpha Migration guide +**************************** + +.. currentmodule:: starknet_py.net.full_node_client + +:class:`FullNodeClient` RPC specification has been updated from `v0.3.0-rc1 `_ to `v0.3.0 `_. + +.. currentmodule:: starknet_py.contract + + +:class:`Contract` now *initially* supports contracts written in **Cairo1**. + +To create an instance of such contract, a keyword parameter ``cairo_version=1`` in the Contract constructor is required. + + +.. note:: + Please note that while using the interface with `Cairo1` contracts, it is possible for problems to occur due to some of the types being not yet implemented in the parser. + + In such case, please open an issue at our `GitHub `_ or contract us on `Starknet Discord server `_ in ``#🐍 | starknet-py`` channel. + + +Breaking changes +---------------- + +1. Deprecated function ``compute_invoke_hash`` in :mod:`starknet_py.net.models.transaction` has been removed in favor of :func:`starknet_py.hash.transaction.compute_invoke_transaction_hash`. + + +Minor changes +------------- + +1. :meth:`DeclareResult.deploy`, :meth:`PreparedFunctionCall.invoke`, :meth:`PreparedFunctionCall.estimate_fee`, :meth:`ContractFunction.invoke`, :meth:`Contract.declare` and :meth:`Contract.deploy_contract` can now accept custom ``nonce`` parameter. + +.. currentmodule:: starknet_py.net.account.account + +2. :meth:`Account.sign_invoke_transaction`, :meth:`Account.sign_declare_transaction`, :meth:`Account.sign_declare_v2_transaction`, :meth:`Account.sign_deploy_account_transaction` and :meth:`Account.execute` can now accept custom ``nonce`` parameter. +3. :meth:`Account.get_nonce` can now be parametrized with ``block_number`` or ``block_hash``. +4. :meth:`Account.get_balance` can now be parametrized with ``block_number`` or ``block_hash``. + +RPC related changes: + +.. currentmodule:: starknet_py.net.client_models + +5. :class:`L2toL1Message` dataclass now has an additional field: ``from_address``. +6. :class:`TransactionReceipt` dataclass now has two additional, optional fields: ``type`` and ``contract_address``. + +.. currentmodule:: starknet_py.net.full_node_client + +7. :meth:`FullNodeClient.get_events` ``keys`` and ``address`` parameters type are now optional. +8. :meth:`FullNodeClient.get_events` ``keys`` parameter can now also accept integers as felts. + + +Bugfixes +-------- + +.. currentmodule:: starknet_py.hash.class_hash + +1. Fixed a bug when :func:`compute_class_hash` mutated the ``contract_class`` argument passed to a function. + + +| + +.. raw:: html + +
+ +| + ********************** 0.16.1 Migration guide ********************** @@ -9,23 +77,25 @@ Migration guide Additionally, this release brings support for `RPC v0.3.0rc1 `_! -Breaking changes ----------------- +0.16.1 Breaking changes +----------------------- -.. currentmodule:: starknet_py.net +.. currentmodule:: starknet_py.net.full_node_client -1. ``FullNodeClient.get_events`` `keys` parameter type is now `List[List[str]]` instead of `List[str]`. -2. ``FullNodeClient.get_state_update`` return type has been changed from `StateUpdate` to `Union[BlockStateUpdate, PendingBlockStateUpdate]` +1. :meth:`FullNodeClient.get_events` ``keys`` parameter type is now ``List[List[str]]`` instead of ``List[str]``. +2. :meth:`FullNodeClient.get_state_update` return type has been changed from ``StateUpdate`` to ``Union[BlockStateUpdate, PendingBlockStateUpdate]`` -.. currentmodule:: starknet_py.net.schemas +.. currentmodule:: starknet_py.net.client_models -3. ``StateDiff`` dataclass properties have been changed (more details in RPC specification linked above). +3. :class:`StateDiff` dataclass properties have been changed (more details in RPC specification linked above). -Minor changes -------------- +0.16.1 Minor changes +-------------------- + +.. currentmodule:: starknet_py.net.client -1. ``Client.estimate_fee`` can take a single transaction or a list of transactions to estimate. +1. :meth:`Client.estimate_fee` can take a single transaction or a list of transactions to estimate. | From 2a34f592a7ae0db75bccfae3c3ba5c15ae314cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C5=9Bkowicz?= Date: Fri, 23 Jun 2023 14:07:32 +0200 Subject: [PATCH 14/17] Throw an error when `FullNodeClient` uses `wait_for_tx` without `wait_for_acceptance=True` (#1078) * added error when fullnode doesnt wait for accept * fixes * some tests fixed * format * change error logic in wait_for_tx() * fix tests to match new logic * logic changed a bit, added test --- starknet_py/net/client.py | 18 +++++++++++++----- starknet_py/tests/e2e/client/full_node_test.py | 9 +++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 9fe8b0505..134124652 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -4,6 +4,7 @@ from abc import ABC, abstractmethod from typing import List, Optional, Tuple, Union +from starknet_py.net.client_errors import ClientError from starknet_py.net.client_models import ( BlockStateUpdate, BlockTransactionTraces, @@ -139,12 +140,13 @@ async def wait_for_tx( ) -> Tuple[int, TransactionStatus]: # pylint: disable=too-many-branches """ - Awaits for transaction to get accepted or at least pending by polling its status + Awaits for transaction to get accepted or at least pending by polling its status. - :param tx_hash: Transaction's hash - :param wait_for_accept: If true waits for at least ACCEPTED_ON_L2 status, otherwise waits for at least PENDING - :param check_interval: Defines interval between checks - :return: Tuple containing block number and transaction status + :param tx_hash: Transaction's hash. + :param wait_for_accept: If true waits for at least ACCEPTED_ON_L2 status, otherwise waits for at least PENDING. + Defaults to false + :param check_interval: Defines interval between checks. + :return: Tuple containing block number and transaction status. """ if check_interval <= 0: raise ValueError("Argument check_interval has to be greater than 0.") @@ -182,6 +184,12 @@ async def wait_for_tx( await asyncio.sleep(check_interval) except asyncio.CancelledError as exc: raise TransactionNotReceivedError from exc + except ClientError as exc: + if "Transaction hash not found" in exc.message: + raise ClientError( + "Nodes can't access pending transactions, try using parameter 'wait_for_accept=True'." + ) from exc + raise exc @abstractmethod async def estimate_fee( diff --git a/starknet_py/tests/e2e/client/full_node_test.py b/starknet_py/tests/e2e/client/full_node_test.py index 02a6475ce..e050d2750 100644 --- a/starknet_py/tests/e2e/client/full_node_test.py +++ b/starknet_py/tests/e2e/client/full_node_test.py @@ -158,6 +158,15 @@ async def test_get_storage_at_incorrect_address_full_node_client(full_node_clien ) +@pytest.mark.asyncio +async def test_wait_for_tx_invalid_tx_hash(full_node_client): + with pytest.raises( + ClientError, + match="Nodes can't access pending transactions, try using parameter 'wait_for_accept=True'.", + ): + _ = await full_node_client.wait_for_tx(tx_hash=0x123456789) + + @pytest.mark.run_on_devnet @pytest.mark.asyncio async def test_get_events_without_following_continuation_token( From 9553f526ef578bbab907e4d4b200b26b47abe727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Mi=C5=9Bkowicz?= Date: Fri, 23 Jun 2023 14:28:39 +0200 Subject: [PATCH 15/17] add codecov test to previous PR (#1080) * add codecov test --- starknet_py/tests/e2e/client/client_test.py | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index ee496e421..fd512af91 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -9,6 +9,7 @@ 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_errors import ClientError from starknet_py.net.client_models import ( Call, ContractClass, @@ -406,6 +407,34 @@ async def test_wait_for_tx_cancelled(client, get_tx_receipt, request): await task +@pytest.mark.asyncio +@pytest.mark.parametrize( + "client, get_tx_receipt", + [ + ( + "gateway_client", + "tx_receipt_gateway_path", + ), + ( + "full_node_client", + "tx_receipt_full_node_path", + ), + ], +) +async def test_wait_for_tx_unknown_error(client, get_tx_receipt, request): + get_tx_receipt = request.getfixturevalue(get_tx_receipt) + + with patch( + get_tx_receipt, + AsyncMock(), + ) as mocked_receipt: + mocked_receipt.side_effect = ClientError(message="Unknown error") + client = request.getfixturevalue(client) + + with pytest.raises(ClientError, match="Unknown error"): + await client.wait_for_tx(tx_hash="0x2137") + + @pytest.mark.asyncio async def test_declare_contract(map_compiled_contract, account): declare_tx = await account.sign_declare_transaction( From 3cd7ac6527e3d40ac27570575f7936a76c9a31e5 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Mon, 26 Jun 2023 16:33:25 +0200 Subject: [PATCH 16/17] Support Cairo1 in `Contract` (#1069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create parser for `Abi V1` (#913) * update abi module * first working parser * Enum -> EnumType * enums are parsed! * test data is passing! * split abiParsers * support array * modify the abi.ebnf * fix tests, format code * update docstrings * add test file * add simple test * update requirements.txt * update lock * update docstrings * "felt" "252" -> "felt252" (ebnf update) * remove unused constructor and l1_handler * add Uint support * improve Option type * support structures without type in name * fix abi test * format * remove unused _transform_cairo_lang_type * support tuples * Update starknet_py/abi/v1/parser_transformer.py Co-authored-by: Kamil Jankowski * rename member to parameter * add TypeIdentifier type to clarify parse return value * add docstrings to the ParserTransformer * rename Option -> OptionType * split enums and structures * fix abi test * format * redefine struct in abi.ebnf * rename struct -> type_identifier in the abi.ebnf * Update starknet_py/abi/v1/parser.py Co-authored-by: Kamil Jankowski * update docstring * fix potential matching problem * rename `struct` -> `defined_type` * update docstring example * fix lock * fix lock * fix lock * remove optional-requirements.txt * fix requirements.txt --------- Co-authored-by: Kamil Jankowski * strip trailing `::` in struct name * Use cairo1.0 compiler from main repo (#993) * clone the latest version * update cairo1 contracts * use resolved commit hash * fix casm_class_hash_test.py * fix remaining failing tests * fix pyright * Adjust serializers (#983) * update abi module * first working parser * Enum -> EnumType * enums are parsed! * test data is passing! * split abiParsers * support array * modify the abi.ebnf * fix tests, format code * update docstrings * add test file * add simple test * update requirements.txt * update lock * update docstrings * "felt" "252" -> "felt252" (ebnf update) * remove unused constructor and l1_handler * add Uint support * improve Option type * support structures without type in name * fix abi test * format * remove unused _transform_cairo_lang_type * support tuples * Update starknet_py/abi/v1/parser_transformer.py Co-authored-by: Kamil Jankowski * rename member to parameter * add TypeIdentifier type to clarify parse return value * add docstrings to the ParserTransformer * rename Option -> OptionType * split enums and structures * fix abi test * format * redefine struct in abi.ebnf * rename struct -> type_identifier in the abi.ebnf * Update starknet_py/abi/v1/parser.py Co-authored-by: Kamil Jankowski * update docstring * fix potential matching problem * rename `struct` -> `defined_type` * update docstring example * fix lock * WIP * format * few improvements (erc20 almost working) * format * uint serializer is working! * fix type errors * format * update `from_abi` docstring * add serializing struct test * make tests readable * option serialization wip * add option serializer * format * add unit type * update cairo_1 contracts * add eth_address to abi.ebnf * bring back default timeout * raise when OutputSerializer.serialize used * lstrip `\x00` from decoded name * clarify decoded_name code * use keyword arguments * move some code to helper functions * create calldata dict * update cairo compiler * update cairo compiler hash * update cairo compiler hash * fix tests * add tests for UintSerializer * add tests for UnitSerializer * add tests for OptionSerializer * add tests for OutputSerializer * test error in output serializer * update OutputSerializer push_entity * ensure_valid_uint modified * remove unnecessary print * add docstrings * format docstrings * EnumSerializer (#1015) * add EnumSerializer * add test for serializing enums * add more test for serializing enums * edit length error * serialize unit enums too :rocket: * fix test * serialize TupleDataclass * do not use package `indexed` * requested changes * add enum example * Update starknet_py/serialization/data_serializers/option_serializer.py Co-authored-by: Kamil Jankowski * Update starknet_py/serialization/data_serializers/uint_serializer.py Co-authored-by: Kamil Jankowski * Update starknet_py/serialization/data_serializers/option_serializer.py Co-authored-by: Kamil Jankowski * improve uint serializer and tests --------- Co-authored-by: Kamil Jankowski * Allow cairo 1 contracts to be declared through `Contract` (#1016) * allow declaring and deploying cairo 1 contracts * add contract tests * move tests to new directories * modify poe test_ci * change warning to exception * combine coverage files * do not combine coverage files * try to use bash script to fix coverage * Fix coverage (#1018) * "package" -> "module" * Revert ""package" -> "module"" This reverts commit dc4b65a638646477c554065cdcddb8d2e2bbfe5e. * fix test, declare only once * add missing Contract tests * change contract name * fix tests * Allow using cairo1 in abi resolver (#1017) * remove error, add support for cairo 1 * detect cairo version in abi resolver * from address ciaro 1 test * extract methods * Add missing tests [Contract v1 branch] (#1024) * add TODOs * add parser tests * add schemas tests * test parser transformer default entry point * add type parser tests * add unit serializer error test * Split tests per client * Adjust CI * Apply review * Add BoolSerializer * Refactor ebnf * Wrap exception * Fix test * Remove abi.ebnf * Update devnet on Windows * Tweaks * Tweaks * Remove wrapped UnexpectedToken error * Update docs * Update docs create_contract_deployment_raw * Update docs * Split docs tests per client --------- Co-authored-by: Marcin Warchoł <61014013+war-in@users.noreply.github.com> Co-authored-by: war-in --- .github/workflows/checks.yml | 16 +- docs/guide/deploying_contracts.rst | 25 +- pyproject.toml | 7 + starknet_py/abi/v1/__init__.py | 0 starknet_py/abi/v1/core_structures.json | 14 ++ starknet_py/abi/v1/model.py | 39 ++++ starknet_py/abi/v1/parser.py | 220 ++++++++++++++++++ starknet_py/abi/v1/parser_test.py | 174 ++++++++++++++ starknet_py/abi/v1/parser_transformer.py | 169 ++++++++++++++ starknet_py/abi/v1/parser_transformer_test.py | 9 + starknet_py/abi/v1/schemas.py | 66 ++++++ starknet_py/abi/v1/schemas_test.py | 32 +++ starknet_py/abi/v1/shape.py | 47 ++++ starknet_py/cairo/data_types.py | 56 +++++ starknet_py/cairo/v1/__init__.py | 0 starknet_py/cairo/v1/type_parser.py | 76 ++++++ starknet_py/cairo/v1/type_parser_test.py | 111 +++++++++ starknet_py/conftest.py | 1 + starknet_py/contract.py | 162 +++++++++---- starknet_py/contract_test.py | 18 ++ starknet_py/net/udc_deployer/deployer.py | 5 +- starknet_py/proxy/contract_abi_resolver.py | 63 ++--- .../data_serializers/__init__.py | 1 + .../data_serializers/bool_serializer.py | 37 +++ .../data_serializers/bool_serializer_test.py | 24 ++ .../data_serializers/cairo_data_serializer.py | 8 +- .../data_serializers/enum_serializer.py | 71 ++++++ .../data_serializers/enum_serializer_test.py | 52 +++++ .../data_serializers/option_serializer.py | 41 ++++ .../option_serializer_test.py | 23 ++ .../data_serializers/output_serializer.py | 38 +++ .../output_serializer_test.py | 54 +++++ .../data_serializers/uint_serializer.py | 98 ++++++++ .../data_serializers/uint_serializer_test.py | 116 +++++++++ .../data_serializers/unit_serializer.py | 30 +++ .../data_serializers/unit_serializer_test.py | 23 ++ starknet_py/serialization/factory.py | 68 +++++- .../function_serialization_adapter.py | 16 ++ starknet_py/tests/e2e/account/account_test.py | 8 +- starknet_py/tests/e2e/client/client_test.py | 22 +- .../tests/e2e/client/fixtures/transactions.py | 64 ++--- starknet_py/tests/e2e/client/gateway_test.py | 7 +- .../e2e/contract_interaction/declare_test.py | 44 ++++ .../e2e/contract_interaction/deploy_test.py | 88 +++++++ .../v1_interaction_test.py | 160 +++++++++++++ starknet_py/tests/e2e/declare/declare_test.py | 42 +--- .../e2e/docs/guide/test_cairo1_contract.py | 23 +- .../test_simple_declare_and_deploy_cairo1.py | 43 ++++ .../e2e/docs/guide/test_simple_deploy.py | 4 +- .../docs/guide/test_simple_deploy_cairo1.py | 48 ++++ .../tests/e2e/fixtures/abi_v1_structures.py | 172 ++++++++++++++ starknet_py/tests/e2e/fixtures/accounts.py | 5 + starknet_py/tests/e2e/fixtures/clients.py | 5 + starknet_py/tests/e2e/fixtures/contracts.py | 46 +++- .../tests/e2e/fixtures/contracts_v1.py | 144 ++++++++++++ starknet_py/tests/e2e/fixtures/misc.py | 6 + .../mock/contracts_v1/hello_starknet.cairo | 2 +- .../contracts_v1/test_contract_declare.cairo | 5 + .../e2e/mock/contracts_v1/test_enum.cairo | 32 +++ .../e2e/mock/contracts_v1/test_option.cairo | 38 +++ .../utils/contructor_args_translator.py | 41 +++- 61 files changed, 2838 insertions(+), 221 deletions(-) create mode 100644 starknet_py/abi/v1/__init__.py create mode 100644 starknet_py/abi/v1/core_structures.json create mode 100644 starknet_py/abi/v1/model.py create mode 100644 starknet_py/abi/v1/parser.py create mode 100644 starknet_py/abi/v1/parser_test.py create mode 100644 starknet_py/abi/v1/parser_transformer.py create mode 100644 starknet_py/abi/v1/parser_transformer_test.py create mode 100644 starknet_py/abi/v1/schemas.py create mode 100644 starknet_py/abi/v1/schemas_test.py create mode 100644 starknet_py/abi/v1/shape.py create mode 100644 starknet_py/cairo/v1/__init__.py create mode 100644 starknet_py/cairo/v1/type_parser.py create mode 100644 starknet_py/cairo/v1/type_parser_test.py create mode 100644 starknet_py/serialization/data_serializers/bool_serializer.py create mode 100644 starknet_py/serialization/data_serializers/bool_serializer_test.py create mode 100644 starknet_py/serialization/data_serializers/enum_serializer.py create mode 100644 starknet_py/serialization/data_serializers/enum_serializer_test.py create mode 100644 starknet_py/serialization/data_serializers/option_serializer.py create mode 100644 starknet_py/serialization/data_serializers/option_serializer_test.py create mode 100644 starknet_py/serialization/data_serializers/output_serializer.py create mode 100644 starknet_py/serialization/data_serializers/output_serializer_test.py create mode 100644 starknet_py/serialization/data_serializers/uint_serializer.py create mode 100644 starknet_py/serialization/data_serializers/uint_serializer_test.py create mode 100644 starknet_py/serialization/data_serializers/unit_serializer.py create mode 100644 starknet_py/serialization/data_serializers/unit_serializer_test.py create mode 100644 starknet_py/tests/e2e/contract_interaction/declare_test.py create mode 100644 starknet_py/tests/e2e/contract_interaction/deploy_test.py create mode 100644 starknet_py/tests/e2e/contract_interaction/v1_interaction_test.py create mode 100644 starknet_py/tests/e2e/docs/guide/test_simple_declare_and_deploy_cairo1.py create mode 100644 starknet_py/tests/e2e/docs/guide/test_simple_deploy_cairo1.py create mode 100644 starknet_py/tests/e2e/fixtures/abi_v1_structures.py create mode 100644 starknet_py/tests/e2e/fixtures/contracts_v1.py create mode 100644 starknet_py/tests/e2e/mock/contracts_v1/test_contract_declare.cairo create mode 100644 starknet_py/tests/e2e/mock/contracts_v1/test_enum.cairo create mode 100644 starknet_py/tests/e2e/mock/contracts_v1/test_option.cairo diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index cc06eab9d..34d8802d6 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -228,7 +228,8 @@ jobs: - name: Run tests run: | - poetry run poe test_ci + poetry run poe test_ci_gateway + poetry run poe test_ci_full_node - name: Generate coverage in XML run: | @@ -278,7 +279,7 @@ jobs: apt-get install -y python3-pip sudo apt install -y libgmp3-dev sudo apt-get install -y git - pip3 install git+https://github.com/0xSpaceShard/starknet-devnet.git@744e9b3bd5fb9e856287158d87673e090df69d73 + pip3 install git+https://github.com/0xSpaceShard/starknet-devnet.git@v0.5.4 # ====================== SETUP PYTHON ====================== # @@ -311,7 +312,8 @@ jobs: - name: Run tests run: | - poetry run poe test_ci + poetry run poe test_ci_gateway + poetry run poe test_ci_full_node - name: Generate coverage in XML run: | @@ -373,7 +375,8 @@ jobs: - name: Run tests run: | - poetry run poe test_ci_docs + poetry run poe test_ci_docs_gateway + poetry run poe test_ci_docs_full_node - name: Generate coverage in XML run: | @@ -423,7 +426,7 @@ jobs: apt-get install -y python3-pip sudo apt install -y libgmp3-dev sudo apt-get install -y git - pip3 install git+https://github.com/0xSpaceShard/starknet-devnet.git@744e9b3bd5fb9e856287158d87673e090df69d73 + pip3 install git+https://github.com/0xSpaceShard/starknet-devnet.git@v0.5.4 # ====================== SETUP PYTHON ====================== # @@ -452,7 +455,8 @@ jobs: - name: Run tests run: | - poetry run poe test_ci_docs + poetry run poe test_ci_docs_gateway + poetry run poe test_ci_docs_full_node - name: Generate coverage in XML run: | diff --git a/docs/guide/deploying_contracts.rst b/docs/guide/deploying_contracts.rst index cb4bfdea0..b84301bca 100644 --- a/docs/guide/deploying_contracts.rst +++ b/docs/guide/deploying_contracts.rst @@ -101,11 +101,6 @@ Here's an example how to declare a Cairo1 contract. :language: python :dedent: 4 -.. note:: - - This is currently the only supported method of declaring a Cairo1 contract to Starknet. - The support for declaring through :ref:`Contract` interface is planned for a future release. - Deploying Cairo1 contracts ########################## @@ -118,8 +113,22 @@ After declaring a Cairo1 contract, it can be deployed using UDC. :start-after: docs-deploy: start :end-before: docs-deploy: end -.. note:: - Currently only :meth:`~starknet_py.net.udc_deployer.deployer.Deployer.create_contract_deployment_raw` is supported. - :meth:`~starknet_py.net.udc_deployer.deployer.Deployer.create_contract_deployment` will not work. +Simple declare and deploy Cairo1 contract example +################################################# +.. codesnippet:: ../../starknet_py/tests/e2e/docs/guide/test_simple_declare_and_deploy_cairo1.py + :language: python + :dedent: 4 + :start-after: docs: start + :end-before: docs: end + + +Simple deploy Cairo1 contract example +##################################### + +.. codesnippet:: ../../starknet_py/tests/e2e/docs/guide/test_simple_deploy_cairo1.py + :language: python + :dedent: 4 + :start-after: docs: start + :end-before: docs: end diff --git a/pyproject.toml b/pyproject.toml index 7fb38e22f..89d9606cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,8 +54,15 @@ starknet-devnet = {version = "0.5.4", 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.shell = "coverage run -m pytest -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core" +test_ci_gateway.shell = "coverage run -m pytest --client=gateway -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core" +test_ci_full_node.shell = "coverage run -m pytest --client=full_node -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py --ignore=starknet_py/tests/e2e/docs --ignore=starknet_py/tests/e2e/core" + test_ci_docs.shell = "coverage run -m pytest -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs" +test_ci_docs_gateway.shell = "coverage run -m pytest --client=gateway -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs" +test_ci_docs_full_node.shell = "coverage run -m pytest --client=full_node -v --reruns 5 --only-rerun aiohttp.client_exceptions.ClientConnectorError starknet_py/tests/e2e/docs" + test_unit.shell = "pytest -n auto -v starknet_py --ignore=starknet_py/tests/e2e" test_e2e.shell = "pytest -n auto -v starknet_py/tests/e2e --ignore=starknet_py/tests/e2e/docs" test_docs.shell = "pytest -n auto -v starknet_py/tests/e2e/docs" diff --git a/starknet_py/abi/v1/__init__.py b/starknet_py/abi/v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/starknet_py/abi/v1/core_structures.json b/starknet_py/abi/v1/core_structures.json new file mode 100644 index 000000000..0448bc28e --- /dev/null +++ b/starknet_py/abi/v1/core_structures.json @@ -0,0 +1,14 @@ +{ + "abi": [ + { + "type": "struct", + "name": "core::starknet::eth_address::EthAddress", + "members": [ + { + "name": "address", + "type": "core::felt252" + } + ] + } + ] +} diff --git a/starknet_py/abi/v1/model.py b/starknet_py/abi/v1/model.py new file mode 100644 index 000000000..c5d621d6b --- /dev/null +++ b/starknet_py/abi/v1/model.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Dict, List, OrderedDict + +from starknet_py.cairo.data_types import CairoType, EnumType, StructType + + +@dataclass +class Abi: + """ + Dataclass representing class abi. Contains parsed functions, enums, events and structures. + """ + + @dataclass + class Function: + """ + Dataclass representing function's abi. + """ + + name: str + inputs: OrderedDict[str, CairoType] + outputs: List[CairoType] + + @dataclass + class Event: + """ + Dataclass representing event's abi. + """ + + name: str + inputs: OrderedDict[str, CairoType] + + defined_structures: Dict[ + str, StructType + ] #: Abi of structures defined by the class. + defined_enums: Dict[str, EnumType] #: Abi of enums defined by the class. + functions: Dict[str, Function] #: Functions defined by the class. + events: Dict[str, Event] #: Events defined by the class diff --git a/starknet_py/abi/v1/parser.py b/starknet_py/abi/v1/parser.py new file mode 100644 index 000000000..e12fc97ea --- /dev/null +++ b/starknet_py/abi/v1/parser.py @@ -0,0 +1,220 @@ +from __future__ import annotations + +import dataclasses +import json +import os +from collections import OrderedDict, defaultdict +from pathlib import Path +from typing import DefaultDict, Dict, List, Optional, Tuple, Union, cast + +from marshmallow import EXCLUDE + +from starknet_py.abi.v1.model import Abi +from starknet_py.abi.v1.schemas import ContractAbiEntrySchema +from starknet_py.abi.v1.shape import ( + ENUM_ENTRY, + EVENT_ENTRY, + FUNCTION_ENTRY, + STRUCT_ENTRY, + EventDict, + FunctionDict, + TypedParameterDict, +) +from starknet_py.cairo.data_types import CairoType, EnumType, StructType +from starknet_py.cairo.v1.type_parser import TypeParser + + +class AbiParsingError(ValueError): + """ + Error raised when something wrong goes during abi parsing. + """ + + +class AbiParser: + """ + Utility class for parsing abi into a dataclass. + """ + + # Entries from ABI grouped by entry type + _grouped: DefaultDict[str, List[Dict]] + # lazy init property + _type_parser: Optional[TypeParser] = None + + def __init__(self, abi_list: List[Dict]): + """ + Abi parser constructor. Ensures that abi satisfies the abi schema. + + :param abi_list: Contract's ABI as a list of dictionaries. + """ + # prepend abi with core structures + core_structures = ( + Path(os.path.dirname(__file__)) / "core_structures.json" + ).read_text("utf-8") + abi_list = json.loads(core_structures)["abi"] + abi_list + abi = [ + ContractAbiEntrySchema().load(entry, unknown=EXCLUDE) for entry in abi_list + ] + grouped = defaultdict(list) + for entry in abi: + assert isinstance(entry, dict) + grouped[entry["type"]].append(entry) + + self._grouped = grouped + + def parse(self) -> Abi: + """ + Parse abi provided to constructor and return it as a dataclass. Ensures that there are no cycles in the abi. + + :raises: AbiParsingError: on any parsing error. + :return: Abi dataclass. + """ + structures, enums = self._parse_structures_and_enums() + functions_dict = cast( + Dict[str, FunctionDict], + AbiParser._group_by_entry_name( + self._grouped[FUNCTION_ENTRY], "defined functions" + ), + ) + events_dict = cast( + Dict[str, EventDict], + AbiParser._group_by_entry_name( + self._grouped[EVENT_ENTRY], "defined events" + ), + ) + + return Abi( + defined_structures=structures, + defined_enums=enums, + functions={ + name: self._parse_function(entry) + for name, entry in functions_dict.items() + }, + events={ + name: self._parse_event(entry) for name, entry in events_dict.items() + }, + ) + + @property + def type_parser(self) -> TypeParser: + if self._type_parser: + return self._type_parser + + raise RuntimeError("Tried to get type_parser before it was set.") + + def _parse_structures_and_enums( + self, + ) -> Tuple[Dict[str, StructType], Dict[str, EnumType]]: + structs_dict = AbiParser._group_by_entry_name( + self._grouped[STRUCT_ENTRY], "defined structures" + ) + enums_dict = AbiParser._group_by_entry_name( + self._grouped[ENUM_ENTRY], "defined enums" + ) + + # Contains sorted members of the struct + struct_members: Dict[str, List[TypedParameterDict]] = {} + structs: Dict[str, StructType] = {} + + # Contains sorted members of the enum + enum_members: Dict[str, List[TypedParameterDict]] = {} + enums: Dict[str, EnumType] = {} + + # Example problem (with a simplified json structure): + # [{name: User, fields: {id: Uint256}}, {name: "Uint256", ...}] + # User refers to Uint256 even though it is not known yet (will be parsed next). + # This is why it is important to create the structure types first. This way other types can already refer to + # them when parsing types, even thought their fields are not filled yet. + # At the end we will mutate those structures to contain the right fields. An alternative would be to use + # topological sorting with an additional "unresolved type", so this flow is much easier. + for name, struct in structs_dict.items(): + structs[name] = StructType(name, OrderedDict()) + struct_members[name] = struct["members"] + + for name, enum in enums_dict.items(): + enums[name] = EnumType(name, OrderedDict()) + enum_members[name] = enum["variants"] + + # Now parse the types of members and save them. + defined_structs_enums: Dict[str, Union[StructType, EnumType]] = dict(structs) + defined_structs_enums.update(enums) + + self._type_parser = TypeParser(defined_structs_enums) + for name, struct in structs.items(): + members = self._parse_members( + cast(List[TypedParameterDict], struct_members[name]), + f"members of structure '{name}'", + ) + struct.types.update(members) + for name, enum in enums.items(): + members = self._parse_members( + cast(List[TypedParameterDict], enum_members[name]), + f"members of enum '{name}'", + ) + enum.variants.update(members) + + # All types have their members assigned now + + self._check_for_cycles(defined_structs_enums) + + return structs, enums + + @staticmethod + def _check_for_cycles(structs: Dict[str, Union[StructType, EnumType]]): + # We want to avoid creating our own cycle checker as it would make it more complex. json module has a built-in + # checker for cycles. + try: + _to_json(structs) + except ValueError as err: + raise AbiParsingError(err) from ValueError + + def _parse_function(self, function: FunctionDict) -> Abi.Function: + return Abi.Function( + name=function["name"], + inputs=self._parse_members(function["inputs"], function["name"]), + outputs=list( + self.type_parser.parse_inline_type(param["type"]) + for param in function["outputs"] + ), + ) + + def _parse_event(self, event: EventDict) -> Abi.Event: + return Abi.Event( + name=event["name"], + inputs=self._parse_members(event["inputs"], event["name"]), + ) + + def _parse_members( + self, params: List[TypedParameterDict], entity_name: str + ) -> OrderedDict[str, CairoType]: + # Without cast, it complains that 'Type "TypedParameterDict" cannot be assigned to type "T@_group_by_name"' + members = AbiParser._group_by_entry_name(cast(List[Dict], params), entity_name) + return OrderedDict( + (name, self.type_parser.parse_inline_type(param["type"])) + for name, param in members.items() + ) + + @staticmethod + def _group_by_entry_name( + dicts: List[Dict], entity_name: str + ) -> OrderedDict[str, Dict]: + grouped = OrderedDict() + for entry in dicts: + name = entry["name"] + if name in grouped: + raise AbiParsingError( + f"Name '{name}' was used more than once in {entity_name}." + ) + grouped[name] = entry + return grouped + + +def _to_json(value): + class DataclassSupportingEncoder(json.JSONEncoder): + def default(self, o): + # Dataclasses are not supported by json. Additionally, dataclasses.asdict() works recursively and doesn't + # check for cycles, so we need to flatten dataclasses (by ONE LEVEL) ourselves. + if dataclasses.is_dataclass(o): + return tuple(getattr(o, field.name) for field in dataclasses.fields(o)) + return super().default(o) + + return json.dumps(value, cls=DataclassSupportingEncoder) diff --git a/starknet_py/abi/v1/parser_test.py b/starknet_py/abi/v1/parser_test.py new file mode 100644 index 000000000..d6faf9fb7 --- /dev/null +++ b/starknet_py/abi/v1/parser_test.py @@ -0,0 +1,174 @@ +import pytest + +import starknet_py.tests.e2e.fixtures.abi_v1_structures as fixtures +from starknet_py.abi.v1.parser import AbiParser, AbiParsingError +from starknet_py.cairo.v1.type_parser import UnknownCairoTypeError + + +def test_parsing_types_abi(): + # Even though user depend on pool id and uint256 it is defined first. Parser has to consider those cases + abi = AbiParser( + [ + fixtures.user_dict, + fixtures.pool_id_dict, + fixtures.user_added_dict, + fixtures.pool_id_added_dict, + fixtures.get_user_dict, + fixtures.delete_pool_dict, + ] + ).parse() + + assert abi.defined_structures == { + "PoolId": fixtures.pool_id_struct, + "User": fixtures.user_struct, + **fixtures.core_structures, + } + assert abi.events == { + "UserAdded": fixtures.user_added_event, + "PoolIdAdded": fixtures.pool_id_added_event, + } + assert abi.functions == { + "get_user": fixtures.get_user_fn, + "delete_pool": fixtures.delete_pool_fn, + } + + +def test_parsing_types_abi2(): + abi = AbiParser( + [ + fixtures.foo_external_dict, + fixtures.foo_event_dict, + fixtures.foo_view_dict, + fixtures.my_enum_dict, + fixtures.my_struct_dict, + ] + ).parse() + + assert abi.defined_structures == { + "test::MyStruct::": fixtures.my_struct, + **fixtures.core_structures, + } + assert abi.defined_enums == { + "test::MyEnum::": fixtures.my_enum, + } + assert abi.events == { + "foo_event": fixtures.foo_event, + } + assert abi.functions == { + "foo_external": fixtures.foo_external, + "foo_view": fixtures.foo_view, + } + + +def test_self_cycle(): + self_referencing_struct = { + "type": "struct", + "name": "Infinite", + "members": [ + {"name": "value", "type": "Infinite"}, + ], + } + with pytest.raises( + AbiParsingError, + match="Circular reference detected", + ): + AbiParser([self_referencing_struct]).parse() + + +def test_bigger_cycle(): + # first -> seconds -> third -> first... + first = { + "type": "struct", + "name": "First", + "members": [{"name": "value", "type": "Second"}], + } + second = { + "type": "struct", + "name": "Second", + "members": [{"name": "value", "type": "Third"}], + } + third = { + "type": "struct", + "name": "Third", + "members": [{"name": "value", "type": "First"}], + } + with pytest.raises( + AbiParsingError, + match="Circular reference detected", + ): + AbiParser([first, second, third]).parse() + + +def test_duplicated_structure(): + with pytest.raises( + AbiParsingError, + match="Name 'User' was used more than once in defined structures", + ): + AbiParser( + [fixtures.user_dict, fixtures.pool_id_dict, fixtures.user_dict] + ).parse() + + +def test_duplicated_function(): + with pytest.raises( + AbiParsingError, + match="Name 'get_user' was used more than once in defined functions", + ): + AbiParser( + [ + fixtures.get_user_dict, + fixtures.delete_pool_dict, + fixtures.get_user_dict, + fixtures.delete_pool_dict, + ] + ).parse() + + +def test_duplicated_event(): + with pytest.raises( + AbiParsingError, + match="Name 'UserAdded' was used more than once in defined events", + ): + AbiParser( + [ + fixtures.user_added_dict, + fixtures.delete_pool_dict, + fixtures.user_added_dict, + ] + ).parse() + + +def test_duplicated_type_members(): + type_dict = { + "type": "struct", + "name": "Record", + "members": [ + {"name": "name", "type": "core::felt252"}, + {"name": "value", "type": "core::felt252"}, + {"name": "id", "type": "core::felt252"}, + {"name": "value", "type": "core::felt252"}, + ], + } + with pytest.raises( + AbiParsingError, + match="Name 'value' was used more than once in members of structure 'Record'", + ): + AbiParser([type_dict]).parse() + + +@pytest.mark.parametrize( + "missing_name, input_dict", + [ + # Type + ("PoolId", fixtures.user_dict), + # Function + ("User", fixtures.get_user_dict), + # Event + ("User", fixtures.user_added_dict), + ], +) +def test_missing_type_used(missing_name, input_dict): + with pytest.raises( + UnknownCairoTypeError, match=f"Type '{missing_name}' is not defined.*" + ): + AbiParser([input_dict]).parse() diff --git a/starknet_py/abi/v1/parser_transformer.py b/starknet_py/abi/v1/parser_transformer.py new file mode 100644 index 000000000..8b0ea761e --- /dev/null +++ b/starknet_py/abi/v1/parser_transformer.py @@ -0,0 +1,169 @@ +from typing import Any, List, Optional + +import lark +from lark import Token, Transformer + +from starknet_py.cairo.data_types import ( + ArrayType, + BoolType, + CairoType, + FeltType, + OptionType, + TupleType, + TypeIdentifier, + UintType, + UnitType, +) + +ABI_EBNF = """ + IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/ + + type: type_unit + | type_bool + | type_felt + | type_uint + | type_contract_address + | type_class_hash + | type_storage_address + | type_option + | type_array + | type_span + | tuple + | type_identifier + + + type_unit: "()" + type_felt: "core::felt252" + type_bool: "core::bool" + type_uint: "core::integer::u" INT + type_contract_address: "core::starknet::contract_address::ContractAddress" + type_class_hash: "core::starknet::class_hash::ClassHash" + type_storage_address: "core::starknet::storage_access::StorageAddress" + type_option: "core::option::Option::<" (type | type_identifier) ">" + type_array: "core::array::Array::<" (type | type_identifier) ">" + type_span: "core::array::Span::<" (type | type_identifier) ">" + + tuple: "(" type? ("," type?)* ")" + + type_identifier: (IDENTIFIER | "::")+ ("<" (type | ",")+ ">")? + + + %import common.INT + %import common.WS + %ignore WS +""" + + +class ParserTransformer(Transformer): + """ + Transforms the lark tree into CairoTypes. + """ + + # pylint: disable=no-self-use + + def __default__(self, data: str, children, meta): + raise TypeError(f"Unable to parse tree node of type {data}.") + + def type(self, value: List[Optional[CairoType]]) -> Optional[CairoType]: + """ + Tokens are read bottom-up, so here all of them are parsed and should be just returned. + `Optional` is added in case of the unit type. + """ + assert len(value) == 1 + return value[0] + + def type_felt(self, _value: List[Any]) -> FeltType: + """ + Felt does not contain any additional arguments, so `_value` is just an empty list. + """ + return FeltType() + + def type_bool(self, _value: List[Any]) -> BoolType: + """ + Bool does not contain any additional arguments, so `_value` is just an empty list. + """ + return BoolType() + + def type_uint(self, value: List[Token]) -> UintType: + """ + Uint type contains information about its size. It is present in the value[0]. + """ + return UintType(int(value[0])) + + def type_unit(self, _value: List[Any]) -> UnitType: + """ + `()` type. + """ + return UnitType() + + def type_option(self, value: List[CairoType]) -> OptionType: + """ + Option includes an information about which type it eventually represents. + `Optional` is added in case of the unit type. + """ + return OptionType(value[0]) + + def type_array(self, value: List[CairoType]) -> ArrayType: + """ + Array contains values of type under `value[0]`. + """ + return ArrayType(value[0]) + + def type_span(self, value: List[CairoType]) -> ArrayType: + """ + Span contains values of type under `value[0]`. + """ + return ArrayType(value[0]) + + def type_identifier(self, tokens: List[Token]) -> TypeIdentifier: + """ + Structs and enums are defined as follows: (IDENTIFIER | "::")+ [some not important info] + where IDENTIFIER is a string. + + Tokens would contain strings and types (if it is present). + We are interested only in the strings because a structure (or enum) name can be built from them. + """ + name = "::".join(token for token in tokens if isinstance(token, str)) + return TypeIdentifier(name) + + def type_contract_address(self, _value: List[Any]) -> FeltType: + """ + ContractAddress is represented by the felt252. + """ + return FeltType() + + def type_class_hash(self, _value: List[Any]) -> FeltType: + """ + ClassHash is represented by the felt252. + """ + return FeltType() + + def type_storage_address(self, _value: List[Any]) -> FeltType: + """ + StorageAddress is represented by the felt252. + """ + return FeltType() + + def tuple(self, types: List[CairoType]) -> TupleType: + """ + Tuple contains values defined in the `types` argument. + """ + return TupleType(types) + + +def parse( + code: str, +) -> CairoType: + """ + Parse the given string and return a CairoType. + """ + grammar_parser = lark.Lark( + grammar=ABI_EBNF, + start="type", + parser="earley", + ) + parsed = grammar_parser.parse(code) + + transformed = ParserTransformer().transform(parsed) + + return transformed diff --git a/starknet_py/abi/v1/parser_transformer_test.py b/starknet_py/abi/v1/parser_transformer_test.py new file mode 100644 index 000000000..ed99ee56a --- /dev/null +++ b/starknet_py/abi/v1/parser_transformer_test.py @@ -0,0 +1,9 @@ +import pytest +from lark import Token, Tree + +from starknet_py.abi.v1.parser_transformer import ParserTransformer + + +def test_default_parser_transformer(): + with pytest.raises(TypeError, match="Unable to parse tree node of type wrong."): + ParserTransformer().transform(Tree(data=Token("RULE", "wrong"), children=[])) diff --git a/starknet_py/abi/v1/schemas.py b/starknet_py/abi/v1/schemas.py new file mode 100644 index 000000000..dacb4d1cc --- /dev/null +++ b/starknet_py/abi/v1/schemas.py @@ -0,0 +1,66 @@ +from marshmallow import Schema, fields +from marshmallow_oneofschema import OneOfSchema + +from starknet_py.abi.v1.shape import ( + ENUM_ENTRY, + EVENT_ENTRY, + FUNCTION_ENTRY, + STRUCT_ENTRY, +) + + +class TypeSchema(Schema): + type = fields.String(data_key="type", required=True) + + +class TypedParameterSchema(TypeSchema): + name = fields.String(data_key="name", required=True) + + +class FunctionBaseSchema(Schema): + name = fields.String(data_key="name", required=True) + inputs = fields.List( + fields.Nested(TypedParameterSchema()), data_key="inputs", required=True + ) + outputs = fields.List( + fields.Nested(TypeSchema()), data_key="outputs", required=True + ) + state_mutability = fields.String(data_key="state_mutability", default=None) + + +class FunctionAbiEntrySchema(FunctionBaseSchema): + type = fields.Constant(FUNCTION_ENTRY, data_key="type", required=True) + + +class EventAbiEntrySchema(Schema): + type = fields.Constant(EVENT_ENTRY, data_key="type", required=True) + name = fields.String(data_key="name", required=True) + inputs = fields.List( + fields.Nested(TypedParameterSchema()), data_key="inputs", required=True + ) + + +class StructAbiEntrySchema(Schema): + type = fields.Constant(STRUCT_ENTRY, data_key="type", required=True) + name = fields.String(data_key="name", required=True) + members = fields.List( + fields.Nested(TypedParameterSchema()), data_key="members", required=True + ) + + +class EnumAbiEntrySchema(Schema): + type = fields.Constant(ENUM_ENTRY, data_key="type", required=True) + name = fields.String(data_key="name", required=True) + variants = fields.List( + fields.Nested(TypedParameterSchema(), data_key="variants", required=True) + ) + + +class ContractAbiEntrySchema(OneOfSchema): + type_field_remove = False + type_schemas = { + FUNCTION_ENTRY: FunctionAbiEntrySchema, + EVENT_ENTRY: EventAbiEntrySchema, + STRUCT_ENTRY: StructAbiEntrySchema, + ENUM_ENTRY: EnumAbiEntrySchema, + } diff --git a/starknet_py/abi/v1/schemas_test.py b/starknet_py/abi/v1/schemas_test.py new file mode 100644 index 000000000..95ca85245 --- /dev/null +++ b/starknet_py/abi/v1/schemas_test.py @@ -0,0 +1,32 @@ +import json + +import pytest +from marshmallow import EXCLUDE + +from starknet_py.abi.v1.schemas import ContractAbiEntrySchema +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR +from starknet_py.tests.e2e.fixtures.misc import read_contract + + +@pytest.mark.parametrize( + "contract_name", + [ + "account", + "erc20", + "hello_starknet", + "minimal_contract", + "test_contract", + "token_bridge", + ], +) +def test_deserialize_abi(contract_name): + abi = json.loads( + read_contract( + f"{contract_name}_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + )["abi"] + deserialized = [ + ContractAbiEntrySchema().load(entry, unknown=EXCLUDE) for entry in abi + ] + + assert len(deserialized) == len(abi) diff --git a/starknet_py/abi/v1/shape.py b/starknet_py/abi/v1/shape.py new file mode 100644 index 000000000..0a4b94f8c --- /dev/null +++ b/starknet_py/abi/v1/shape.py @@ -0,0 +1,47 @@ +from typing import List, Literal, Optional, TypedDict, Union + +ENUM_ENTRY = "enum" +STRUCT_ENTRY = "struct" +FUNCTION_ENTRY = "function" +EVENT_ENTRY = "event" + + +class TypeDict(TypedDict): + type: str + + +class TypedParameterDict(TypeDict): + name: str + + +class StructDict(TypedDict): + type: Literal["struct"] + name: str + members: List[TypedParameterDict] + + +class FunctionBaseDict(TypedDict): + name: str + inputs: List[TypedParameterDict] + outputs: List[TypeDict] + state_mutability: Optional[Literal["external", "view"]] + + +class FunctionDict(FunctionBaseDict): + type: Literal["function"] + + +class EventDict(TypedDict): + name: str + type: Literal["event"] + inputs: List[TypedParameterDict] + + +class EnumDict(TypedDict): + type: Literal["enum"] + name: str + variants: List[TypedParameterDict] + + +AbiDictEntry = Union[StructDict, FunctionDict, EventDict, EnumDict] +AbiDictList = List[AbiDictEntry] diff --git a/starknet_py/cairo/data_types.py b/starknet_py/cairo/data_types.py index b0a034949..d26844c3b 100644 --- a/starknet_py/cairo/data_types.py +++ b/starknet_py/cairo/data_types.py @@ -19,6 +19,13 @@ class FeltType(CairoType): """ +@dataclass +class BoolType(CairoType): + """ + Type representation of Cairo boolean. + """ + + @dataclass class TupleType(CairoType): """ @@ -55,3 +62,52 @@ class StructType(CairoType): name: str #: Structure name # We need ordered dict, because it is important in serialization types: OrderedDict[str, CairoType] #: types of every structure member. + + +@dataclass +class EnumType(CairoType): + """ + Type representation of Cairo enums. + """ + + name: str + variants: OrderedDict[str, CairoType] + + +@dataclass +class OptionType(CairoType): + """ + Type representation of Cairo options. + """ + + type: CairoType + + +@dataclass +class UintType(CairoType): + """ + Type representation of Cairo unsigned integers. + """ + + bits: int + + def check_range(self, value: int): + """ + Utility method checking if the `value` is in range. + """ + + +@dataclass +class TypeIdentifier(CairoType): + """ + Type representation of Cairo identifiers. + """ + + name: str + + +@dataclass +class UnitType(CairoType): + """ + Type representation of Cairo unit `()`. + """ diff --git a/starknet_py/cairo/v1/__init__.py b/starknet_py/cairo/v1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/starknet_py/cairo/v1/type_parser.py b/starknet_py/cairo/v1/type_parser.py new file mode 100644 index 000000000..74f1f35ce --- /dev/null +++ b/starknet_py/cairo/v1/type_parser.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from typing import Dict, Union + +from starknet_py.abi.v1.parser_transformer import parse +from starknet_py.cairo.data_types import ( + ArrayType, + CairoType, + EnumType, + StructType, + TypeIdentifier, +) + + +class UnknownCairoTypeError(ValueError): + """ + Error thrown when TypeParser finds type that was not declared prior to parsing. + """ + + type_name: str + + def __init__(self, type_name: str): + super().__init__( + # pylint: disable=line-too-long + f"Type '{type_name}' is not defined. Please report this issue at https://github.com/software-mansion/starknet.py/issues" + ) + self.type_name = type_name + + +class TypeParser: + """ + Low level utility class for parsing Cairo types that can be used in external methods. + """ + + defined_types: Dict[str, Union[StructType, EnumType]] + + def __init__(self, defined_types: Dict[str, Union[StructType, EnumType]]): + """ + TypeParser constructor. + + :param defined_types: dictionary containing all defined types. For now, they can only be structures. + """ + self.defined_types = defined_types + for name, defined_type in defined_types.items(): + if name != defined_type.name: + raise ValueError( + f"Keys must match name of type, '{name}' != '{defined_type.name}'." + ) + + def parse_inline_type(self, type_string: str) -> CairoType: + """ + Inline type is one that can be used inline, for instance as return type. For instance + (core::felt252, (), (core::felt252,)). Structure can only be referenced in inline type, can't be defined + this way. + + :param type_string: type to parse. + """ + parsed = parse(type_string) + + if isinstance(parsed, TypeIdentifier): + return self._get_struct(parsed) + + # TODO (#1079): add recursive support for iterables with structures (tuples?) + if isinstance(parsed, ArrayType): + inner_type_string = type_string[ + type_string.find("<") + 1 : type_string.rfind(">") + ] + parsed.inner_type = self.parse_inline_type(inner_type_string) + + return parsed + + def _get_struct(self, identifier: TypeIdentifier): + for struct_name in self.defined_types.keys(): + if identifier.name == struct_name.split("<")[0].strip(":"): + return self.defined_types[struct_name] + raise UnknownCairoTypeError(identifier.name) diff --git a/starknet_py/cairo/v1/type_parser_test.py b/starknet_py/cairo/v1/type_parser_test.py new file mode 100644 index 000000000..0ebc68582 --- /dev/null +++ b/starknet_py/cairo/v1/type_parser_test.py @@ -0,0 +1,111 @@ +from collections import OrderedDict +from typing import Dict, Union + +import pytest + +from starknet_py.cairo.data_types import ( + ArrayType, + EnumType, + FeltType, + StructType, + TupleType, + TypeIdentifier, + UnitType, +) +from starknet_py.cairo.v1.type_parser import TypeParser, UnknownCairoTypeError + + +@pytest.mark.parametrize( + "type_string, expected", + [ + ("core::felt252", FeltType()), + ("core::array::Array::", ArrayType(FeltType())), + ( + "(core::felt252, core::felt252, core::felt252, core::felt252)", + TupleType([FeltType()] * 4), + ), + ("(,)", TupleType([])), + ("()", UnitType()), + ( + "(core::felt252, (core::felt252, (core::array::Array::, core::felt252)))", + TupleType( + [ + FeltType(), + TupleType( + [ + FeltType(), + TupleType( + [ + ArrayType(FeltType()), + FeltType(), + ] + ), + ] + ), + ] + ), + ), + ], +) +def test_parse_without_defined_types(type_string, expected): + parsed = TypeParser({}).parse_inline_type(type_string) + assert parsed == expected + + +uint256_type = StructType("Uint256", OrderedDict(low=FeltType(), high=FeltType())) +wrapped_felt_type = StructType("WrappedFelt", OrderedDict(value=FeltType())) + + +@pytest.mark.parametrize( + "type_string, expected", + [ + ("Uint256", uint256_type), + ("core::array::Array::", ArrayType(uint256_type)), + ( + "(Uint256, WrappedFelt)", + TupleType([TypeIdentifier("Uint256"), TypeIdentifier("WrappedFelt")]), + ), + ( + "(Uint256, (WrappedFelt, (core::array::Array::, core::array::Array::)))", + TupleType( + [ + TypeIdentifier("Uint256"), + TupleType( + [ + TypeIdentifier("WrappedFelt"), + TupleType( + [ + ArrayType(FeltType()), + ArrayType(TypeIdentifier("WrappedFelt")), + ] + ), + ] + ), + ] + ), + ), + ], +) +def test_parse_with_defined_types(type_string, expected): + types: Dict[str, Union[StructType, EnumType]] = { + "Uint256": uint256_type, + "WrappedFelt": wrapped_felt_type, + } + parsed = TypeParser(types).parse_inline_type(type_string) + assert parsed == expected + + +def test_missing_type(): + with pytest.raises( + UnknownCairoTypeError, match="Type 'Uint256' is not defined" + ) as err_info: + TypeParser({}).parse_inline_type("Uint256") + + assert err_info.value.type_name == "Uint256" + + +def test_names_not_matching(): + with pytest.raises( + ValueError, match="Keys must match name of type, 'OtherName' != 'Uint256'." + ): + TypeParser({"OtherName": uint256_type}) diff --git a/starknet_py/conftest.py b/starknet_py/conftest.py index 4c4de0294..f586a5338 100644 --- a/starknet_py/conftest.py +++ b/starknet_py/conftest.py @@ -4,6 +4,7 @@ "starknet_py.tests.e2e.fixtures.clients", "starknet_py.tests.e2e.fixtures.accounts", "starknet_py.tests.e2e.fixtures.contracts", + "starknet_py.tests.e2e.fixtures.contracts_v1", "starknet_py.tests.e2e.fixtures.misc", "starknet_py.tests.e2e.fixtures.devnet", "starknet_py.tests.e2e.fixtures.constants", diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 2380be177..07974bdd0 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -1,18 +1,25 @@ from __future__ import annotations import dataclasses -import warnings +import json from dataclasses import dataclass from functools import cached_property -from typing import Dict, List, Optional, Tuple, TypeVar, Union +from typing import Dict, List, Optional, Tuple, TypeVar, Union, cast from marshmallow import ValidationError from starknet_py.abi.model import Abi from starknet_py.abi.parser import AbiParser -from starknet_py.common import create_compiled_contract +from starknet_py.abi.v1.model import Abi as AbiV1 +from starknet_py.abi.v1.parser import AbiParser as AbiV1Parser +from starknet_py.common import ( + create_casm_class, + create_compiled_contract, + create_sierra_compiled_contract, +) from starknet_py.constants import DEFAULT_DEPLOYER_ADDRESS from starknet_py.hash.address import compute_address +from starknet_py.hash.casm_class_hash import compute_casm_class_hash from starknet_py.hash.class_hash import compute_class_hash from starknet_py.hash.selector import get_selector_from_name from starknet_py.net.account.base_account import BaseAccount @@ -23,10 +30,10 @@ from starknet_py.proxy.contract_abi_resolver import ( ContractAbiResolver, ProxyConfig, - UnsupportedAbiError, prepare_proxy_config, ) from starknet_py.serialization import TupleDataclass, serializer_for_function +from starknet_py.serialization.factory import serializer_for_function_v1 from starknet_py.serialization.function_serialization_adapter import ( FunctionSerializationAdapter, ) @@ -46,28 +53,33 @@ class ContractData: address: int abi: ABI + cairo_version: int @cached_property - def parsed_abi(self) -> Abi: + def parsed_abi(self) -> Union[Abi, AbiV1]: """ Abi parsed into proper dataclass. :return: Abi """ + if self.cairo_version == 1: + return AbiV1Parser(self.abi).parse() return AbiParser(self.abi).parse() @staticmethod - def from_abi(address: int, abi: ABI) -> ContractData: + def from_abi(address: int, abi: ABI, cairo_version: int = 0) -> ContractData: """ Create ContractData from ABI. :param address: Address of the deployed contract. :param abi: Abi of the contract. + :param cairo_version: Version of the Cairo in which contract is written. :return: ContractData instance. """ return ContractData( address=address, abi=abi, + cairo_version=cairo_version, ) @@ -140,6 +152,7 @@ class DeclareResult(SentTransaction): """ _account: BaseAccount = None # pyright: ignore + _cairo_version: int = 0 class_hash: int = None # pyright: ignore """Class hash of the declared contract.""" @@ -182,15 +195,31 @@ async def deploy( :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :return: DeployResult instance. """ - # pylint: disable=too-many-arguments - abi = create_compiled_contract(compiled_contract=self.compiled_contract).abi + # pylint: disable=too-many-arguments, too-many-locals + if self._cairo_version == 0: + abi = create_compiled_contract(compiled_contract=self.compiled_contract).abi + else: + try: + sierra_compiled_contract = create_sierra_compiled_contract( + compiled_contract=self.compiled_contract + ) + abi = json.loads(sierra_compiled_contract.abi) + except Exception as exc: + raise ValueError( + "Contract's ABI can't be converted to format List[Dict]. " + "Make sure provided compiled_contract is correct." + ) from exc deployer = Deployer( deployer_address=deployer_address, account_address=self._account.address if unique else None, ) deploy_call, address = deployer.create_contract_deployment( - class_hash=self.class_hash, salt=salt, abi=abi, calldata=constructor_args + class_hash=self.class_hash, + salt=salt, + abi=abi, + calldata=constructor_args, + cairo_version=self._cairo_version, ) res = await self._account.execute( calls=deploy_call, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate @@ -200,6 +229,7 @@ async def deploy( provider=self._account, address=address, abi=abi, + cairo_version=self._cairo_version, ) deploy_result = DeployResult( @@ -363,6 +393,7 @@ def __init__( contract_data: ContractData, client: Client, account: Optional[BaseAccount], + cairo_version: int = 0, ): # pylint: disable=too-many-arguments self.name = name @@ -371,8 +402,14 @@ def __init__( self.contract_data = contract_data self.client = client self.account = account - self._payload_transformer = serializer_for_function( - contract_data.parsed_abi.functions[name] + self._payload_transformer = ( + serializer_for_function_v1( + cast(AbiV1, contract_data.parsed_abi).functions[name] + ) + if cairo_version == 1 + else serializer_for_function( + cast(Abi, contract_data.parsed_abi).functions[name] + ) ) def prepare( @@ -464,6 +501,8 @@ def __init__( address: AddressRepresentation, abi: list, provider: Union[BaseAccount, Client], + *, + cairo_version: int = 0, ): """ Should be used instead of ``from_address`` when ABI is known statically. @@ -473,22 +512,25 @@ def __init__( :param address: contract's address. :param abi: contract's abi. :param provider: BaseAccount or Client used to perform transactions. + :param cairo_version: Version of the Cairo in which contract is written. """ client, account = _unpack_provider(provider) self.account: Optional[BaseAccount] = account self.client: Client = client - self.data = ContractData.from_abi(parse_address(address), abi) + self.data = ContractData.from_abi(parse_address(address), abi, cairo_version) try: - self._functions = self._make_functions(self.data, self.client, self.account) - except ValidationError: - warnings.warn( - "Make sure valid ABI is used to create a Contract instance: " - "Cairo1 contract ABIs are currently unsupported." + self._functions = self._make_functions( + contract_data=self.data, + client=self.client, + account=self.account, + cairo_version=cairo_version, ) - # Re-raise the exception - raise + except ValidationError as exc: + raise ValueError( + "Make sure valid ABI is used to create a Contract instance" + ) from exc @property def functions(self) -> FunctionsRepository: @@ -534,22 +576,24 @@ async def from_address( address = parse_address(address) proxy_config = Contract._create_proxy_config(proxy_config) - try: - abi = await ContractAbiResolver( - address=address, client=client, proxy_config=proxy_config - ).resolve() - except UnsupportedAbiError as err: - raise ValueError( - "Provided address of Cairo1 contract which is currently not supported in Contract." - ) from err + abi, cairo_version = await ContractAbiResolver( + address=address, client=client, proxy_config=proxy_config + ).resolve() - return Contract(address=address, abi=abi, provider=account or client) + return Contract( + address=address, + abi=abi, + provider=account or client, + cairo_version=cairo_version, + ) @staticmethod async def declare( account: BaseAccount, compiled_contract: str, *, + compiled_contract_casm: Optional[str] = None, + casm_class_hash: Optional[int] = None, nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, @@ -559,18 +603,43 @@ async def declare( :param account: BaseAccount used to sign and send declare transaction. :param compiled_contract: String containing compiled contract. + :param compiled_contract_casm: String containing the content of the starknet-sierra-compile (.casm file). + Used when declaring Cairo1 contracts. + :param casm_class_hash: Hash of the compiled_contract_casm. :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :return: DeclareResult instance. """ - declare_tx = await account.sign_declare_transaction( - compiled_contract=compiled_contract, - nonce=nonce, - max_fee=max_fee, - auto_estimate=auto_estimate, - ) + if Contract._get_cairo_version(compiled_contract) == 1: + if casm_class_hash is None and compiled_contract_casm is None: + raise ValueError( + "Cairo 1.0 contract was provided without casm_class_hash or compiled_contract_casm argument." + ) + + cairo_version = 1 + if casm_class_hash is None: + assert compiled_contract_casm is not None + casm_class_hash = compute_casm_class_hash( + create_casm_class(compiled_contract_casm) + ) + + declare_tx = await account.sign_declare_v2_transaction( + compiled_contract=compiled_contract, + compiled_class_hash=casm_class_hash, + nonce=nonce, + max_fee=max_fee, + auto_estimate=auto_estimate, + ) + else: + cairo_version = 0 + declare_tx = await account.sign_declare_transaction( + compiled_contract=compiled_contract, + nonce=nonce, + max_fee=max_fee, + auto_estimate=auto_estimate, + ) res = await account.client.declare(transaction=declare_tx) return DeclareResult( @@ -579,8 +648,13 @@ async def declare( class_hash=res.class_hash, _account=account, compiled_contract=compiled_contract, + _cairo_version=cairo_version, ) + @staticmethod + def _get_cairo_version(compiled_contract: str) -> int: + return 1 if "sierra_program" in compiled_contract else 0 + @staticmethod async def deploy_contract( account: BaseAccount, @@ -589,6 +663,7 @@ async def deploy_contract( constructor_args: Optional[Union[List, Dict]] = None, *, deployer_address: AddressRepresentation = DEFAULT_DEPLOYER_ADDRESS, + cairo_version: int = 0, nonce: Optional[int] = None, max_fee: Optional[int] = None, auto_estimate: bool = False, @@ -600,10 +675,11 @@ async def deploy_contract( :param class_hash: The class_hash of the contract to be deployed. :param abi: An abi of the contract to be deployed. :param constructor_args: a ``list`` or ``dict`` of arguments for the constructor. - :param nonce: Nonce of the transaction. :param deployer_address: Address of the UDC. Is set to the address of the default UDC (same address on mainnet/testnet/devnet) by default. Must be set when using custom network other than ones listed above. + :param cairo_version: Version of the Cairo in which contract is written. + :param nonce: Nonce of the transaction. :param max_fee: Max amount of Wei to be paid when executing transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :return: DeployResult instance. @@ -613,16 +689,17 @@ async def deploy_contract( deployer_address=deployer_address, account_address=account.address ) deploy_call, address = deployer.create_contract_deployment( - class_hash=class_hash, abi=abi, calldata=constructor_args + class_hash=class_hash, + abi=abi, + calldata=constructor_args, + cairo_version=cairo_version, ) res = await account.execute( calls=deploy_call, nonce=nonce, max_fee=max_fee, auto_estimate=auto_estimate ) deployed_contract = Contract( - provider=account, - address=address, - abi=abi, + provider=account, address=address, abi=abi, cairo_version=cairo_version ) deploy_result = DeployResult( hash=res.transaction_hash, @@ -674,7 +751,11 @@ def compute_contract_hash(compiled_contract: str) -> int: @classmethod def _make_functions( - cls, contract_data: ContractData, client: Client, account: Optional[BaseAccount] + cls, + contract_data: ContractData, + client: Client, + account: Optional[BaseAccount], + cairo_version: int = 0, ) -> FunctionsRepository: repository = {} @@ -689,6 +770,7 @@ def _make_functions( contract_data=contract_data, client=client, account=account, + cairo_version=cairo_version, ) return repository diff --git a/starknet_py/contract_test.py b/starknet_py/contract_test.py index 94f75fe82..04ca51597 100644 --- a/starknet_py/contract_test.py +++ b/starknet_py/contract_test.py @@ -64,3 +64,21 @@ def test_contract_create_with_client(client): contract = Contract(address=0x1, abi=[], provider=client) assert contract.account is None assert contract.client == client + + +def test_throws_on_wrong_abi(account): + with pytest.raises( + ValueError, match="Make sure valid ABI is used to create a Contract instance" + ): + Contract( + address=0x1, + abi=[ + { + "type": "function", + "name": "empty", + "inputs": "", # inputs should be a list + } + ], + provider=account, + cairo_version=1, + ) diff --git a/starknet_py/net/udc_deployer/deployer.py b/starknet_py/net/udc_deployer/deployer.py index ff54c5b3f..805a15eeb 100644 --- a/starknet_py/net/udc_deployer/deployer.py +++ b/starknet_py/net/udc_deployer/deployer.py @@ -86,6 +86,7 @@ def create_contract_deployment( *, salt: Optional[int] = None, abi: Optional[List] = None, + cairo_version: int = 0, calldata: Optional[Union[List, dict]] = None, ) -> ContractDeployment: """ @@ -94,6 +95,8 @@ def create_contract_deployment( :param class_hash: The class_hash of the contract to be deployed. :param salt: The salt for a contract to be deployed. Random value is selected if it is not provided. :param abi: ABI of the contract to be deployed. + :param cairo_version: Version of the Cairo [0 or 1] in which contract to be deployed is written. + Used when abi is provided. :param calldata: Constructor args of the contract to be deployed. :return: NamedTuple with call and address of the contract to be deployed. """ @@ -101,7 +104,7 @@ def create_contract_deployment( raise ValueError("Argument calldata was provided without an ABI.") raw_calldata = translate_constructor_args( - abi=abi or [], constructor_args=calldata + abi=abi or [], constructor_args=calldata, cairo_version=cairo_version ) return self.create_contract_deployment_raw( diff --git a/starknet_py/proxy/contract_abi_resolver.py b/starknet_py/proxy/contract_abi_resolver.py index e841e86fd..fd846b09a 100644 --- a/starknet_py/proxy/contract_abi_resolver.py +++ b/starknet_py/proxy/contract_abi_resolver.py @@ -1,6 +1,7 @@ +import json import re from enum import Enum -from typing import AsyncGenerator, List, Tuple, TypedDict, Union +from typing import AsyncGenerator, List, Tuple, TypedDict, Union, cast from starknet_py.abi.shape import AbiDictList from starknet_py.constants import ( @@ -73,9 +74,10 @@ def __init__( self.client = client self.proxy_config = proxy_config - async def resolve(self) -> AbiDictList: + async def resolve(self) -> Tuple[AbiDictList, int]: """ - Returns abi of either direct contract or contract proxied by direct contract depending on proxy_config. + Returns abi and cairo version of either direct contract + or contract proxied by direct contract depending on proxy_config. :raises ContractNotFoundError: when contract could not be found at address :raises ProxyResolutionError: when given ProxyChecks were not sufficient to resolve proxy @@ -85,26 +87,25 @@ async def resolve(self) -> AbiDictList: return await self.get_abi_for_address() return await self.resolve_abi() - async def get_abi_for_address(self) -> AbiDictList: + async def get_abi_for_address(self) -> Tuple[AbiDictList, int]: """ - Returns abi of a contract directly from address. + Returns abi and cairo version of a contract directly from address. :raises ContractNotFoundError: when contract could not be found at address :raises AbiNotFoundError: when abi is not present in contract class at address """ contract_class = await _get_class_at(address=self.address, client=self.client) - if isinstance(contract_class, SierraContractClass): - # TODO (#1012): Consider better handling - raise UnsupportedAbiError( - "Proxy resolver does not currently support Cairo1 ABIs." - ) + if contract_class.abi is None: raise AbiNotFoundError() - return contract_class.abi - async def resolve_abi(self) -> AbiDictList: + return self._get_abi_from_contract_class( + contract_class + ), self._get_cairo_version(contract_class) + + async def resolve_abi(self) -> Tuple[AbiDictList, int]: """ - Returns abi of a contract that is being proxied by contract at address. + Returns abi and cairo version of a contract that is being proxied by contract at address. :raises ContractNotFoundError: when contract could not be found at address :raises ProxyResolutionError: when given ProxyChecks were not sufficient to resolve proxy @@ -122,15 +123,13 @@ async def resolve_abi(self) -> AbiDictList: address=implementation, client=self.client ) - if isinstance(contract_class, SierraContractClass): - # TODO (#1012): Consider better handling - raise UnsupportedAbiError( - "Proxy resolver does not currently support Cairo1 ABIs." - ) if contract_class.abi is None: # Some contract_class has been found, but it does not have abi raise AbiNotFoundError() - return contract_class.abi + + return self._get_abi_from_contract_class( + contract_class + ), self._get_cairo_version(contract_class) except ClientError as err: if not ( "is not declared" in err.message @@ -141,6 +140,22 @@ async def resolve_abi(self) -> AbiDictList: raise ProxyResolutionError() + @staticmethod + def _get_cairo_version( + contract_class: Union[ContractClass, SierraContractClass] + ) -> int: + return 1 if isinstance(contract_class, SierraContractClass) else 0 + + @staticmethod + def _get_abi_from_contract_class( + contract_class: Union[ContractClass, SierraContractClass] + ) -> AbiDictList: + return ( + cast(AbiDictList, contract_class.abi) + if isinstance(contract_class, ContractClass) + else json.loads(cast(str, contract_class.abi)) + ) + async def _get_implementation_from_proxy( self, ) -> AsyncGenerator[Tuple[int, ImplementationType], None]: @@ -178,16 +193,6 @@ class AbiNotFoundError(Exception): """ -class UnsupportedAbiError(Exception): - """ - Incompatible Abi error. - """ - - def __init__(self, message): - self.message = message - super().__init__(message) - - class ProxyResolutionError(Exception): """ Error while resolving proxy using ProxyChecks. diff --git a/starknet_py/serialization/data_serializers/__init__.py b/starknet_py/serialization/data_serializers/__init__.py index 49d854e87..017d4797f 100644 --- a/starknet_py/serialization/data_serializers/__init__.py +++ b/starknet_py/serialization/data_serializers/__init__.py @@ -1,4 +1,5 @@ from .array_serializer import ArraySerializer +from .bool_serializer import BoolSerializer from .cairo_data_serializer import CairoDataSerializer from .felt_serializer import FeltSerializer from .named_tuple_serializer import NamedTupleSerializer diff --git a/starknet_py/serialization/data_serializers/bool_serializer.py b/starknet_py/serialization/data_serializers/bool_serializer.py new file mode 100644 index 000000000..f5cfb67fd --- /dev/null +++ b/starknet_py/serialization/data_serializers/bool_serializer.py @@ -0,0 +1,37 @@ +from dataclasses import dataclass +from typing import Generator + +from starknet_py.serialization._context import ( + Context, + DeserializationContext, + SerializationContext, +) +from starknet_py.serialization.data_serializers.cairo_data_serializer import ( + CairoDataSerializer, +) + + +@dataclass +class BoolSerializer(CairoDataSerializer[bool, int]): + """ + Serializer for boolean. + """ + + def deserialize_with_context(self, context: DeserializationContext) -> bool: + [val] = context.reader.read(1) + self._ensure_bool(context, val) + return bool(val) + + def serialize_with_context( + self, context: SerializationContext, value: bool + ) -> Generator[int, None, None]: + context.ensure_valid_type(value, isinstance(value, bool), "bool") + self._ensure_bool(context, value) + yield int(value) + + @staticmethod + def _ensure_bool(context: Context, value: int): + context.ensure_valid_value( + value in [0, 1], + f"invalid value '{value}' - must be in [0, 2) range", + ) diff --git a/starknet_py/serialization/data_serializers/bool_serializer_test.py b/starknet_py/serialization/data_serializers/bool_serializer_test.py new file mode 100644 index 000000000..a51360442 --- /dev/null +++ b/starknet_py/serialization/data_serializers/bool_serializer_test.py @@ -0,0 +1,24 @@ +from typing import cast + +import pytest + +from starknet_py.serialization.data_serializers import BoolSerializer +from starknet_py.serialization.errors import InvalidTypeException + + +@pytest.mark.parametrize( + "value", + [True, False], +) +def test_valid_bool_values(value): + serialized = BoolSerializer().serialize(value) + deserialized = BoolSerializer().deserialize([value]) + + assert deserialized == value + assert serialized == [value] + + +def test_invalid_type(): + error_message = "Error: expected bool, received '{}' of type '." + with pytest.raises(InvalidTypeException, match=error_message): + BoolSerializer().serialize(cast(bool, {})) diff --git a/starknet_py/serialization/data_serializers/cairo_data_serializer.py b/starknet_py/serialization/data_serializers/cairo_data_serializer.py index a431fe666..2bad3b8e6 100644 --- a/starknet_py/serialization/data_serializers/cairo_data_serializer.py +++ b/starknet_py/serialization/data_serializers/cairo_data_serializer.py @@ -39,7 +39,9 @@ def serialize(self, data: SerializationType) -> CairoData: :return: calldata. """ with SerializationContext.create() as context: - return list(self.serialize_with_context(context, data)) + serialized_data = list(self.serialize_with_context(context, data)) + + return self.remove_units_from_serialized_data(serialized_data) @abstractmethod def deserialize_with_context( @@ -63,3 +65,7 @@ def serialize_with_context( :param value: python value to serialize. :return: defined SerializationType. """ + + @staticmethod + def remove_units_from_serialized_data(serialized_data: List) -> List: + return [x for x in serialized_data if x is not None] diff --git a/starknet_py/serialization/data_serializers/enum_serializer.py b/starknet_py/serialization/data_serializers/enum_serializer.py new file mode 100644 index 000000000..b31b938ae --- /dev/null +++ b/starknet_py/serialization/data_serializers/enum_serializer.py @@ -0,0 +1,71 @@ +from dataclasses import dataclass +from typing import Dict, Generator, OrderedDict, Tuple, Union + +from starknet_py.serialization._context import ( + DeserializationContext, + SerializationContext, +) +from starknet_py.serialization.data_serializers.cairo_data_serializer import ( + CairoDataSerializer, +) +from starknet_py.serialization.tuple_dataclass import TupleDataclass + + +@dataclass +class EnumSerializer(CairoDataSerializer[Union[Dict, TupleDataclass], TupleDataclass]): + """ + Serializer of enums. + Can serialize a dictionary and TupleDataclass. + Deserializes data to a TupleDataclass. + + Example: + enum MyEnum { + a: u128, + b: u128 + } + + {"a": 1} => [0, 1] + {"b": 100} => [1, 100] + TupleDataclass(variant='a', value=100) => [0, 100] + """ + + serializers: OrderedDict[str, CairoDataSerializer] + + def deserialize_with_context( + self, context: DeserializationContext + ) -> TupleDataclass: + [variant_index] = context.reader.read(1) + variant_name, serializer = self._get_variant(variant_index) + + with context.push_entity("enum.variant: " + variant_name): + result_dict = { + "variant": variant_name, + "value": serializer.deserialize_with_context(context), + } + + return TupleDataclass.from_dict(result_dict) + + def serialize_with_context( + self, context: SerializationContext, value: Union[Dict, TupleDataclass] + ) -> Generator[int, None, None]: + if isinstance(value, Dict): + items = list(value.items()) + if len(items) != 1: + raise ValueError( + "Can serialize only one enum variant, got: " + str(len(items)) + ) + + variant_name, variant_value = items[0] + else: + variant_name, variant_value = value + + yield self._get_variant_index(variant_name) + yield from self.serializers[variant_name].serialize_with_context( + context, variant_value + ) + + def _get_variant(self, variant_index: int) -> Tuple[str, CairoDataSerializer]: + return list(self.serializers.items())[variant_index] + + def _get_variant_index(self, variant_name: str) -> int: + return list(self.serializers.keys()).index(variant_name) diff --git a/starknet_py/serialization/data_serializers/enum_serializer_test.py b/starknet_py/serialization/data_serializers/enum_serializer_test.py new file mode 100644 index 000000000..f933fc796 --- /dev/null +++ b/starknet_py/serialization/data_serializers/enum_serializer_test.py @@ -0,0 +1,52 @@ +from collections import OrderedDict + +import pytest + +from starknet_py.serialization.data_serializers.enum_serializer import EnumSerializer +from starknet_py.serialization.data_serializers.option_serializer import ( + OptionSerializer, +) +from starknet_py.serialization.data_serializers.struct_serializer import ( + StructSerializer, +) +from starknet_py.serialization.data_serializers.uint_serializer import UintSerializer + +serializer = EnumSerializer( + serializers=OrderedDict( + a=UintSerializer(256), + b=UintSerializer(128), + c=StructSerializer( + OrderedDict( + my_option=OptionSerializer(UintSerializer(128)), + my_uint=UintSerializer(256), + ) + ), + ) +) + + +@pytest.mark.parametrize( + "value, correct_serialized_value", + [ + ({"a": 100}, [0, 100, 0]), + ({"b": 200}, [1, 200]), + ({"c": {"my_option": 300, "my_uint": 300}}, [2, 0, 300, 300, 0]), + ], +) +def test_output_serializer(value, correct_serialized_value): + deserialized = serializer.deserialize(correct_serialized_value) + + deserialized_and_serialized = serializer.serialize(deserialized) + serialized_value = serializer.serialize(value) + + assert deserialized_and_serialized == correct_serialized_value + assert serialized_value == correct_serialized_value + assert serialized_value == deserialized_and_serialized + + +def test_serializer_throws_on_wrong_parameters(): + with pytest.raises(ValueError, match="Can serialize only one enum variant, got: 2"): + serializer.serialize({"a": 100, "b": 200}) + + with pytest.raises(ValueError, match="Can serialize only one enum variant, got: 0"): + serializer.serialize({}) diff --git a/starknet_py/serialization/data_serializers/option_serializer.py b/starknet_py/serialization/data_serializers/option_serializer.py new file mode 100644 index 000000000..eab921256 --- /dev/null +++ b/starknet_py/serialization/data_serializers/option_serializer.py @@ -0,0 +1,41 @@ +from dataclasses import dataclass +from typing import Any, Generator, Optional + +from starknet_py.serialization import CairoDataSerializer +from starknet_py.serialization._context import ( + DeserializationContext, + SerializationContext, +) + + +@dataclass +class OptionSerializer(CairoDataSerializer[Optional[Any], Optional[Any]]): + """ + Serializer for Option type. + Can serialize None and common CairoTypes. + Deserializes data to None or CairoType. + + Example: + None => [1] + {"option1": 123, "option2": None} => [0, 123, 1] + """ + + serializer: CairoDataSerializer + + def deserialize_with_context( + self, context: DeserializationContext + ) -> Optional[Any]: + (is_none,) = context.reader.read(1) + if is_none == 1: + return None + + return self.serializer.deserialize_with_context(context) + + def serialize_with_context( + self, context: SerializationContext, value: Optional[Any] + ) -> Generator[int, None, None]: + if value is None: + yield 1 + else: + yield 0 + yield from self.serializer.serialize_with_context(context, value) diff --git a/starknet_py/serialization/data_serializers/option_serializer_test.py b/starknet_py/serialization/data_serializers/option_serializer_test.py new file mode 100644 index 000000000..e2cd76be6 --- /dev/null +++ b/starknet_py/serialization/data_serializers/option_serializer_test.py @@ -0,0 +1,23 @@ +import pytest + +from starknet_py.serialization.data_serializers.option_serializer import ( + OptionSerializer, +) +from starknet_py.serialization.data_serializers.uint_serializer import UintSerializer + + +@pytest.mark.parametrize( + "serializer, value, serialized_value", + [ + (OptionSerializer(UintSerializer(128)), 123, [0, 123]), + (OptionSerializer(UintSerializer(256)), 1, [0, 1, 0]), + (OptionSerializer(UintSerializer(128)), None, [1]), + (OptionSerializer(UintSerializer(256)), None, [1]), + ], +) +def test_option_serializer(serializer, value, serialized_value): + deserialized = serializer.deserialize(serialized_value) + assert deserialized == value + + serialized = serializer.serialize(value) + assert serialized == serialized_value diff --git a/starknet_py/serialization/data_serializers/output_serializer.py b/starknet_py/serialization/data_serializers/output_serializer.py new file mode 100644 index 000000000..9059aa662 --- /dev/null +++ b/starknet_py/serialization/data_serializers/output_serializer.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass, field +from typing import Dict, Generator, List, Tuple + +from starknet_py.serialization import CairoDataSerializer +from starknet_py.serialization._context import ( + DeserializationContext, + SerializationContext, +) + + +@dataclass +class OutputSerializer(CairoDataSerializer[List, Tuple]): + """ + Serializer for function output. + Can't serialize anything. + Deserializes data to a Tuple. + + Example: + [1, 1, 1] => (340282366920938463463374607431768211457) + """ + + serializers: List[CairoDataSerializer] = field(init=True) + + def deserialize_with_context(self, context: DeserializationContext) -> Tuple: + result = [] + + for index, serializer in enumerate(self.serializers): + with context.push_entity("output[" + str(index) + "]"): + result.append(serializer.deserialize_with_context(context)) + + return tuple(result) + + def serialize_with_context( + self, context: SerializationContext, value: Dict + ) -> Generator[int, None, None]: + raise ValueError( + "Output serializer can't be used to transform python data into calldata." + ) diff --git a/starknet_py/serialization/data_serializers/output_serializer_test.py b/starknet_py/serialization/data_serializers/output_serializer_test.py new file mode 100644 index 000000000..d1d00d621 --- /dev/null +++ b/starknet_py/serialization/data_serializers/output_serializer_test.py @@ -0,0 +1,54 @@ +import re +from collections import OrderedDict + +import pytest + +from starknet_py.serialization.data_serializers.option_serializer import ( + OptionSerializer, +) +from starknet_py.serialization.data_serializers.output_serializer import ( + OutputSerializer, +) +from starknet_py.serialization.data_serializers.struct_serializer import ( + StructSerializer, +) +from starknet_py.serialization.data_serializers.uint_serializer import UintSerializer +from starknet_py.serialization.data_serializers.uint_serializer_test import SHIFT + +serializer = OutputSerializer( + serializers=[ + UintSerializer(256), + OptionSerializer( + StructSerializer( + OrderedDict( + my_option=OptionSerializer(UintSerializer(128)), + my_uint=UintSerializer(256), + ) + ) + ), + ] +) + + +@pytest.mark.parametrize( + "value, serialized_value", + [ + ( + (1 + 1 * SHIFT, OrderedDict(my_option=123, my_uint=1 + 1 * SHIFT)), + [1, 1, 0, 0, 123, 1, 1], + ), + ((0, OrderedDict(my_option=None, my_uint=1)), [0, 0, 0, 1, 1, 0]), + ((1, None), [1, 0, 1]), + ], +) +def test_output_serializer_deserialize(value, serialized_value): + deserialized = serializer.deserialize(serialized_value) + assert deserialized == value + + +def test_output_serializer_serialize(): + error_message = re.escape( + "Output serializer can't be used to transform python data into calldata." + ) + with pytest.raises(ValueError, match=error_message): + serializer.serialize([1, None]) diff --git a/starknet_py/serialization/data_serializers/uint_serializer.py b/starknet_py/serialization/data_serializers/uint_serializer.py new file mode 100644 index 000000000..7f6322fc2 --- /dev/null +++ b/starknet_py/serialization/data_serializers/uint_serializer.py @@ -0,0 +1,98 @@ +from dataclasses import dataclass +from typing import Generator, TypedDict, Union + +from starknet_py.cairo.felt import uint256_range_check +from starknet_py.serialization import CairoDataSerializer +from starknet_py.serialization._context import ( + Context, + DeserializationContext, + SerializationContext, +) + + +class Uint256Dict(TypedDict): + low: int + high: int + + +@dataclass +class UintSerializer(CairoDataSerializer[Union[int, Uint256Dict], int]): + """ + Serializer of uint. In Cairo there are few uints (u8, ..., u128 and u256). + u256 is represented by structure {low: u128, high: u128}. + Can serialize an int and dict. + Deserializes data to an int. + + Examples: + if bits < 256: + 0 => [0] + 1 => [1] + 2**128-1 => [2**128-1] + else: + 0 => [0,0] + 1 => [1,0] + 2**128 => [0,1] + 3 + 2**128 => [3,1] + """ + + bits: int + + def deserialize_with_context(self, context: DeserializationContext) -> int: + if self.bits < 256: + (uint,) = context.reader.read(1) + with context.push_entity("uint" + str(self.bits)): + self._ensure_valid_uint(uint, context, self.bits) + + return uint + + [low, high] = context.reader.read(2) + + # Checking if resulting value is in [0, 2**256) range is not enough. Uint256 should be made of two uint128. + with context.push_entity("low"): + self._ensure_valid_uint(low, context, bits=128) + with context.push_entity("high"): + self._ensure_valid_uint(high, context, bits=128) + + return (high << 128) + low + + def serialize_with_context( + self, context: SerializationContext, value: Union[int, Uint256Dict] + ) -> Generator[int, None, None]: + context.ensure_valid_type(value, isinstance(value, (int, dict)), "int or dict") + if isinstance(value, int): + yield from self._serialize_from_int(value, context, self.bits) + else: + yield from self._serialize_from_dict(context, value) + + @staticmethod + def _serialize_from_int( + value: int, context: SerializationContext, bits: int + ) -> Generator[int, None, None]: + if bits < 256: + UintSerializer._ensure_valid_uint(value, context, bits) + + yield value + else: + uint256_range_check(value) + + result = (value % 2**128, value >> 128) + yield from result + + def _serialize_from_dict( + self, context: SerializationContext, value: Uint256Dict + ) -> Generator[int, None, None]: + with context.push_entity("low"): + self._ensure_valid_uint(value["low"], context, bits=128) + yield value["low"] + with context.push_entity("high"): + self._ensure_valid_uint(value["high"], context, bits=128) + yield value["high"] + + @staticmethod + def _ensure_valid_uint(value: int, context: Context, bits: int): + """ + Ensures that value is a valid uint on `bits` bits. + """ + context.ensure_valid_value( + 0 <= value < 2**bits, "expected value in range [0;2**" + str(bits) + ")" + ) diff --git a/starknet_py/serialization/data_serializers/uint_serializer_test.py b/starknet_py/serialization/data_serializers/uint_serializer_test.py new file mode 100644 index 000000000..0a421a4b0 --- /dev/null +++ b/starknet_py/serialization/data_serializers/uint_serializer_test.py @@ -0,0 +1,116 @@ +import re + +import pytest + +from starknet_py.serialization.data_serializers.uint_serializer import UintSerializer +from starknet_py.serialization.errors import InvalidTypeException, InvalidValueException + +u128_serializer = UintSerializer(bits=128) +u256_serializer = UintSerializer(bits=256) + +SHIFT = 2**128 +MAX_U128 = SHIFT - 1 + + +@pytest.mark.parametrize( + "value, serializer, serialized_value", + [ + (123 + 456 * SHIFT, u256_serializer, [123, 456]), + ( + 21323213211421424142 + 347932774343 * SHIFT, + u256_serializer, + [21323213211421424142, 347932774343], + ), + (0, u256_serializer, [0, 0]), + (MAX_U128, u256_serializer, [MAX_U128, 0]), + (MAX_U128 * SHIFT, u256_serializer, [0, MAX_U128]), + (MAX_U128 + MAX_U128 * SHIFT, u256_serializer, [MAX_U128, MAX_U128]), + (123, u128_serializer, [123]), + (0, u128_serializer, [0]), + (MAX_U128, u128_serializer, [MAX_U128]), + ], +) +def test_valid_values(value, serializer, serialized_value): + deserialized = serializer.deserialize(serialized_value) + assert deserialized == value + + serialized = serializer.serialize(value) + assert serialized == serialized_value + + if serializer.bits == 256: + assert serialized_value == serializer.serialize( + {"low": serialized_value[0], "high": serialized_value[1]} + ) + + +@pytest.mark.parametrize( + "value, uint256_part", + [ + ([MAX_U128 + 1, 0], "low"), + ([MAX_U128 + 1, MAX_U128 + 1], "low"), + ([-1, 0], "low"), + ([0, MAX_U128 + 1], "high"), + ([0, -1], "high"), + ], +) +def test_deserialize_invalid_256_values(value, uint256_part): + # We need to escape braces + error_message = re.escape( + "Error at path '" + uint256_part + "': expected value in range [0;2**128)" + ) + with pytest.raises(InvalidValueException, match=error_message): + u256_serializer.deserialize(value) + + +def test_deserialize_invalid_128_values(): + # We need to escape braces + error_message = re.escape( + "Error at path 'uint128': expected value in range [0;2**128)" + ) + with pytest.raises(InvalidValueException, match=error_message): + u128_serializer.deserialize([MAX_U128 + 1]) + with pytest.raises(InvalidValueException, match=error_message): + u128_serializer.deserialize([-1]) + + +def test_serialize_invalid_256_int_value(): + error_message = re.escape("Error: Uint256 is expected to be in range [0;2**256)") + with pytest.raises(InvalidValueException, match=error_message): + u256_serializer.serialize(2**256) + with pytest.raises(InvalidValueException, match=error_message): + u256_serializer.serialize(-1) + + +def test_serialize_invalid_128_int_value(): + error_message = re.escape("Error: expected value in range [0;2**128)") + with pytest.raises(InvalidValueException, match=error_message): + u128_serializer.serialize(2**128) + with pytest.raises(InvalidValueException, match=error_message): + u128_serializer.serialize(-1) + + +def test_serialize_invalid_dict_values(): + low_error_message = re.escape( + "Error at path 'low': expected value in range [0;2**128)" + ) + with pytest.raises(InvalidValueException, match=low_error_message): + u256_serializer.serialize({"low": -1, "high": 12324}) + with pytest.raises(InvalidValueException, match=low_error_message): + u256_serializer.serialize({"low": MAX_U128 + 1, "high": 4543535}) + + high_error_message = re.escape( + "Error at path 'high': expected value in range [0;2**128)" + ) + with pytest.raises(InvalidValueException, match=high_error_message): + u256_serializer.serialize({"low": 652432, "high": -1}) + with pytest.raises(InvalidValueException, match=high_error_message): + u256_serializer.serialize({"low": 0, "high": MAX_U128 + 1}) + + +@pytest.mark.parametrize("serializer", (u128_serializer, u256_serializer)) +def test_invalid_type(serializer): + error_message = re.escape( + "Error: expected int or dict, received 'wololoo' of type ''." + ) + with pytest.raises(InvalidTypeException, match=error_message): + serializer.serialize("wololoo") # type: ignore diff --git a/starknet_py/serialization/data_serializers/unit_serializer.py b/starknet_py/serialization/data_serializers/unit_serializer.py new file mode 100644 index 000000000..8c8124022 --- /dev/null +++ b/starknet_py/serialization/data_serializers/unit_serializer.py @@ -0,0 +1,30 @@ +from dataclasses import dataclass +from typing import Any, Generator, Optional + +from starknet_py.serialization import CairoDataSerializer +from starknet_py.serialization._context import ( + DeserializationContext, + SerializationContext, +) + + +@dataclass +class UnitSerializer(CairoDataSerializer[None, None]): + """ + Serializer for unit type. + Can only serialize None. + Deserializes data to None. + + Example: + [] => None + """ + + def deserialize_with_context(self, context: DeserializationContext) -> None: + return None + + def serialize_with_context( + self, context: SerializationContext, value: Optional[Any] + ) -> Generator[None, None, None]: + if value is not None: + raise ValueError("Can only serialize `None`.") + yield None diff --git a/starknet_py/serialization/data_serializers/unit_serializer_test.py b/starknet_py/serialization/data_serializers/unit_serializer_test.py new file mode 100644 index 000000000..aa17a954b --- /dev/null +++ b/starknet_py/serialization/data_serializers/unit_serializer_test.py @@ -0,0 +1,23 @@ +import pytest + +from starknet_py.serialization.data_serializers.unit_serializer import UnitSerializer + +serializer = UnitSerializer() + + +def test_deserialize_unit(): + deserialized = serializer.deserialize([]) + + assert deserialized is None + + +def test_serialize_unit(): + # pylint: disable=use-implicit-booleaness-not-comparison + serialized = serializer.serialize(None) + + assert serialized == [] + + +def test_throws_on_not_none(): + with pytest.raises(ValueError, match="Can only serialize `None`."): + serializer.serialize("abc") # type: ignore diff --git a/starknet_py/serialization/factory.py b/starknet_py/serialization/factory.py index d3f593ed2..2afaa3de0 100644 --- a/starknet_py/serialization/factory.py +++ b/starknet_py/serialization/factory.py @@ -1,25 +1,39 @@ from __future__ import annotations from collections import OrderedDict -from typing import Dict +from typing import Dict, List from starknet_py.abi.model import Abi +from starknet_py.abi.v1.model import Abi as AbiV1 from starknet_py.cairo.data_types import ( ArrayType, + BoolType, CairoType, + EnumType, FeltType, NamedTupleType, + OptionType, StructType, TupleType, + UintType, + UnitType, ) +from starknet_py.serialization.data_serializers import BoolSerializer from starknet_py.serialization.data_serializers.array_serializer import ArraySerializer from starknet_py.serialization.data_serializers.cairo_data_serializer import ( CairoDataSerializer, ) +from starknet_py.serialization.data_serializers.enum_serializer import EnumSerializer from starknet_py.serialization.data_serializers.felt_serializer import FeltSerializer from starknet_py.serialization.data_serializers.named_tuple_serializer import ( NamedTupleSerializer, ) +from starknet_py.serialization.data_serializers.option_serializer import ( + OptionSerializer, +) +from starknet_py.serialization.data_serializers.output_serializer import ( + OutputSerializer, +) from starknet_py.serialization.data_serializers.payload_serializer import ( PayloadSerializer, ) @@ -30,9 +44,12 @@ from starknet_py.serialization.data_serializers.uint256_serializer import ( Uint256Serializer, ) +from starknet_py.serialization.data_serializers.uint_serializer import UintSerializer +from starknet_py.serialization.data_serializers.unit_serializer import UnitSerializer from starknet_py.serialization.errors import InvalidTypeException from starknet_py.serialization.function_serialization_adapter import ( FunctionSerializationAdapter, + FunctionSerializationAdapterV1, ) _uint256_type = StructType("Uint256", OrderedDict(low=FeltType(), high=FeltType())) @@ -45,9 +62,13 @@ def serializer_for_type(cairo_type: CairoType) -> CairoDataSerializer: :param cairo_type: CairoType. :return: CairoDataSerializer. """ + # pylint: disable=too-many-return-statements if isinstance(cairo_type, FeltType): return FeltSerializer() + if isinstance(cairo_type, BoolType): + return BoolSerializer() + if isinstance(cairo_type, StructType): # Special case: Uint256 is represented as struct if cairo_type == _uint256_type: @@ -76,6 +97,23 @@ def serializer_for_type(cairo_type: CairoType) -> CairoDataSerializer: ) ) + if isinstance(cairo_type, UintType): + return UintSerializer(bits=cairo_type.bits) + + if isinstance(cairo_type, OptionType): + return OptionSerializer(serializer_for_type(cairo_type.type)) + + if isinstance(cairo_type, UnitType): + return UnitSerializer() + + if isinstance(cairo_type, EnumType): + return EnumSerializer( + OrderedDict( + (name, serializer_for_type(variant_type)) + for name, variant_type in cairo_type.variants.items() + ) + ) + raise InvalidTypeException(f"Received unknown Cairo type '{cairo_type}'.") @@ -96,6 +134,19 @@ def serializer_for_payload(payload: Dict[str, CairoType]) -> PayloadSerializer: ) +def serializer_for_outputs(payload: List[CairoType]) -> OutputSerializer: + """ + Create OutputSerializer for types in list. Please note that the order of fields in the list is + very important. Make sure the types are provided in the right order. + + :param payload: list with cairo types. + :return: OutputSerializer that can be used to deserialize function outputs. + """ + return OutputSerializer( + serializers=[serializer_for_type(cairo_type) for cairo_type in payload] + ) + + def serializer_for_event(event: Abi.Event) -> PayloadSerializer: """ Create serializer for an event. @@ -117,3 +168,18 @@ def serializer_for_function(abi_function: Abi.Function) -> FunctionSerialization inputs_serializer=serializer_for_payload(abi_function.inputs), outputs_deserializer=serializer_for_payload(abi_function.outputs), ) + + +def serializer_for_function_v1( + abi_function: AbiV1.Function, +) -> FunctionSerializationAdapter: + """ + Create FunctionSerializationAdapter for serializing function inputs and deserializing function outputs. + + :param abi_function: parsed function's abi. + :return: FunctionSerializationAdapter. + """ + return FunctionSerializationAdapterV1( + inputs_serializer=serializer_for_payload(abi_function.inputs), + outputs_deserializer=serializer_for_outputs(abi_function.outputs), + ) diff --git a/starknet_py/serialization/function_serialization_adapter.py b/starknet_py/serialization/function_serialization_adapter.py index 0853e7807..b3ba0a263 100644 --- a/starknet_py/serialization/function_serialization_adapter.py +++ b/starknet_py/serialization/function_serialization_adapter.py @@ -4,6 +4,9 @@ from typing import Dict, List, Set, Tuple from starknet_py.cairo.felt import CairoData +from starknet_py.serialization.data_serializers.output_serializer import ( + OutputSerializer, +) from starknet_py.serialization.data_serializers.payload_serializer import ( PayloadSerializer, ) @@ -90,3 +93,16 @@ def _ensure_no_missing_args(expected_args: Set[str], provided_args: Set[str]): raise InvalidTypeException( f"Missing arguments: '{', '.join(missing_arguments)}'." ) + + +@dataclass +class FunctionSerializationAdapterV1(FunctionSerializationAdapter): + outputs_deserializer: OutputSerializer + + def deserialize(self, data: List[int]) -> Tuple: + """ + Deserializes data into TupleDataclass containing python representations. + + :return: cairo data. + """ + return self.outputs_deserializer.deserialize(data) diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index fecb49464..35b8d9f32 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -557,12 +557,8 @@ async def test_sign_deploy_account_tx_for_fee_estimation( @pytest.mark.asyncio -async def test_sign_transaction_custom_nonce( - account, hello_starknet_class_hash_tx_hash -): - deployment = Deployer().create_contract_deployment( - hello_starknet_class_hash_tx_hash[0] - ) +async def test_sign_transaction_custom_nonce(account, cairo1_hello_starknet_class_hash): + deployment = Deployer().create_contract_deployment(cairo1_hello_starknet_class_hash) deploy_tx = await account.sign_invoke_transaction(deployment.call, max_fee=MAX_FEE) new_balance = 30 diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index fd512af91..8f7f672d9 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -600,11 +600,11 @@ async def test_state_update_deployed_contracts( @pytest.mark.asyncio async def test_get_class_by_hash_sierra_program( - client, hello_starknet_class_hash_tx_hash: Tuple[int, int] + client, cairo1_hello_starknet_class_hash: int ): - (class_hash, _) = hello_starknet_class_hash_tx_hash - - contract_class = await client.get_class_by_hash(class_hash=class_hash) + contract_class = await client.get_class_by_hash( + class_hash=cairo1_hello_starknet_class_hash + ) assert isinstance(contract_class, SierraContractClass) assert contract_class.contract_class_version == "0.1.0" @@ -616,10 +616,10 @@ async def test_get_class_by_hash_sierra_program( @pytest.mark.asyncio async def test_get_declare_v2_transaction( client, - hello_starknet_class_hash_tx_hash: Tuple[int, int], + cairo1_hello_starknet_class_hash_tx_hash: Tuple[int, int], declare_v2_hello_starknet: DeclareV2, ): - (class_hash, tx_hash) = hello_starknet_class_hash_tx_hash + (class_hash, tx_hash) = cairo1_hello_starknet_class_hash_tx_hash transaction = await client.get_transaction(tx_hash=tx_hash) @@ -639,11 +639,11 @@ async def test_get_declare_v2_transaction( @pytest.mark.asyncio async def test_get_block_with_declare_v2( client, - hello_starknet_class_hash_tx_hash: Tuple[int, int], + cairo1_hello_starknet_class_hash_tx_hash: Tuple[int, int], declare_v2_hello_starknet: DeclareV2, block_with_declare_v2_number: int, ): - (class_hash, tx_hash) = hello_starknet_class_hash_tx_hash + (class_hash, tx_hash) = cairo1_hello_starknet_class_hash_tx_hash block = await client.get_block(block_number=block_with_declare_v2_number) @@ -665,20 +665,18 @@ async def test_get_block_with_declare_v2( @pytest.mark.asyncio async def test_get_new_state_update( client, - hello_starknet_class_hash_tx_hash: Tuple[int, int], + cairo1_hello_starknet_class_hash: int, declare_v2_hello_starknet: DeclareV2, block_with_declare_v2_number: int, replaced_class: Tuple[int, int, int], ): - (class_hash, _) = hello_starknet_class_hash_tx_hash - state_update = await client.get_state_update( block_number=block_with_declare_v2_number ) assert state_update.state_diff.replaced_classes == [] assert ( DeclaredContractHash( - class_hash=class_hash, + class_hash=cairo1_hello_starknet_class_hash, compiled_class_hash=declare_v2_hello_starknet.compiled_class_hash, ) in state_update.state_diff.declared_contract_hashes diff --git a/starknet_py/tests/e2e/client/fixtures/transactions.py b/starknet_py/tests/e2e/client/fixtures/transactions.py index bd0ddeb60..fbd6e9db5 100644 --- a/starknet_py/tests/e2e/client/fixtures/transactions.py +++ b/starknet_py/tests/e2e/client/fixtures/transactions.py @@ -4,17 +4,14 @@ import pytest import pytest_asyncio -from starknet_py.common import create_casm_class from starknet_py.contract import Contract -from starknet_py.hash.casm_class_hash import compute_casm_class_hash from starknet_py.net.account.account import Account -from starknet_py.net.client import Client -from starknet_py.net.models.transaction import DeclareV2, DeployAccount +from starknet_py.net.models.transaction import DeployAccount from starknet_py.net.udc_deployer.deployer import Deployer from starknet_py.tests.e2e.client.fixtures.prepare_net_for_gateway_test import ( PreparedNetworkData, ) -from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR, MAX_FEE +from starknet_py.tests.e2e.fixtures.constants import MAX_FEE from starknet_py.tests.e2e.fixtures.misc import read_contract from starknet_py.tests.e2e.utils import ( get_deploy_account_details, @@ -63,67 +60,32 @@ def block_with_deploy_account_number( return prepared_data.block_with_deploy_account_number -@pytest_asyncio.fixture(scope="package") -async def declare_v2_hello_starknet(gateway_account: Account) -> DeclareV2: - """ - Returns DeclareV2 transaction. - """ - hello_starknet_compiled = read_contract( - "hello_starknet_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR - ) - compiled_class_hash = compute_casm_class_hash( - create_casm_class( - read_contract( - "hello_starknet_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR - ) - ) - ) - return await gateway_account.sign_declare_v2_transaction( - hello_starknet_compiled, - compiled_class_hash=compiled_class_hash, - max_fee=MAX_FEE, - ) - - -@pytest_asyncio.fixture(scope="package") -async def hello_starknet_class_hash_tx_hash( - gateway_client: Client, declare_v2_hello_starknet: DeclareV2 -) -> Tuple[int, int]: - """ - Returns class_hash and transaction_hash from the declare_v2_hello_starknet transaction. - """ - result = await gateway_client.declare(declare_v2_hello_starknet) - await gateway_client.wait_for_tx( - tx_hash=result.transaction_hash, wait_for_accept=True - ) - - return result.class_hash, result.transaction_hash - - @pytest_asyncio.fixture(scope="package") async def hello_starknet_deploy_transaction_address( - gateway_account: Account, hello_starknet_class_hash_tx_hash: Tuple[int, int] + account: Account, cairo1_hello_starknet_class_hash ) -> int: - class_hash, _ = hello_starknet_class_hash_tx_hash deployer = Deployer() - contract_deployment = deployer.create_contract_deployment_raw(class_hash=class_hash) - deploy_invoke_transaction = await gateway_account.sign_invoke_transaction( + contract_deployment = deployer.create_contract_deployment_raw( + class_hash=cairo1_hello_starknet_class_hash + ) + deploy_invoke_transaction = await account.sign_invoke_transaction( calls=contract_deployment.call, max_fee=MAX_FEE ) - resp = await gateway_account.client.send_transaction(deploy_invoke_transaction) - await gateway_account.client.wait_for_tx(resp.transaction_hash) + resp = await account.client.send_transaction(deploy_invoke_transaction) + await account.client.wait_for_tx(resp.transaction_hash) return contract_deployment.address @pytest_asyncio.fixture(scope="package") async def block_with_declare_v2_number( - hello_starknet_class_hash_tx_hash: Tuple[int, int], full_node_client + cairo1_hello_starknet_tx_hash: int, client ) -> int: """ Returns number of the block with DeclareV2 transaction """ - (_, tx_hash) = hello_starknet_class_hash_tx_hash - declare_v2_receipt = await full_node_client.get_transaction_receipt(tx_hash) + declare_v2_receipt = await client.get_transaction_receipt( + cairo1_hello_starknet_tx_hash + ) return declare_v2_receipt.block_number diff --git a/starknet_py/tests/e2e/client/gateway_test.py b/starknet_py/tests/e2e/client/gateway_test.py index 2171e4c14..aceb1b518 100644 --- a/starknet_py/tests/e2e/client/gateway_test.py +++ b/starknet_py/tests/e2e/client/gateway_test.py @@ -1,4 +1,3 @@ -from typing import Tuple from unittest.mock import AsyncMock, patch import pytest @@ -41,12 +40,10 @@ async def test_get_class_hash_at(contract_address, gateway_client, class_hash): @pytest.mark.asyncio async def test_get_compiled_class_by_class_hash( - gateway_client, hello_starknet_class_hash_tx_hash: Tuple[int, int] + gateway_client, cairo1_hello_starknet_class_hash ): - (class_hash, _) = hello_starknet_class_hash_tx_hash - compiled_class = await gateway_client.get_compiled_class_by_class_hash( - class_hash=class_hash + class_hash=cairo1_hello_starknet_class_hash ) assert isinstance(compiled_class, CasmClass) diff --git a/starknet_py/tests/e2e/contract_interaction/declare_test.py b/starknet_py/tests/e2e/contract_interaction/declare_test.py new file mode 100644 index 000000000..72242dffe --- /dev/null +++ b/starknet_py/tests/e2e/contract_interaction/declare_test.py @@ -0,0 +1,44 @@ +import pytest + +from starknet_py.contract import Contract +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR, MAX_FEE +from starknet_py.tests.e2e.fixtures.misc import read_contract + + +@pytest.mark.asyncio +async def test_contract_declare(account): + compiled_contract = read_contract( + "test_contract_declare_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + compiled_contract_casm = read_contract( + "test_contract_declare_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ) + + declare_result = await Contract.declare( + account, + compiled_contract=compiled_contract, + compiled_contract_casm=compiled_contract_casm, + max_fee=MAX_FEE, + ) + await declare_result.wait_for_acceptance() + + assert isinstance(declare_result.hash, int) + assert isinstance(declare_result.class_hash, int) + assert declare_result.compiled_contract == compiled_contract + + +@pytest.mark.asyncio +async def test_throws_when_cairo1_without_compiled_contract_casm_and_casm_class_hash( + account, +): + compiled_contract = read_contract( + "erc20_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + + with pytest.raises( + ValueError, + match="Cairo 1.0 contract was provided without casm_class_hash or compiled_contract_casm argument.", + ): + await Contract.declare( + account, compiled_contract=compiled_contract, max_fee=MAX_FEE + ) diff --git a/starknet_py/tests/e2e/contract_interaction/deploy_test.py b/starknet_py/tests/e2e/contract_interaction/deploy_test.py new file mode 100644 index 000000000..63b3d90f7 --- /dev/null +++ b/starknet_py/tests/e2e/contract_interaction/deploy_test.py @@ -0,0 +1,88 @@ +import dataclasses +import json +import re + +import pytest + +from starknet_py.common import create_sierra_compiled_contract +from starknet_py.contract import Contract, DeclareResult +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR, MAX_FEE +from starknet_py.tests.e2e.fixtures.misc import read_contract + + +@pytest.mark.asyncio +async def test_declare_deploy( + account, + cairo1_minimal_contract_class_hash: int, +): + compiled_contract = read_contract( + "minimal_contract_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + + declare_result = DeclareResult( + _account=account, + _client=account.client, + _cairo_version=1, + class_hash=cairo1_minimal_contract_class_hash, + compiled_contract=compiled_contract, + hash=0, + ) + + deploy_result = await declare_result.deploy(max_fee=MAX_FEE) + await deploy_result.wait_for_acceptance() + + assert isinstance(deploy_result.hash, int) + assert deploy_result.hash != 0 + assert deploy_result.deployed_contract.address != 0 + + +@pytest.mark.asyncio +async def test_throws_on_wrong_abi(account, cairo1_minimal_contract_class_hash: int): + compiled_contract = read_contract( + "minimal_contract_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + + declare_result = DeclareResult( + _account=account, + _client=account.client, + _cairo_version=1, + class_hash=cairo1_minimal_contract_class_hash, + compiled_contract=compiled_contract, + hash=0, + ) + + compiled_contract = compiled_contract.replace('"abi": [', '"abi": ') + declare_result = dataclasses.replace( + declare_result, compiled_contract=compiled_contract + ) + + with pytest.raises( + ValueError, + match=re.escape( + "Contract's ABI can't be converted to format List[Dict]. " + "Make sure provided compiled_contract is correct." + ), + ): + await declare_result.deploy(max_fee=MAX_FEE) + + +@pytest.mark.asyncio +async def test_deploy_contract_flow(account, cairo1_hello_starknet_class_hash: int): + compiled_contract = read_contract( + "hello_starknet_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + abi = create_sierra_compiled_contract(compiled_contract=compiled_contract).abi + + deploy_result = await Contract.deploy_contract( + class_hash=cairo1_hello_starknet_class_hash, + account=account, + abi=json.loads(abi), + max_fee=MAX_FEE, + cairo_version=1, + ) + await deploy_result.wait_for_acceptance() + + contract = deploy_result.deployed_contract + + assert isinstance(contract.address, int) + assert len(contract.functions) != 0 diff --git a/starknet_py/tests/e2e/contract_interaction/v1_interaction_test.py b/starknet_py/tests/e2e/contract_interaction/v1_interaction_test.py new file mode 100644 index 000000000..c5df3bb17 --- /dev/null +++ b/starknet_py/tests/e2e/contract_interaction/v1_interaction_test.py @@ -0,0 +1,160 @@ +import pytest + +from starknet_py.cairo.felt import decode_shortstring, encode_shortstring +from starknet_py.contract import Contract +from starknet_py.tests.e2e.fixtures.constants import MAX_FEE +from starknet_py.tests.e2e.fixtures.contracts import deploy_v1_contract + + +@pytest.mark.asyncio +async def test_general_v1_interaction(account, cairo1_erc20_class_hash: int): + calldata = { + "name_": encode_shortstring("erc20_basic"), + "symbol_": encode_shortstring("ERC20B"), + "decimals_": 10, + "initial_supply": 12345, + "recipient": account.address, + } + erc20 = await deploy_v1_contract( + account=account, + contract_file_name="erc20", + class_hash=cairo1_erc20_class_hash, + calldata=calldata, + ) + + (name,) = await erc20.functions["get_name"].call() + decoded_name = decode_shortstring(name).lstrip("\x00") + (decimals,) = await erc20.functions["get_decimals"].call() + (supply,) = await erc20.functions["get_total_supply"].call() + (account_balance,) = await erc20.functions["balance_of"].call( + account=account.address + ) + + transfer_amount = 10 + await ( + await erc20.functions["transfer"].invoke( + recipient=0x11, amount=transfer_amount, max_fee=MAX_FEE + ) + ).wait_for_acceptance() + + (after_transfer_balance,) = await erc20.functions["balance_of"].call( + account=account.address + ) + + assert decoded_name == "erc20_basic" + assert decimals == calldata["decimals_"] + assert supply == calldata["initial_supply"] + assert account_balance == calldata["initial_supply"] + assert after_transfer_balance == calldata["initial_supply"] - transfer_amount + + +@pytest.mark.asyncio +async def test_serializing_struct(account, cairo1_token_bridge_class_hash: int): + bridge = await deploy_v1_contract( + account=account, + contract_file_name="token_bridge", + class_hash=cairo1_token_bridge_class_hash, + calldata={"governor_address": account.address}, + ) + + await ( + await bridge.functions["set_l1_bridge"].invoke( + l1_bridge_address={"address": 0x11}, max_fee=MAX_FEE + ) + ).wait_for_acceptance() + + +@pytest.mark.asyncio +async def test_serializing_option(account, cairo1_test_option_class_hash: int): + test_option = await deploy_v1_contract( + account=account, + contract_file_name="test_option", + class_hash=cairo1_test_option_class_hash, + ) + + (received_option,) = await test_option.functions["get_option_struct"].call() + + assert dict(received_option) == { + "first_field": 1, + "second_field": 2, + "third_field": None, + "fourth_field": 4, + } + + option_struct = { + "first_field": 1, + "second_field": 2**128 + 1, + "third_field": None, + "fourth_field": 4, + } + + (received_option,) = await test_option.functions[ + "receive_and_send_option_struct" + ].call(option_struct=option_struct) + + assert dict(received_option) == option_struct + + (received_option,) = await test_option.functions["get_empty_option"].call() + + assert received_option is None + + +@pytest.mark.asyncio +async def test_serializing_enum(account, cairo1_test_enum_class_hash: int): + test_enum = await deploy_v1_contract( + account=account, + contract_file_name="test_enum", + class_hash=cairo1_test_enum_class_hash, + ) + + (received_enum,) = await test_enum.functions["get_enum"].call() + + assert received_enum.variant == "a" + assert received_enum.value == 100 + + (received_enum,) = await test_enum.functions["get_enum_without_value"].call() + + assert received_enum.variant == "c" + assert received_enum.value is None + + variant_name = "b" + value = 200 + (received_enum,) = await test_enum.functions["receive_and_send_enum"].call( + my_enum={variant_name: value} + ) + + assert received_enum.variant == variant_name + assert received_enum.value == value + + variant_name = "c" + value = None + (received_enum,) = await test_enum.functions["receive_and_send_enum"].call( + my_enum={variant_name: value} + ) + + assert received_enum.variant == variant_name + assert received_enum.value == value + + +@pytest.mark.asyncio +async def test_from_address_on_v1_contract(account, cairo1_erc20_class_hash: int): + calldata = { + "name_": encode_shortstring("erc20_basic"), + "symbol_": encode_shortstring("ERC20B"), + "decimals_": 10, + "initial_supply": 12345, + "recipient": account.address, + } + erc20 = await deploy_v1_contract( + account=account, + contract_file_name="erc20", + class_hash=cairo1_erc20_class_hash, + calldata=calldata, + ) + + erc20_from_address = await Contract.from_address(erc20.address, provider=account) + + assert erc20_from_address.address == erc20.address + assert erc20_from_address.account == erc20.account + assert erc20_from_address.functions.keys() == erc20.functions.keys() + assert erc20_from_address.data == erc20.data diff --git a/starknet_py/tests/e2e/declare/declare_test.py b/starknet_py/tests/e2e/declare/declare_test.py index 87250ded1..bea65eae7 100644 --- a/starknet_py/tests/e2e/declare/declare_test.py +++ b/starknet_py/tests/e2e/declare/declare_test.py @@ -16,42 +16,6 @@ async def test_declare_tx(account, map_compiled_contract): @pytest.mark.asyncio -async def test_declare_v2_tx_full_node_client( - full_node_account, sierra_minimal_compiled_contract_and_class_hash -): - ( - compiled_contract, - compiled_class_hash, - ) = sierra_minimal_compiled_contract_and_class_hash - - declare_tx = await full_node_account.sign_declare_v2_transaction( - compiled_contract=compiled_contract, - compiled_class_hash=compiled_class_hash, - max_fee=MAX_FEE, - ) - assert declare_tx.version == 2 - declare_result = await full_node_account.client.declare(declare_tx) - await full_node_account.client.wait_for_tx( - tx_hash=declare_result.transaction_hash, wait_for_accept=True - ) - - -@pytest.mark.asyncio -async def test_declare_v2_tx_gateway_client( - gateway_account, another_sierra_minimal_compiled_contract_and_class_hash -): - ( - compiled_contract, - compiled_class_hash, - ) = another_sierra_minimal_compiled_contract_and_class_hash - - declare_tx = await gateway_account.sign_declare_v2_transaction( - compiled_contract=compiled_contract, - compiled_class_hash=compiled_class_hash, - max_fee=MAX_FEE, - ) - assert declare_tx.version == 2 - declare_result = await gateway_account.client.declare(declare_tx) - await gateway_account.client.wait_for_tx( - tx_hash=declare_result.transaction_hash, wait_for_accept=True - ) +async def test_declare_v2_tx(cairo1_minimal_contract_class_hash: int): + assert isinstance(cairo1_minimal_contract_class_hash, int) + assert cairo1_minimal_contract_class_hash != 0 diff --git a/starknet_py/tests/e2e/docs/guide/test_cairo1_contract.py b/starknet_py/tests/e2e/docs/guide/test_cairo1_contract.py index 863cb6509..168e0ba22 100644 --- a/starknet_py/tests/e2e/docs/guide/test_cairo1_contract.py +++ b/starknet_py/tests/e2e/docs/guide/test_cairo1_contract.py @@ -1,3 +1,5 @@ +import json + import pytest from starknet_py.net.client_models import CasmClass @@ -64,25 +66,26 @@ async def test_cairo1_contract( assert sierra_class_hash != 0 # START OF DEPLOY SECTION - raw_calldata = [] + calldata = [] salt = _get_random_salt() + abi = json.loads(compiled_contract)["abi"] # docs-deploy: start from starknet_py.net.udc_deployer.deployer import Deployer # Use Universal Deployer Contract (UDC) to deploy the Cairo1 contract deployer = Deployer() - # Create a ContractDeployment, optionally passing salt and raw_calldata - contract_deployment = deployer.create_contract_deployment_raw( - class_hash=sierra_class_hash, raw_calldata=raw_calldata, salt=salt + # Create a ContractDeployment, optionally passing salt and calldata + contract_deployment = deployer.create_contract_deployment( + class_hash=sierra_class_hash, + abi=abi, + cairo_version=1, + calldata=calldata, + salt=salt, ) - # Using the call, create an Invoke transaction to the UDC - deploy_invoke_transaction = await account.sign_invoke_transaction( - calls=contract_deployment.call, max_fee=MAX_FEE - ) - resp = await account.client.send_transaction(deploy_invoke_transaction) - await account.client.wait_for_tx(resp.transaction_hash) + res = await account.execute(calls=contract_deployment.call, max_fee=MAX_FEE) + await account.client.wait_for_tx(res.transaction_hash) # The contract has been deployed and can be found at contract_deployment.address # docs-deploy: end diff --git a/starknet_py/tests/e2e/docs/guide/test_simple_declare_and_deploy_cairo1.py b/starknet_py/tests/e2e/docs/guide/test_simple_declare_and_deploy_cairo1.py new file mode 100644 index 000000000..ce9b110d9 --- /dev/null +++ b/starknet_py/tests/e2e/docs/guide/test_simple_declare_and_deploy_cairo1.py @@ -0,0 +1,43 @@ +import pytest + +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR +from starknet_py.tests.e2e.fixtures.misc import read_contract + + +@pytest.mark.asyncio +async def test_simple_declare_and_deploy(account): + # pylint: disable=import-outside-toplevel + # docs: start + from starknet_py.contract import Contract + + # docs: end + compiled_contract = read_contract( + "account_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + compiled_contract_casm = read_contract( + "account_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ) + constructor_args = {"public_key_": 0x123} + + # docs: start + declare_result = await Contract.declare( + account=account, + compiled_contract=compiled_contract, + compiled_contract_casm=compiled_contract_casm, + max_fee=int(1e16), + ) + await declare_result.wait_for_acceptance() + + deploy_result = await declare_result.deploy( + constructor_args=constructor_args, max_fee=int(1e16) + ) + await deploy_result.wait_for_acceptance() + + contract = deploy_result.deployed_contract + # docs: end + + assert isinstance(declare_result.hash, int) + assert isinstance(declare_result.class_hash, int) + assert declare_result.compiled_contract == compiled_contract + assert contract.address != 0 + assert len(contract.functions) > 0 diff --git a/starknet_py/tests/e2e/docs/guide/test_simple_deploy.py b/starknet_py/tests/e2e/docs/guide/test_simple_deploy.py index 333e973b2..3348211b3 100644 --- a/starknet_py/tests/e2e/docs/guide/test_simple_deploy.py +++ b/starknet_py/tests/e2e/docs/guide/test_simple_deploy.py @@ -4,9 +4,7 @@ @pytest.mark.asyncio -async def test_simple_declare_and_deploy( - account, map_class_hash, map_compiled_contract -): +async def test_simple_deploy(account, map_class_hash, map_compiled_contract): # pylint: disable=import-outside-toplevel # docs: start from starknet_py.contract import Contract diff --git a/starknet_py/tests/e2e/docs/guide/test_simple_deploy_cairo1.py b/starknet_py/tests/e2e/docs/guide/test_simple_deploy_cairo1.py new file mode 100644 index 000000000..3d71710c1 --- /dev/null +++ b/starknet_py/tests/e2e/docs/guide/test_simple_deploy_cairo1.py @@ -0,0 +1,48 @@ +import json + +import pytest + +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR +from starknet_py.tests.e2e.fixtures.misc import read_contract + + +@pytest.mark.asyncio +async def test_simple_deploy_cairo1(account, cairo1_erc20_class_hash): + # pylint: disable=import-outside-toplevel + # docs: start + from starknet_py.cairo.felt import encode_shortstring + from starknet_py.common import create_sierra_compiled_contract + from starknet_py.contract import Contract + + # docs: end + + compiled_contract = read_contract( + "erc20_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + class_hash = cairo1_erc20_class_hash + + # docs: start + abi = create_sierra_compiled_contract(compiled_contract=compiled_contract).abi + + constructor_args = { + "name_": encode_shortstring("erc20_basic"), + "symbol_": encode_shortstring("ERC20B"), + "decimals_": 10, + "initial_supply": 12345, + "recipient": account.address, + } + + deploy_result = await Contract.deploy_contract( + account=account, + class_hash=class_hash, + abi=json.loads(abi), + constructor_args=constructor_args, + max_fee=int(1e16), + cairo_version=1, # note the `cairo_version` parameter + ) + + await deploy_result.wait_for_acceptance() + contract = deploy_result.deployed_contract + # docs: end + + assert contract.address != 0 diff --git a/starknet_py/tests/e2e/fixtures/abi_v1_structures.py b/starknet_py/tests/e2e/fixtures/abi_v1_structures.py new file mode 100644 index 000000000..46bb8a66f --- /dev/null +++ b/starknet_py/tests/e2e/fixtures/abi_v1_structures.py @@ -0,0 +1,172 @@ +# data from cairo repository: crates/cairo-lang-starknet/src/abi_test.rs +from collections import OrderedDict + +from starknet_py.abi.v1.model import Abi +from starknet_py.cairo.data_types import ( + ArrayType, + EnumType, + FeltType, + StructType, + UintType, +) + +core_structures = { + "core::starknet::eth_address::EthAddress": StructType( + name="core::starknet::eth_address::EthAddress", + types=OrderedDict([("address", FeltType())]), + ) +} + + +pool_id_dict = { + "type": "struct", + "name": "PoolId", + "members": [ + {"name": "value", "type": "core::integer::u256"}, + ], +} +pool_id_struct = StructType("PoolId", OrderedDict(value=UintType(256))) + +user_dict = { + "type": "struct", + "name": "User", + "members": [ + {"name": "id", "type": "core::integer::u256"}, + {"name": "name_len", "type": "core::felt252"}, + {"name": "name", "type": "core::array::Array::"}, + {"name": "pool_id", "type": "PoolId"}, + ], +} +user_struct = StructType( + "User", + OrderedDict( + id=UintType(256), + name_len=FeltType(), + name=ArrayType(FeltType()), + pool_id=pool_id_struct, + ), +) + +user_added_dict = { + "type": "event", + "name": "UserAdded", + "inputs": [ + {"name": "user", "type": "User"}, + ], +} +user_added_event: Abi.Event = Abi.Event( + "UserAdded", + OrderedDict(user=user_struct), +) + +pool_id_added_dict = { + "type": "event", + "name": "PoolIdAdded", + "inputs": [ + {"name": "pool_id", "type": "PoolId"}, + ], +} +pool_id_added_event: Abi.Event = Abi.Event( + "PoolIdAdded", + OrderedDict(pool_id=pool_id_struct), +) + +get_user_dict = { + "type": "function", + "name": "get_user", + "inputs": [ + { + "name": "id", + "type": "core::integer::u256", + } + ], + "outputs": [{"type": "User"}], +} +get_user_fn = Abi.Function("get_user", OrderedDict(id=UintType(256)), [user_struct]) + +delete_pool_dict = { + "type": "function", + "name": "delete_pool", + "inputs": [ + { + "name": "id", + "type": "PoolId", + }, + {"name": "user_id", "type": "core::integer::u256"}, + ], + "outputs": [], +} +delete_pool_fn = Abi.Function( + "delete_pool", OrderedDict(id=pool_id_struct, user_id=UintType(256)), [] +) + + +my_struct_dict = { + "type": "struct", + "name": "test::MyStruct::", + "members": [ + {"name": "a", "type": "core::integer::u256"}, + {"name": "b", "type": "core::felt252"}, + ], +} +my_struct = StructType( + name="test::MyStruct::", + types=OrderedDict(a=UintType(256), b=FeltType()), +) + +my_enum_dict = { + "type": "enum", + "name": "test::MyEnum::", + "variants": [ + {"name": "a", "type": "core::integer::u256"}, + {"name": "b", "type": "test::MyStruct::"}, + ], +} +my_enum = EnumType( + name="test::MyEnum::", + variants=OrderedDict(a=UintType(256), b=my_struct), +) + +foo_event_dict = { + "type": "event", + "name": "foo_event", + "inputs": [ + {"name": "a", "type": "core::felt252"}, + {"name": "b", "type": "core::integer::u128"}, + ], +} +foo_event = Abi.Event( + name="foo_event", inputs=OrderedDict(a=FeltType(), b=UintType(128)) +) + +foo_external_dict = { + "type": "function", + "name": "foo_external", + "inputs": [ + {"name": "a", "type": "core::felt252"}, + {"name": "b", "type": "core::integer::u128"}, + ], + "outputs": [{"type": "test::MyStruct::"}], + "state_mutability": "external", +} +foo_external = Abi.Function( + name="foo_external", + inputs=OrderedDict(a=FeltType(), b=UintType(128)), + outputs=[my_struct], +) + +foo_view_dict = { + "type": "function", + "name": "foo_view", + "inputs": [ + {"name": "a", "type": "core::felt252"}, + {"name": "b", "type": "core::integer::u128"}, + ], + "outputs": [{"type": "test::MyEnum::"}], + "state_mutability": "view", +} +foo_view = Abi.Function( + name="foo_view", + inputs=OrderedDict(a=FeltType(), b=UintType(128)), + outputs=[my_enum], +) diff --git a/starknet_py/tests/e2e/fixtures/accounts.py b/starknet_py/tests/e2e/fixtures/accounts.py index 5936580f6..7b56c6307 100644 --- a/starknet_py/tests/e2e/fixtures/accounts.py +++ b/starknet_py/tests/e2e/fixtures/accounts.py @@ -143,6 +143,11 @@ def full_node_account( def net_to_base_accounts() -> List[str]: + if "--client=gateway" in sys.argv: + return ["gateway_account"] + if "--client=full_node" in sys.argv: + return ["full_node_account"] + accounts = ["gateway_account"] nets = ["--net=integration", "--net=testnet", "testnet", "integration"] diff --git a/starknet_py/tests/e2e/fixtures/clients.py b/starknet_py/tests/e2e/fixtures/clients.py index ceb6ecd75..dab831b25 100644 --- a/starknet_py/tests/e2e/fixtures/clients.py +++ b/starknet_py/tests/e2e/fixtures/clients.py @@ -28,6 +28,11 @@ def net_to_clients() -> List[str]: """ Return client fixture names based on network in sys.argv. """ + if "--client=gateway" in sys.argv: + return ["gateway_client"] + if "--client=full_node" in sys.argv: + return ["full_node_client"] + clients = ["gateway_client"] nets = ["--net=integration", "--net=testnet", "testnet", "integration"] diff --git a/starknet_py/tests/e2e/fixtures/contracts.py b/starknet_py/tests/e2e/fixtures/contracts.py index a3131a03c..cf4aeeb41 100644 --- a/starknet_py/tests/e2e/fixtures/contracts.py +++ b/starknet_py/tests/e2e/fixtures/contracts.py @@ -1,14 +1,20 @@ # pylint: disable=redefined-outer-name -from typing import List, Tuple +import json +from typing import Any, Dict, List, Optional, Tuple import pytest import pytest_asyncio -from starknet_py.common import create_casm_class, create_compiled_contract +from starknet_py.common import ( + create_casm_class, + create_compiled_contract, + create_sierra_compiled_contract, +) from starknet_py.constants import FEE_CONTRACT_ADDRESS from starknet_py.contract import Contract from starknet_py.hash.casm_class_hash import compute_casm_class_hash 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_V1_DIR, CONTRACTS_DIR, @@ -110,6 +116,42 @@ async def deploy_contract(account: BaseAccount, class_hash: int, abi: List) -> C return deployment_result.deployed_contract +async def deploy_v1_contract( + account: BaseAccount, + contract_file_name: str, + class_hash: int, + calldata: Optional[Dict[str, Any]] = None, +) -> Contract: + """ + Deploys Cairo1.0 contract. + + :param account: An account which will be used to deploy the Contract. + :param contract_file_name: Name of the file with code (e.g. `erc20` if filename is `erc20.cairo`). + :param class_hash: class_hash of the contract to be deployed. + :param calldata: Dict with constructor arguments (can be empty). + :returns: Instance of the deployed contract. + """ + contract_sierra = read_contract( + contract_file_name + "_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + sierra_compiled_contract = create_sierra_compiled_contract( + compiled_contract=contract_sierra + ) + abi = json.loads(sierra_compiled_contract.abi) + + deployer = Deployer() + deploy_call, address = deployer.create_contract_deployment( + class_hash=class_hash, + abi=abi, + calldata=calldata, + cairo_version=1, + ) + res = await account.execute(calls=deploy_call, max_fee=MAX_FEE) + await account.client.wait_for_tx(res.transaction_hash) + + return Contract(address, abi, provider=account, cairo_version=1) + + @pytest_asyncio.fixture(scope="package") async def deployed_balance_contract( gateway_account: BaseAccount, diff --git a/starknet_py/tests/e2e/fixtures/contracts_v1.py b/starknet_py/tests/e2e/fixtures/contracts_v1.py new file mode 100644 index 000000000..fb783d22e --- /dev/null +++ b/starknet_py/tests/e2e/fixtures/contracts_v1.py @@ -0,0 +1,144 @@ +# pylint: disable=redefined-outer-name +from typing import Tuple + +import pytest +import pytest_asyncio + +from starknet_py.common import create_casm_class +from starknet_py.hash.casm_class_hash import compute_casm_class_hash +from starknet_py.net.account.base_account import BaseAccount +from starknet_py.net.models import DeclareV2 +from starknet_py.tests.e2e.fixtures.constants import CONTRACTS_COMPILED_V1_DIR, MAX_FEE +from starknet_py.tests.e2e.fixtures.misc import read_contract + + +async def declare_cairo1_contract( + account: BaseAccount, compiled_contract: str, compiled_contract_casm: str +) -> Tuple[int, int]: + casm_class_hash = compute_casm_class_hash(create_casm_class(compiled_contract_casm)) + + declare_tx = await account.sign_declare_v2_transaction( + compiled_contract=compiled_contract, + compiled_class_hash=casm_class_hash, + max_fee=MAX_FEE, + ) + assert declare_tx.version == 2 + + resp = await account.client.declare(declare_tx) + await account.client.wait_for_tx(resp.transaction_hash, wait_for_accept=True) + + return resp.class_hash, resp.transaction_hash + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_erc20_class_hash(account: BaseAccount) -> int: + class_hash, _ = await declare_cairo1_contract( + account, + read_contract("erc20_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR), + read_contract("erc20_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR), + ) + return class_hash + + +@pytest_asyncio.fixture(scope="package") +async def declare_v2_hello_starknet(account: BaseAccount) -> DeclareV2: + compiled_contract = read_contract( + "hello_starknet_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ) + compiled_contract_casm = read_contract( + "hello_starknet_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ) + casm_class_hash = compute_casm_class_hash(create_casm_class(compiled_contract_casm)) + + declare_tx = await account.sign_declare_v2_transaction( + compiled_contract, casm_class_hash, max_fee=MAX_FEE + ) + return declare_tx + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_hello_starknet_class_hash_tx_hash( + account: BaseAccount, declare_v2_hello_starknet: DeclareV2 +) -> Tuple[int, int]: + resp = await account.client.declare(declare_v2_hello_starknet) + await account.client.wait_for_tx(resp.transaction_hash) + + return resp.class_hash, resp.transaction_hash + + +@pytest.fixture(scope="package") +def cairo1_hello_starknet_class_hash( + cairo1_hello_starknet_class_hash_tx_hash: Tuple[int, int] +) -> int: + class_hash, _ = cairo1_hello_starknet_class_hash_tx_hash + return class_hash + + +@pytest.fixture(scope="package") +def cairo1_hello_starknet_tx_hash( + cairo1_hello_starknet_class_hash_tx_hash: Tuple[int, int] +) -> int: + _, tx_hash = cairo1_hello_starknet_class_hash_tx_hash + return tx_hash + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_minimal_contract_class_hash(account: BaseAccount) -> int: + class_hash, _ = await declare_cairo1_contract( + account, + read_contract( + "minimal_contract_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ), + read_contract( + "minimal_contract_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ), + ) + return class_hash + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_test_contract_class_hash(account: BaseAccount) -> int: + class_hash, _ = await declare_cairo1_contract( + account, + read_contract( + "test_contract_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ), + read_contract( + "test_contract_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ), + ) + return class_hash + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_test_enum_class_hash(account: BaseAccount) -> int: + class_hash, _ = await declare_cairo1_contract( + account, + read_contract("test_enum_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR), + read_contract("test_enum_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR), + ) + return class_hash + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_test_option_class_hash(account: BaseAccount) -> int: + class_hash, _ = await declare_cairo1_contract( + account, + read_contract("test_option_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR), + read_contract("test_option_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR), + ) + return class_hash + + +@pytest_asyncio.fixture(scope="package") +async def cairo1_token_bridge_class_hash(account: BaseAccount) -> int: + class_hash, _ = await declare_cairo1_contract( + account, + read_contract( + "token_bridge_compiled.json", directory=CONTRACTS_COMPILED_V1_DIR + ), + read_contract( + "token_bridge_compiled.casm", directory=CONTRACTS_COMPILED_V1_DIR + ), + ) + return class_hash diff --git a/starknet_py/tests/e2e/fixtures/misc.py b/starknet_py/tests/e2e/fixtures/misc.py index 0096d2e17..eb6d5fd33 100644 --- a/starknet_py/tests/e2e/fixtures/misc.py +++ b/starknet_py/tests/e2e/fixtures/misc.py @@ -19,6 +19,12 @@ def pytest_addoption(parser): default="devnet", help="Network to run tests on: possible 'testnet', 'devnet', 'all'", ) + parser.addoption( + "--client", + action="store", + default="", + help="Client to run tests with: possible 'gateway', 'full_node'", + ) def pytest_collection_modifyitems(config, items): diff --git a/starknet_py/tests/e2e/mock/contracts_v1/hello_starknet.cairo b/starknet_py/tests/e2e/mock/contracts_v1/hello_starknet.cairo index 41c0e8780..e7152c2dc 100644 --- a/starknet_py/tests/e2e/mock/contracts_v1/hello_starknet.cairo +++ b/starknet_py/tests/e2e/mock/contracts_v1/hello_starknet.cairo @@ -1,7 +1,7 @@ #[contract] mod HelloStarknet { struct Storage { - balance: felt252, + balance: felt252, } // Increases the balance by the given amount. diff --git a/starknet_py/tests/e2e/mock/contracts_v1/test_contract_declare.cairo b/starknet_py/tests/e2e/mock/contracts_v1/test_contract_declare.cairo new file mode 100644 index 000000000..4120b5aea --- /dev/null +++ b/starknet_py/tests/e2e/mock/contracts_v1/test_contract_declare.cairo @@ -0,0 +1,5 @@ +#[contract] +mod TestContractDeclare { + #[view] + fn empty2() {} +} diff --git a/starknet_py/tests/e2e/mock/contracts_v1/test_enum.cairo b/starknet_py/tests/e2e/mock/contracts_v1/test_enum.cairo new file mode 100644 index 000000000..ebc2f35bc --- /dev/null +++ b/starknet_py/tests/e2e/mock/contracts_v1/test_enum.cairo @@ -0,0 +1,32 @@ +use serde::Serde; + +#[derive(Copy, Drop, Serde)] +enum MyEnum { + a: u256, + b: u128, + c: () +} + +#[contract] +mod TestEnum { + use super::MyEnum; + + #[view] + fn receive_and_send_enum(my_enum: MyEnum) -> MyEnum { + my_enum + } + + #[view] + fn get_enum() -> MyEnum { + let my_enum = MyEnum::a(u256{low: 100, high: 0}); + + my_enum + } + + #[view] + fn get_enum_without_value() -> MyEnum { + let my_enum = MyEnum::c(()); + + my_enum + } +} diff --git a/starknet_py/tests/e2e/mock/contracts_v1/test_option.cairo b/starknet_py/tests/e2e/mock/contracts_v1/test_option.cairo new file mode 100644 index 000000000..7e9a5359c --- /dev/null +++ b/starknet_py/tests/e2e/mock/contracts_v1/test_option.cairo @@ -0,0 +1,38 @@ +use serde::Serde; +use array::SpanTrait; + + +#[derive(Copy, Drop, Serde)] +struct OptionStruct { + first_field: felt252, + second_field: Option::, + third_field: Option::, + fourth_field: felt252 +} + +#[contract] +mod HelloStarknet { + use super::OptionStruct; + + #[view] + fn receive_and_send_option_struct(option_struct: OptionStruct) -> OptionStruct { + option_struct + } + + #[view] + fn get_option_struct() -> OptionStruct { + let option_struct = OptionStruct { + first_field: 1, + second_field: Option::Some(u256{low: 2, high: 0}), + third_field: Option::None(()), + fourth_field: 4 + }; + + option_struct + } + + #[view] + fn get_empty_option() -> Option::<()> { + Option::Some(()) + } +} diff --git a/starknet_py/utils/contructor_args_translator.py b/starknet_py/utils/contructor_args_translator.py index 5d099018f..94f126e3a 100644 --- a/starknet_py/utils/contructor_args_translator.py +++ b/starknet_py/utils/contructor_args_translator.py @@ -1,16 +1,24 @@ from typing import List, Optional, Union from starknet_py.abi.parser import AbiParser -from starknet_py.serialization import serializer_for_function +from starknet_py.abi.v1.parser import AbiParser as AbiV1Parser +from starknet_py.serialization import ( + FunctionSerializationAdapter, + serializer_for_function, +) +from starknet_py.serialization.factory import serializer_for_function_v1 def translate_constructor_args( - abi: List, constructor_args: Optional[Union[List, dict]] + abi: List, constructor_args: Optional[Union[List, dict]], *, cairo_version: int = 0 ) -> List[int]: - parsed = AbiParser(abi).parse() + serializer = ( + _get_constructor_serializer_v1(abi) + if cairo_version == 1 + else _get_constructor_serializer_v0(abi) + ) - # Constructor might not accept any arguments - if not parsed.constructor or not parsed.constructor.inputs: + if serializer is None: return [] if not constructor_args: @@ -23,4 +31,25 @@ def translate_constructor_args( if isinstance(constructor_args, dict) else (constructor_args, {}) ) - return serializer_for_function(parsed.constructor).serialize(*args, **kwargs) + return serializer.serialize(*args, **kwargs) + + +def _get_constructor_serializer_v1(abi: List) -> Optional[FunctionSerializationAdapter]: + parsed = AbiV1Parser(abi).parse() + constructor = parsed.functions.get("constructor", None) + + # Constructor might not accept any arguments + if constructor is None or not constructor.inputs: + return None + + return serializer_for_function_v1(constructor) + + +def _get_constructor_serializer_v0(abi: List) -> Optional[FunctionSerializationAdapter]: + parsed = AbiParser(abi).parse() + + # Constructor might not accept any arguments + if not parsed.constructor or not parsed.constructor.inputs: + return None + + return serializer_for_function(parsed.constructor) From bab95faeab0734a78283848ec55afd1b1f31f7b7 Mon Sep 17 00:00:00 2001 From: Kamil Jankowski Date: Mon, 26 Jun 2023 18:13:15 +0200 Subject: [PATCH 17/17] v0.17.0-alpha (#1082) --- poetry.lock | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4de27ac6a..d71b8b31a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -3505,7 +3505,7 @@ 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 = ["sphinx", "enum-tools", "furo"] +docs = ["enum-tools", "furo", "sphinx"] [metadata] lock-version = "2.0" diff --git a/pyproject.toml b/pyproject.toml index 89d9606cf..5419c3043 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "starknet-py" -version = "0.16.1" +version = "0.17.0-alpha" description = "A python SDK for Starknet" authors = ["Tomasz Rejowski ", "Jakub Ptak "] include = ["starknet_py", "starknet_py/utils/crypto/libcrypto_c_exports.*"]