diff --git a/docs/api/typed_data.rst b/docs/api/typed_data.rst index ccc83b76a..14be9f4a5 100644 --- a/docs/api/typed_data.rst +++ b/docs/api/typed_data.rst @@ -15,11 +15,65 @@ Parameter :members: :undoc-members: :exclude-members: __init__ + :member-order: bysource + +------------------- +StandardParameter +------------------- + + +.. autoclass:: starknet_py.utils.typed_data.StandardParameter + :members: + :undoc-members: + :exclude-members: __init__ + :member-order: bysource + +------------------- +EnumParameter +------------------- + +.. autoclass:: starknet_py.utils.typed_data.EnumParameter + :members: + :undoc-members: + :exclude-members: __init__ + :member-order: bysource + +------------------- +MerkleTreeParameter +------------------- + +.. autoclass:: starknet_py.utils.typed_data.MerkleTreeParameter + :members: + :undoc-members: + :exclude-members: __init__ + :member-order: bysource -------------- -StarkNetDomain +Domain -------------- -.. autoclass:: starknet_py.net.models.typed_data.StarkNetDomain +.. autoclass:: starknet_py.utils.typed_data.Domain :members: :undoc-members: + :exclude-members: __init__ + :member-order: bysource + +--------- +BasicType +--------- + +.. autoclass:: starknet_py.utils.typed_data.BasicType + :members: + :undoc-members: + :exclude-members: __init__ + :member-order: bysource + +---------- +PresetType +---------- + +.. autoclass:: starknet_py.utils.typed_data.PresetType + :members: + :undoc-members: + :exclude-members: __init__ + :member-order: bysource \ No newline at end of file diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 58f0323b2..41cef0f73 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -1,6 +1,36 @@ Migration guide =============== +****************************** +0.23.0 Migration guide +****************************** + +Version 0.23.0 of **starknet.py** comes with support for `SNIP-12 `_! + +0.23.0 Targeted versions +------------------------ + +- Starknet - `0.13.1.1 `_ +- RPC - `0.7.1 `_ + +0.23.0 Breaking changes +----------------------- + +.. currentmodule:: starknet_py.utils.typed_data + +1. :class:`StarkNetDomain` has been renamed to :class:`Domain` +2. :class:`TypedData` field ``domain`` has been changed from ``dict`` to :class:`Domain` +3. :class:`Parameter` is now abstract - :class:`StandardParameter`, :class:`EnumParameter` and :class:`MerkleTreeParameter` should be used + +0.23.0 Minor changes +----------------------- + +.. currentmodule:: starknet_py.net.account.account + +1. :meth:`Account.sign_message` now accepts parameter ``typed_data`` as both :class:`~starknet_py.utils.typed_data.TypedData` and :class:`~starknet_py.net.models.typed_data.TypedDataDict` +2. :meth:`Account.verify_message` now accepts parameter ``typed_data`` as both :class:`~starknet_py.utils.typed_data.TypedData` and :class:`~starknet_py.net.models.typed_data.TypedDataDict` +3. :meth:`~starknet_py.net.signer.stark_curve_signer.KeyPair.from_keystore` has been added + ****************************** 0.22.0 Migration guide ****************************** diff --git a/poetry.lock b/poetry.lock index f034e158a..b70007af5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,88 +1,88 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "aiohttp" -version = "3.9.3" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, - {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, - {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, - {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, - {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, - {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, - {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -159,13 +159,13 @@ idna = ">=2.5" [[package]] name = "asgiref" -version = "3.7.2" +version = "3.8.1" description = "ASGI specs, helper code, and adapters" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"}, - {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"}, + {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, + {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, ] [package.dependencies] @@ -637,6 +637,125 @@ files = [ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[[package]] +name = "cytoolz" +version = "0.12.3" +description = "Cython implementation of Toolz: High performance functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + [[package]] name = "dict2css" version = "0.3.0.post1" @@ -718,13 +837,13 @@ gmpy2 = ["gmpy2"] [[package]] name = "enum-tools" -version = "0.11.0" +version = "0.12.0" description = "Tools to expand Python's enum module." optional = true python-versions = ">=3.6" files = [ - {file = "enum_tools-0.11.0-py3-none-any.whl", hash = "sha256:9e76186ff4fd1798a64a855d334e245a7d2b67970c40029acccff06c58bf0535"}, - {file = "enum_tools-0.11.0.tar.gz", hash = "sha256:ed10ae4c2109c52e6ca17505a3bdb173b2554f5f0449677621829023a9d8bd33"}, + {file = "enum_tools-0.12.0-py3-none-any.whl", hash = "sha256:d69b019f193c7b850b17d9ce18440db7ed62381571409af80ccc08c5218b340a"}, + {file = "enum_tools-0.12.0.tar.gz", hash = "sha256:13ceb9376a4c5f574a1e7c5f9c8eb7f3d3fbfbb361cc18a738df1a58dfefd460"}, ] [package.dependencies] @@ -738,6 +857,107 @@ typing-extensions = ">=3.7.4.3" all = ["sphinx (>=3.4.0)", "sphinx-jinja2-compat (>=0.1.1)", "sphinx-toolbox (>=2.16.0)"] sphinx = ["sphinx (>=3.4.0)", "sphinx-jinja2-compat (>=0.1.1)", "sphinx-toolbox (>=2.16.0)"] +[[package]] +name = "eth-hash" +version = "0.7.0" +description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-hash-0.7.0.tar.gz", hash = "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a"}, + {file = "eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f"}, +] + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +pycryptodome = ["pycryptodome (>=3.6.6,<4)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keyfile" +version = "0.8.1" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_keyfile-0.8.1-py3-none-any.whl", hash = "sha256:65387378b82fe7e86d7cb9f8d98e6d639142661b2f6f490629da09fddbef6d64"}, + {file = "eth_keyfile-0.8.1.tar.gz", hash = "sha256:9708bc31f386b52cca0969238ff35b1ac72bd7a7186f2a84b86110d3c973bec1"}, +] + +[package.dependencies] +eth-keys = ">=0.4.0" +eth-utils = ">=2" +pycryptodome = ">=3.6.6,<4" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keys" +version = "0.5.1" +description = "eth-keys: Common API for Ethereum key operations" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_keys-0.5.1-py3-none-any.whl", hash = "sha256:ad13d920a2217a49bed3a1a7f54fb0980f53caf86d3bbab2139fd3330a17b97e"}, + {file = "eth_keys-0.5.1.tar.gz", hash = "sha256:2b587e4bbb9ac2195215a7ab0c0fb16042b17d4ec50240ed670bbb8f53da7a48"}, +] + +[package.dependencies] +eth-typing = ">=3" +eth-utils = ">=2" + +[package.extras] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] + +[[package]] +name = "eth-typing" +version = "4.3.1" +description = "eth-typing: Common type annotations for ethereum python packages" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_typing-4.3.1-py3-none-any.whl", hash = "sha256:b4d7cee912c7779da75da4b42fa61475c1089d35a4df5081a786eaa29d5f6865"}, + {file = "eth_typing-4.3.1.tar.gz", hash = "sha256:4504559c87a9f71f4b99aa5a1e0549adaa7f192cbf8e37a295acfcddb1b5412d"}, +] + +[package.dependencies] +typing-extensions = ">=4.5.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-utils" +version = "4.1.1" +description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_utils-4.1.1-py3-none-any.whl", hash = "sha256:ccbbac68a6d65cb6e294c5bcb6c6a5cec79a241c56dc5d9c345ed788c30f8534"}, + {file = "eth_utils-4.1.1.tar.gz", hash = "sha256:71c8d10dec7494aeed20fa7a4d52ec2ce4a2e52fdce80aab4f5c3c19f3648b25"}, +] + +[package.dependencies] +cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} +eth-hash = ">=0.3.1" +eth-typing = ">=3.0.0" +toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.5.1)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -870,20 +1090,20 @@ files = [ [[package]] name = "furo" -version = "2023.9.10" +version = "2024.5.6" description = "A clean customisable Sphinx documentation theme." optional = true python-versions = ">=3.8" files = [ - {file = "furo-2023.9.10-py3-none-any.whl", hash = "sha256:513092538537dc5c596691da06e3c370714ec99bc438680edc1debffb73e5bfc"}, - {file = "furo-2023.9.10.tar.gz", hash = "sha256:5707530a476d2a63b8cad83b4f961f3739a69f4b058bcf38a03a39fa537195b2"}, + {file = "furo-2024.5.6-py3-none-any.whl", hash = "sha256:490a00d08c0a37ecc90de03ae9227e8eb5d6f7f750edf9807f398a2bdf2358de"}, + {file = "furo-2024.5.6.tar.gz", hash = "sha256:81f205a6605ebccbb883350432b4831c0196dd3d1bc92f61e1f459045b3d2b0b"}, ] [package.dependencies] beautifulsoup4 = "*" pygments = ">=2.7" sphinx = ">=6.0,<8.0" -sphinx-basic-ng = "*" +sphinx-basic-ng = ">=1.0.0.beta2" [[package]] name = "html5lib" @@ -932,7 +1152,7 @@ files = [ name = "importlib-metadata" version = "7.0.1" description = "Read metadata from Python packages" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, @@ -1077,66 +1297,63 @@ files = [ [[package]] name = "marshmallow" -version = "3.20.2" +version = "3.21.3" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.8" files = [ - {file = "marshmallow-3.20.2-py3-none-any.whl", hash = "sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"}, - {file = "marshmallow-3.20.2.tar.gz", hash = "sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd"}, + {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"}, + {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"}, ] [package.dependencies] packaging = ">=17.0" [package.extras] -dev = ["pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.15)", "autodocsumm (==0.2.12)", "sphinx (==7.2.6)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["pre-commit (>=2.4,<4.0)"] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==0.7.16)", "autodocsumm (==0.2.12)", "sphinx (==7.3.7)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] tests = ["pytest", "pytz", "simplejson"] [[package]] name = "marshmallow-dataclass" -version = "8.6.0" +version = "8.7.0" description = "Python library to convert dataclasses into marshmallow schemas." optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "marshmallow_dataclass-8.6.0-py3-none-any.whl", hash = "sha256:7885e9b5f5287b64573b174d69334fd20de1628001a4fa2adc8e75be5196755e"}, - {file = "marshmallow_dataclass-8.6.0.tar.gz", hash = "sha256:a21f4d050a1d24249fd43aa56c7e4aea4b6454e049dc2c5f1496f479e30bf5d7"}, + {file = "marshmallow_dataclass-8.7.0-py3-none-any.whl", hash = "sha256:9e528d72b83f2b6b0f60cb29fd38781a6f7ce2155295adb1ed33289826a93c4b"}, + {file = "marshmallow_dataclass-8.7.0.tar.gz", hash = "sha256:0218008fec3fd4b5f739b2a0c6d7593bcc403308f6da953e341e4e359e268849"}, ] [package.dependencies] -marshmallow = ">=3.13.0,<4.0" -typing-extensions = {version = ">=4.2.0", markers = "python_version < \"3.11\" and python_version >= \"3.7\""} -typing-inspect = ">=0.8.0,<1.0" +marshmallow = ">=3.18.0" +typeguard = ">=4.0.0,<4.1.0" +typing-extensions = {version = ">=4.2.0", markers = "python_version < \"3.11\""} +typing-inspect = ">=0.9.0,<0.10.0" [package.extras] -dev = ["marshmallow (>=3.18.0,<4.0)", "marshmallow-enum", "pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)", "sphinx", "typeguard (>=2.4.1,<4.0.0)"] +dev = ["pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)", "sphinx"] docs = ["sphinx"] -enum = ["marshmallow (>=3.18.0,<4.0)", "marshmallow-enum"] lint = ["pre-commit (>=2.17,<3.0)"] tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)"] -union = ["typeguard (>=2.4.1,<4.0.0)"] [[package]] name = "marshmallow-oneofschema" -version = "3.0.1" +version = "3.1.1" description = "marshmallow multiplexing schema" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "marshmallow-oneofschema-3.0.1.tar.gz", hash = "sha256:62cd2099b29188c92493c2940ee79d1bf2f2619a71721664e5a98ec2faa58237"}, - {file = "marshmallow_oneofschema-3.0.1-py2.py3-none-any.whl", hash = "sha256:bd29410a9f2f7457a2b428286e2a80ef76b8ddc3701527dc1f935a88914b02f2"}, + {file = "marshmallow_oneofschema-3.1.1-py3-none-any.whl", hash = "sha256:ff4cb2a488785ee8edd521a765682c2c80c78b9dc48894124531bdfa1ec9303b"}, + {file = "marshmallow_oneofschema-3.1.1.tar.gz", hash = "sha256:68b4a57d0281a04ac25d4eb7a4c5865a57090a0a8fd30fd6362c8e833ac6a6d9"}, ] [package.dependencies] marshmallow = ">=3.0.0,<4.0.0" [package.extras] -dev = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mock", "pre-commit (>=2.7,<3.0)", "pytest", "tox"] -lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "pre-commit (>=2.7,<3.0)"] -tests = ["mock", "pytest"] +dev = ["marshmallow-oneofschema[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +tests = ["pytest"] [[package]] name = "mccabe" @@ -2228,15 +2445,45 @@ files = [ {file = "tomlkit-0.12.3.tar.gz", hash = "sha256:75baf5012d06501f07bee5bf8e801b9f343e7aac5a92581f20f80ce632e6b5a4"}, ] +[[package]] +name = "toolz" +version = "0.12.1" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, +] + +[[package]] +name = "typeguard" +version = "4.0.1" +description = "Run-time type checker for Python" +optional = false +python-versions = ">=3.7.4" +files = [ + {file = "typeguard-4.0.1-py3-none-any.whl", hash = "sha256:43f55cc9953f26dae362adb973b6c9ad6b97bfffcc6757277912eddd5cfa345b"}, + {file = "typeguard-4.0.1.tar.gz", hash = "sha256:db35142d1f92fc8c1b954e5cc03b57810428f9cd4e4604647bdf5764fc5bbba9"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""} + +[package.extras] +doc = ["Sphinx (<7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["mypy (>=1.2.0)", "pytest (>=7)"] + [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2389,7 +2636,7 @@ multidict = ">=4.0" name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" -optional = true +optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, @@ -2406,4 +2653,4 @@ docs = ["enum-tools", "furo", "sphinx"] [metadata] lock-version = "2.0" python-versions = ">=3.8, <3.13" -content-hash = "d1bddd58cc49fada24208c5d31245316860966a4624331ddee74ff340f8ddf83" +content-hash = "a0ea9cc9e5722b1a4e01b9603eead1274dcd530bd54834ccf23434944abc4557" diff --git a/pyproject.toml b/pyproject.toml index 9bcb57870..72f06e9df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "starknet-py" -version = "0.22.0" +version = "0.23.0" description = "A python SDK for Starknet" authors = ["Tomasz Rejowski ", "Jakub Ptak "] include = ["starknet_py", "starknet_py/utils/crypto/libcrypto_c_exports.*"] @@ -17,17 +17,18 @@ documentation = "https://starknetpy.rtfd.io/" python = ">=3.8, <3.13" asgiref = "^3.4.1" marshmallow = "^3.15.0" -marshmallow-oneofschema = "3.0.1" +marshmallow-oneofschema = "3.1.1" typing-extensions = "^4.3.0" -marshmallow-dataclass = "<8.7.0" +marshmallow-dataclass = "<8.8.0" poseidon-py = "0.1.4" lark = "^1.1.5" aiohttp = "^3.8.4" sphinx = { version = ">=4.3.1,<8.0.0", optional = true } -enum-tools = { extras = ["sphinx"], version = "0.11.0", optional = true } -furo = { version = "^2023.5.20", optional = true } +enum-tools = { extras = ["sphinx"], version = "0.12.0", optional = true } +furo = { version = "^2024.5.6", optional = true } pycryptodome = "^3.17" crypto-cpp-py = "1.4.4" +eth-keyfile = "^0.8.1" [tool.poetry.extras] docs = ["sphinx", "enum-tools", "furo"] diff --git a/starknet_py/abi/v0/schemas.py b/starknet_py/abi/v0/schemas.py index 8ed4b1c4b..bb16e8807 100644 --- a/starknet_py/abi/v0/schemas.py +++ b/starknet_py/abi/v0/schemas.py @@ -1,5 +1,5 @@ from marshmallow import Schema, fields -from marshmallow_oneofschema import OneOfSchema +from marshmallow_oneofschema.one_of_schema import OneOfSchema from starknet_py.abi.v0.shape import ( CONSTRUCTOR_ENTRY, diff --git a/starknet_py/abi/v1/schemas.py b/starknet_py/abi/v1/schemas.py index dacb4d1cc..d79c4c2a9 100644 --- a/starknet_py/abi/v1/schemas.py +++ b/starknet_py/abi/v1/schemas.py @@ -1,5 +1,5 @@ from marshmallow import Schema, fields -from marshmallow_oneofschema import OneOfSchema +from marshmallow_oneofschema.one_of_schema import OneOfSchema from starknet_py.abi.v1.shape import ( ENUM_ENTRY, diff --git a/starknet_py/abi/v2/schemas.py b/starknet_py/abi/v2/schemas.py index 52fae1c93..debb2a66c 100644 --- a/starknet_py/abi/v2/schemas.py +++ b/starknet_py/abi/v2/schemas.py @@ -1,5 +1,5 @@ from marshmallow import Schema, fields -from marshmallow_oneofschema import OneOfSchema +from marshmallow_oneofschema.one_of_schema import OneOfSchema from starknet_py.abi.v2.shape import ( CONSTRUCTOR_ENTRY, diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 39a98bd80..0ce1d0369 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -80,7 +80,7 @@ def parsed_abi(self) -> Union[AbiV0, AbiV1, AbiV2]: return AbiParserV0(self.abi).parse() @staticmethod - def from_abi(address: int, abi: ABI, cairo_version: int = 0) -> ContractData: + def from_abi(address: int, abi: ABI, cairo_version: int = 1) -> ContractData: """ Create ContractData from ABI. @@ -162,7 +162,7 @@ class DeclareResult(SentTransaction): """ _account: BaseAccount = None # pyright: ignore - _cairo_version: int = 0 + _cairo_version: int = 1 class_hash: int = None # pyright: ignore """Class hash of the declared contract.""" @@ -524,7 +524,7 @@ def __init__( contract_data: ContractData, client: Client, account: Optional[BaseAccount], - cairo_version: int = 0, + cairo_version: int = 1, *, interface_name: Optional[str] = None, ): @@ -729,7 +729,7 @@ def __init__( abi: list, provider: Union[BaseAccount, Client], *, - cairo_version: int = 0, + cairo_version: int = 1, ): """ Should be used instead of ``from_address`` when ABI is known statically. @@ -1067,10 +1067,10 @@ def compute_address( deployer_address: int = 0, ) -> int: """ - Computes address for given contract. + Computes address for given Cairo 0 contract. :param salt: int - :param compiled_contract: String containing compiled contract. + :param compiled_contract: String containing compiled Cairo 0 contract. :param constructor_args: A ``list`` or ``dict`` of arguments for the constructor. :param deployer_address: Address of the deployer (if not provided default 0 is used). @@ -1079,7 +1079,9 @@ def compute_address( compiled = create_compiled_contract(compiled_contract) assert compiled.abi is not None - translated_args = translate_constructor_args(compiled.abi, constructor_args) + translated_args = translate_constructor_args( + compiled.abi, constructor_args, cairo_version=0 + ) return compute_address( salt=salt, class_hash=compute_class_hash(compiled), @@ -1105,7 +1107,7 @@ def _make_functions( contract_data: ContractData, client: Client, account: Optional[BaseAccount], - cairo_version: int = 0, + cairo_version: int = 1, ) -> FunctionsRepository: repository = {} implemented_interfaces = [ diff --git a/starknet_py/hash/hash_method.py b/starknet_py/hash/hash_method.py new file mode 100644 index 000000000..dddced0f6 --- /dev/null +++ b/starknet_py/hash/hash_method.py @@ -0,0 +1,29 @@ +from enum import Enum +from typing import List + +from poseidon_py.poseidon_hash import poseidon_hash, poseidon_hash_many + +from starknet_py.hash.utils import compute_hash_on_elements, pedersen_hash + + +class HashMethod(Enum): + """ + Enum representing hash method. + """ + + PEDERSEN = "pedersen" + POSEIDON = "poseidon" + + def hash(self, left: int, right: int): + if self == HashMethod.PEDERSEN: + return pedersen_hash(left, right) + if self == HashMethod.POSEIDON: + return poseidon_hash(left, right) + raise ValueError(f"Unsupported hash method: {self}.") + + def hash_many(self, values: List[int]): + if self == HashMethod.PEDERSEN: + return compute_hash_on_elements(values) + if self == HashMethod.POSEIDON: + return poseidon_hash_many(values) + raise ValueError(f"Unsupported hash method: {self}.") diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index aaf553425..18f493cf2 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -36,7 +36,7 @@ InvokeV3, TypeAccountTransaction, ) -from starknet_py.net.models.typed_data import TypedData +from starknet_py.net.models.typed_data import TypedDataDict from starknet_py.net.signer import BaseSigner from starknet_py.net.signer.stark_curve_signer import KeyPair, StarkCurveSigner from starknet_py.serialization.data_serializers.array_serializer import ArraySerializer @@ -49,7 +49,7 @@ ) from starknet_py.utils.iterable import ensure_iterable from starknet_py.utils.sync import add_sync_methods -from starknet_py.utils.typed_data import TypedData as TypedDataDataclass +from starknet_py.utils.typed_data import TypedData @add_sync_methods @@ -584,13 +584,18 @@ async def execute_v3( ) return await self._client.send_transaction(execute_transaction) - def sign_message(self, typed_data: TypedData) -> List[int]: - typed_data_dataclass = TypedDataDataclass.from_dict(typed_data) + def sign_message(self, typed_data: Union[TypedData, TypedDataDict]) -> List[int]: + if isinstance(typed_data, TypedData): + return self.signer.sign_message(typed_data, self.address) + typed_data_dataclass = TypedData.from_dict(typed_data) return self.signer.sign_message(typed_data_dataclass, self.address) - def verify_message(self, typed_data: TypedData, signature: List[int]) -> bool: - typed_data_dataclass = TypedDataDataclass.from_dict(typed_data) - message_hash = typed_data_dataclass.message_hash(account_address=self.address) + def verify_message( + self, typed_data: Union[TypedData, TypedDataDict], signature: List[int] + ) -> bool: + if not isinstance(typed_data, TypedData): + typed_data = TypedData.from_dict(typed_data) + message_hash = typed_data.message_hash(account_address=self.address) return verify_message_signature(message_hash, signature, self.signer.public_key) @staticmethod diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index 00dcac9d4..aed3b10a2 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -22,7 +22,7 @@ InvokeV3, TypeAccountTransaction, ) -from starknet_py.net.models.typed_data import TypedData +from starknet_py.net.models.typed_data import TypedDataDict class BaseAccount(ABC): @@ -314,19 +314,21 @@ async def execute_v3( """ @abstractmethod - def sign_message(self, typed_data: TypedData) -> List[int]: + def sign_message(self, typed_data: TypedDataDict) -> List[int]: """ Sign an TypedData TypedDict for off-chain usage with the Starknet private key and return the signature. This adds a message prefix, so it can't be interchanged with transactions. + Both v0 and v1 domain revision versions are supported. :param typed_data: TypedData TypedDict to be signed. :return: The signature of the TypedData TypedDict. """ @abstractmethod - def verify_message(self, typed_data: TypedData, signature: List[int]) -> bool: + def verify_message(self, typed_data: TypedDataDict, signature: List[int]) -> bool: """ Verify a signature of a TypedData dict on Starknet. + Both v0 and v1 domain revision versions are supported. :param typed_data: TypedData TypedDict to be verified. :param signature: signature of the TypedData TypedDict. diff --git a/starknet_py/net/models/typed_data.py b/starknet_py/net/models/typed_data.py index 09652dfa8..6a2c23aba 100644 --- a/starknet_py/net/models/typed_data.py +++ b/starknet_py/net/models/typed_data.py @@ -2,34 +2,43 @@ TypedDict structures for TypedData """ -from typing import Any, Dict, List, TypedDict, Union +from typing import Any, Dict, List, Optional, TypedDict +from starknet_py.net.schemas.common import Revision -class Parameter(TypedDict): + +class ParameterDict(TypedDict): """ TypedDict representing a Parameter object """ name: str type: str + contains: Optional[str] -class StarkNetDomain(TypedDict): +class DomainDict(TypedDict): """ - TypedDict representing a StarkNetDomain object + TypedDict representing a domain object (both StarkNetDomain, StarknetDomain). """ name: str version: str - chainId: Union[str, int] + chainId: str + revision: Optional[Revision] -class TypedData(TypedDict): +class TypedDataDict(TypedDict): """ TypedDict representing a TypedData object """ - types: Dict[str, List[Parameter]] + types: Dict[str, List[ParameterDict]] primaryType: str - domain: StarkNetDomain + domain: DomainDict message: Dict[str, Any] + + +class TypeContext(TypedDict): + parent: str + key: str diff --git a/starknet_py/net/schemas/broadcasted_txn.py b/starknet_py/net/schemas/broadcasted_txn.py index da459ea8b..0444ae84b 100644 --- a/starknet_py/net/schemas/broadcasted_txn.py +++ b/starknet_py/net/schemas/broadcasted_txn.py @@ -1,5 +1,5 @@ from marshmallow import fields, post_dump, pre_load -from marshmallow_oneofschema import OneOfSchema +from marshmallow_oneofschema.one_of_schema import OneOfSchema from starknet_py.net.client_models import TransactionType from starknet_py.net.models.transaction import compress_program, decompress_program @@ -49,9 +49,9 @@ def decompress_program(self, data, **kwargs): class BroadcastedDeclareSchema(OneOfSchema): type_schemas = { - 1: BroadcastedDeclareV1Schema, - 2: BroadcastedDeclareV2Schema, - 3: BroadcastedDeclareV3Schema, + "1": BroadcastedDeclareV1Schema, + "2": BroadcastedDeclareV2Schema, + "3": BroadcastedDeclareV3Schema, } def get_obj_type(self, obj): diff --git a/starknet_py/net/schemas/common.py b/starknet_py/net/schemas/common.py index 6b614fbfd..b8ce23ae9 100644 --- a/starknet_py/net/schemas/common.py +++ b/starknet_py/net/schemas/common.py @@ -1,5 +1,6 @@ import re import sys +from enum import Enum from typing import Any, Mapping, Optional, Union from marshmallow import Schema, ValidationError, fields, post_load @@ -336,3 +337,32 @@ class StorageEntrySchema(Schema): def make_dataclass(self, data, **kwargs): # pylint: disable=no-self-use return StorageEntry(**data) + + +class Revision(Enum): + """ + Enum representing the revision of the specification to be used. + """ + + V0 = 0 + V1 = 1 + + +class RevisionField(fields.Field): + def _serialize(self, value: Any, attr: str, obj: Any, **kwargs): + if value is None or value == Revision.V0: + return str(Revision.V0.value) + return value.value + + def _deserialize(self, value, attr, data, **kwargs) -> Revision: + if isinstance(value, str): + value = int(value) + + revisions = [revision.value for revision in Revision] + if value not in revisions: + allowed_revisions_str = "".join(list(map(str, revisions))) + raise ValidationError( + f"Invalid value provided for Revision: {value}. Allowed values are {allowed_revisions_str}." + ) + + return Revision(value) diff --git a/starknet_py/net/schemas/rpc.py b/starknet_py/net/schemas/rpc.py index 59ffe1f14..6079b050d 100644 --- a/starknet_py/net/schemas/rpc.py +++ b/starknet_py/net/schemas/rpc.py @@ -1,7 +1,7 @@ # pylint: disable=too-many-lines from marshmallow import EXCLUDE, fields, post_load -from marshmallow_oneofschema import OneOfSchema +from marshmallow_oneofschema.one_of_schema import OneOfSchema from starknet_py.abi.v0.schemas import ContractAbiEntrySchema from starknet_py.net.client_models import ( @@ -415,10 +415,10 @@ def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionV3: class DeclareTransactionSchema(OneOfSchema): type_schemas = { - 0: DeclareTransactionV0Schema, - 1: DeclareTransactionV1Schema, - 2: DeclareTransactionV2Schema, - 3: DeclareTransactionV3Schema, + "0": DeclareTransactionV0Schema, + "1": DeclareTransactionV1Schema, + "2": DeclareTransactionV2Schema, + "3": DeclareTransactionV3Schema, } def get_data_type(self, data): @@ -427,9 +427,9 @@ def get_data_type(self, data): class InvokeTransactionSchema(OneOfSchema): type_schemas = { - 0: InvokeTransactionV0Schema, - 1: InvokeTransactionV1Schema, - 3: InvokeTransactionV3Schema, + "0": InvokeTransactionV0Schema, + "1": InvokeTransactionV1Schema, + "3": InvokeTransactionV3Schema, } def get_obj_type(self, obj): @@ -441,8 +441,8 @@ def get_data_type(self, data): class DeployAccountTransactionSchema(OneOfSchema): type_schemas = { - 1: DeployAccountTransactionV1Schema, - 3: DeployAccountTransactionV3Schema, + "1": DeployAccountTransactionV1Schema, + "3": DeployAccountTransactionV3Schema, } def get_obj_type(self, obj): diff --git a/starknet_py/net/schemas/utils.py b/starknet_py/net/schemas/utils.py index 6ca12cf11..4b11155cf 100644 --- a/starknet_py/net/schemas/utils.py +++ b/starknet_py/net/schemas/utils.py @@ -3,7 +3,7 @@ from starknet_py.constants import QUERY_VERSION_BASE -def _extract_tx_version(version: Union[int, str]): +def _extract_tx_version(version: Union[int, str]) -> str: if isinstance(version, str): version = int(version, 16) - return version % QUERY_VERSION_BASE + return str(version % QUERY_VERSION_BASE) diff --git a/starknet_py/net/signer/stark_curve_signer.py b/starknet_py/net/signer/stark_curve_signer.py index c582445d8..621a1c5c9 100644 --- a/starknet_py/net/signer/stark_curve_signer.py +++ b/starknet_py/net/signer/stark_curve_signer.py @@ -1,6 +1,8 @@ from dataclasses import dataclass from typing import List +from eth_keyfile.keyfile import extract_key_from_keyfile + from starknet_py.hash.utils import message_signature, private_to_stark_key from starknet_py.net.client_models import Hash from starknet_py.net.models import AddressRepresentation, parse_address @@ -32,6 +34,19 @@ def from_private_key(key: Hash) -> "KeyPair": key = int(key, 0) return KeyPair(private_key=key, public_key=private_to_stark_key(key)) + @staticmethod + def from_keystore(path: str, password: str) -> "KeyPair": + """ + Create a key pair from a keystore file. + The keystore file should follow the Ethereum keystore format. + + :param path: Path to the keystore file. + :param password: Password to decrypt the keystore file. + :return: KeyPair object. + """ + key = extract_key_from_keyfile(path, password) + return KeyPair.from_private_key(int.from_bytes(key, byteorder="big")) + class StarkCurveSigner(BaseSigner): def __init__( diff --git a/starknet_py/net/udc_deployer/deployer.py b/starknet_py/net/udc_deployer/deployer.py index ea79074b0..37bbb0b5e 100644 --- a/starknet_py/net/udc_deployer/deployer.py +++ b/starknet_py/net/udc_deployer/deployer.py @@ -61,7 +61,7 @@ def create_contract_deployment( *, salt: Optional[int] = None, abi: Optional[List] = None, - cairo_version: int = 0, + cairo_version: int = 1, calldata: Optional[Union[List, dict]] = None, ) -> ContractDeployment: """ diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 55ad35961..f86458133 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -67,7 +67,10 @@ async def test_balance_when_token_specified(account, erc20_contract): @pytest.mark.asyncio async def test_estimated_fee_greater_than_zero(account, erc20_contract): erc20_contract = Contract( - address=erc20_contract.address, abi=erc20_contract.data.abi, provider=account + address=erc20_contract.address, + abi=erc20_contract.data.abi, + provider=account, + cairo_version=0, ) estimated_fee = ( diff --git a/starknet_py/tests/e2e/deploy/deployer_test.py b/starknet_py/tests/e2e/deploy/deployer_test.py index cbfd71372..5dc1784fe 100644 --- a/starknet_py/tests/e2e/deploy/deployer_test.py +++ b/starknet_py/tests/e2e/deploy/deployer_test.py @@ -43,7 +43,7 @@ async def test_throws_when_calldata_not_provided(constructor_with_arguments_abi) match="Provided contract has a constructor and no arguments were provided.", ): deployer.create_contract_deployment( - class_hash=1234, abi=constructor_with_arguments_abi + class_hash=1234, abi=constructor_with_arguments_abi, cairo_version=0 ) @@ -72,6 +72,7 @@ async def test_constructor_arguments_contract_deploy( class_hash=constructor_with_arguments_class_hash, abi=constructor_with_arguments_abi, calldata=calldata, + cairo_version=0, ) deploy_invoke_transaction = await account.sign_invoke_v1( @@ -84,6 +85,7 @@ async def test_constructor_arguments_contract_deploy( address=contract_address, abi=constructor_with_arguments_abi, provider=account, + cairo_version=0, ) result = await contract.functions["get"].call(block_number="latest") @@ -145,7 +147,7 @@ async def test_create_deployment_call_raw( deployer = Deployer(account_address=account.address) raw_calldata = translate_constructor_args( - abi=constructor_with_arguments_abi, constructor_args=calldata + abi=constructor_with_arguments_abi, constructor_args=calldata, cairo_version=0 ) ( @@ -182,7 +184,9 @@ async def test_create_deployment_call_raw_supports_seed_0( deployer = Deployer() raw_calldata = translate_constructor_args( - abi=constructor_with_arguments_abi, constructor_args=sample_calldata + abi=constructor_with_arguments_abi, + constructor_args=sample_calldata, + cairo_version=0, ) expected_address = compute_address( diff --git a/starknet_py/tests/e2e/docs/code_examples/test_account.py b/starknet_py/tests/e2e/docs/code_examples/test_account.py index 4da9eafc8..2f32f4b78 100644 --- a/starknet_py/tests/e2e/docs/code_examples/test_account.py +++ b/starknet_py/tests/e2e/docs/code_examples/test_account.py @@ -9,7 +9,7 @@ from starknet_py.net.client_models import Call from starknet_py.net.full_node_client import FullNodeClient from starknet_py.net.models import StarknetChainId -from starknet_py.net.models.typed_data import TypedData +from starknet_py.net.models.typed_data import TypedDataDict from starknet_py.net.signer.stark_curve_signer import KeyPair @@ -66,7 +66,7 @@ async def test_get_balance(account): def test_sign_message(account): # docs-start: sign_message signature = account.sign_message( - typed_data=TypedData( + typed_data=TypedDataDict( types={ "StarkNetDomain": [ {"name": "name", "type": "felt"}, @@ -78,7 +78,7 @@ def test_sign_message(account): ], }, primaryType="Example", - domain={"name": "StarkNet Example", "version": "1", "chainId": 1}, + domain={"name": "StarkNet Example", "version": "1", "chainId": "1"}, message={"value": 1}, ) ) @@ -88,7 +88,7 @@ def test_sign_message(account): def test_verify_message(account): # docs-start: verify_message is_correct = account.verify_message( - typed_data=TypedData( + typed_data=TypedDataDict( types={ "StarkNetDomain": [ {"name": "name", "type": "felt"}, @@ -100,7 +100,7 @@ def test_verify_message(account): ], }, primaryType="Example", - domain={"name": "StarkNet Example", "version": "1", "chainId": 1}, + domain={"name": "StarkNet Example", "version": "1", "chainId": "1"}, message={"value": 1}, ), signature=[12, 34], diff --git a/starknet_py/tests/e2e/docs/code_examples/test_contract.py b/starknet_py/tests/e2e/docs/code_examples/test_contract.py index 4c71a7a5d..14274a9c2 100644 --- a/starknet_py/tests/e2e/docs/code_examples/test_contract.py +++ b/starknet_py/tests/e2e/docs/code_examples/test_contract.py @@ -26,6 +26,7 @@ def test_init(): key_pair=KeyPair(12, 34), chain=StarknetChainId.SEPOLIA, ), + cairo_version=0, ) # docs-end: init diff --git a/starknet_py/tests/e2e/docs/guide/test_deploying_in_multicall.py b/starknet_py/tests/e2e/docs/guide/test_deploying_in_multicall.py index 85b769231..b62d03fcd 100644 --- a/starknet_py/tests/e2e/docs/guide/test_deploying_in_multicall.py +++ b/starknet_py/tests/e2e/docs/guide/test_deploying_in_multicall.py @@ -23,7 +23,9 @@ async def test_deploying_in_multicall(account, map_class_hash, map_compiled_cont # docs: start # Address of the `map` contract is known here, so we can create its instance! - map_contract = Contract(address=address, abi=map_abi, provider=account) + map_contract = Contract( + address=address, abi=map_abi, provider=account, cairo_version=0 + ) # And now we can prepare a call put_call = map_contract.functions["put"].prepare_invoke_v1(key=10, value=20) diff --git a/starknet_py/tests/e2e/docs/guide/test_deploying_with_udc.py b/starknet_py/tests/e2e/docs/guide/test_deploying_with_udc.py index 01bf5670b..e1a2451dc 100644 --- a/starknet_py/tests/e2e/docs/guide/test_deploying_with_udc.py +++ b/starknet_py/tests/e2e/docs/guide/test_deploying_with_udc.py @@ -56,6 +56,7 @@ async def test_deploying_with_udc( deploy_call, address = deployer.create_contract_deployment( class_hash=contract_with_constructor_class_hash, abi=contract_with_constructor_abi, + cairo_version=0, calldata={ "single_value": 10, "tuple": (1, (2, 3)), @@ -77,6 +78,7 @@ async def test_deploying_with_udc( "arr": [1], "dict": {"value": 12, "nested_struct": {"value": 99}}, }, + cairo_version=0, ) # docs: start # Or signed and send with an account diff --git a/starknet_py/tests/e2e/docs/guide/test_sign_offchain_message.py b/starknet_py/tests/e2e/docs/guide/test_sign_offchain_message.py index 1b3e272a9..fa1ad5438 100644 --- a/starknet_py/tests/e2e/docs/guide/test_sign_offchain_message.py +++ b/starknet_py/tests/e2e/docs/guide/test_sign_offchain_message.py @@ -31,7 +31,7 @@ async def test_sign_offchain_message(account): ], }, "primaryType": "Mail", - "domain": {"name": "StarkNet Mail", "version": "1", "chainId": 1}, + "domain": {"name": "StarkNet Mail", "version": "1", "chainId": "1"}, "message": { "from": { "name": "Cow", diff --git a/starknet_py/tests/e2e/docs/guide/test_using_existing_contracts.py b/starknet_py/tests/e2e/docs/guide/test_using_existing_contracts.py index d92d7504f..ddcd50104 100644 --- a/starknet_py/tests/e2e/docs/guide/test_using_existing_contracts.py +++ b/starknet_py/tests/e2e/docs/guide/test_using_existing_contracts.py @@ -32,7 +32,7 @@ async def test_using_existing_contracts(account, erc20_contract): address = "0x00178130dd6286a9a0e031e4c73b2bd04ffa92804264a25c1c08c1612559f458" # When ABI is known statically just use the Contract constructor - contract = Contract(address=address, abi=abi, provider=account) + contract = Contract(address=address, abi=abi, provider=account, cairo_version=0) # or if it is not known # Contract.from_address makes additional request to fetch the ABI # docs: end diff --git a/starknet_py/tests/e2e/docs/quickstart/test_using_contract.py b/starknet_py/tests/e2e/docs/quickstart/test_using_contract.py index 1203e1298..ab6dc6e66 100644 --- a/starknet_py/tests/e2e/docs/quickstart/test_using_contract.py +++ b/starknet_py/tests/e2e/docs/quickstart/test_using_contract.py @@ -34,6 +34,7 @@ async def test_using_contract(account, map_contract): address=contract_address, abi=abi, provider=account, + cairo_version=0, ) # All exposed functions are available at contract.functions. diff --git a/starknet_py/tests/e2e/fixtures/contracts.py b/starknet_py/tests/e2e/fixtures/contracts.py index d3a70707d..453e16fd3 100644 --- a/starknet_py/tests/e2e/fixtures/contracts.py +++ b/starknet_py/tests/e2e/fixtures/contracts.py @@ -238,6 +238,7 @@ def eth_fee_contract(account: BaseAccount, fee_contract_abi) -> Contract: address=FEE_CONTRACT_ADDRESS, abi=fee_contract_abi, provider=account, + cairo_version=0, ) @@ -251,6 +252,7 @@ def strk_fee_contract(account: BaseAccount, fee_contract_abi) -> Contract: address=STRK_FEE_CONTRACT_ADDRESS, abi=fee_contract_abi, provider=account, + cairo_version=0, ) diff --git a/starknet_py/tests/e2e/fixtures/misc.py b/starknet_py/tests/e2e/fixtures/misc.py index e7d73d264..49878e087 100644 --- a/starknet_py/tests/e2e/fixtures/misc.py +++ b/starknet_py/tests/e2e/fixtures/misc.py @@ -9,7 +9,7 @@ import pytest from starknet_py.net.full_node_client import FullNodeClient -from starknet_py.net.models.typed_data import TypedData +from starknet_py.net.models.typed_data import TypedDataDict from starknet_py.tests.e2e.fixtures.constants import ( CONTRACTS_V1_ARTIFACTS_MAP, CONTRACTS_V1_COMPILED, @@ -30,13 +30,14 @@ def pytest_addoption(parser): @pytest.fixture( params=[ - "typed_data_example.json", - "typed_data_felt_array_example.json", - "typed_data_long_string_example.json", - "typed_data_struct_array_example.json", + "typed_data_rev_0_example.json", + "typed_data_rev_0_felt_array_example.json", + "typed_data_rev_0_long_string_example.json", + "typed_data_rev_0_struct_array_example.json", + "typed_data_rev_1_example.json", ], ) -def typed_data(request) -> TypedData: +def typed_data(request) -> TypedDataDict: """ Returns TypedData dictionary example. """ diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_example.json similarity index 97% rename from starknet_py/tests/e2e/mock/typed_data/typed_data_example.json rename to starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_example.json index 8efa1e038..690a6dc88 100644 --- a/starknet_py/tests/e2e/mock/typed_data/typed_data_example.json +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_example.json @@ -19,7 +19,7 @@ "domain": { "name": "StarkNet Mail", "version": "1", - "chainId": 1 + "chainId": "1" }, "message": { "from": { diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_felt_array_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_felt_array_example.json similarity index 97% rename from starknet_py/tests/e2e/mock/typed_data/typed_data_felt_array_example.json rename to starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_felt_array_example.json index 1eea5b1cd..0f7815ca3 100644 --- a/starknet_py/tests/e2e/mock/typed_data/typed_data_felt_array_example.json +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_felt_array_example.json @@ -20,7 +20,7 @@ "domain": { "name": "StarkNet Mail", "version": "1", - "chainId": 1 + "chainId": "1" }, "message": { "from": { diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_long_string_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_long_string_example.json similarity index 98% rename from starknet_py/tests/e2e/mock/typed_data/typed_data_long_string_example.json rename to starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_long_string_example.json index d35dd5908..af5f77fc2 100644 --- a/starknet_py/tests/e2e/mock/typed_data/typed_data_long_string_example.json +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_long_string_example.json @@ -23,7 +23,7 @@ "domain": { "name": "StarkNet Mail", "version": "1", - "chainId": 1 + "chainId": "1" }, "message": { "from": { diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_struct_array_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_array_example.json similarity index 98% rename from starknet_py/tests/e2e/mock/typed_data/typed_data_struct_array_example.json rename to starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_array_example.json index fe4e6506b..771f0e9f3 100644 --- a/starknet_py/tests/e2e/mock/typed_data/typed_data_struct_array_example.json +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_array_example.json @@ -24,7 +24,7 @@ "domain": { "name": "StarkNet Mail", "version": "1", - "chainId": 1 + "chainId": "1" }, "message": { "from": { diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json new file mode 100644 index 000000000..05ed4af7b --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_0_struct_merkletree_example.json @@ -0,0 +1,67 @@ +{ + "primaryType": "Session", + "types": { + "Policy": [ + { + "name": "contractAddress", + "type": "felt" + }, + { + "name": "selector", + "type": "selector" + } + ], + "Session": [ + { + "name": "key", + "type": "felt" + }, + { + "name": "expires", + "type": "felt" + }, + { + "name": "root", + "type": "merkletree", + "contains": "Policy" + } + ], + "StarkNetDomain": [ + { + "name": "name", + "type": "felt" + }, + { + "name": "version", + "type": "felt" + }, + { + "name": "chainId", + "type": "felt" + } + ] + }, + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1" + }, + "message": { + "key": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expires": "0x0000000000000000000000000000000000000000000000000000000000000000", + "root": [ + { + "contractAddress": "0x1", + "selector": "transfer" + }, + { + "contractAddress": "0x2", + "selector": "transfer" + }, + { + "contractAddress": "0x3", + "selector": "transfer" + } + ] + } +} diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_basic_types_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_basic_types_example.json new file mode 100644 index 000000000..ed0cea61a --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_basic_types_example.json @@ -0,0 +1,41 @@ +{ + "types": { + "StarknetDomain": [ + { "name": "name", "type": "shortstring" }, + { "name": "version", "type": "shortstring" }, + { "name": "chainId", "type": "shortstring" }, + { "name": "revision", "type": "shortstring" } + ], + "Example": [ + { "name": "n0", "type": "felt" }, + { "name": "n1", "type": "bool" }, + { "name": "n2", "type": "string" }, + { "name": "n3", "type": "selector" }, + { "name": "n4", "type": "u128" }, + { "name": "n5", "type": "i128" }, + { "name": "n6", "type": "ContractAddress" }, + { "name": "n7", "type": "ClassHash" }, + { "name": "n8", "type": "timestamp" }, + { "name": "n9", "type": "shortstring" } + ] + }, + "primaryType": "Example", + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1", + "revision": "1" + }, + "message": { + "n0": "0x3e8", + "n1": true, + "n2": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "n3": "transfer", + "n4": "0x3e8", + "n5": "-170141183460469231731687303715884105727", + "n6": "0x3e8", + "n7": "0x3e8", + "n8": 1000, + "n9": "transfer" + } +} diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_enum_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_enum_example.json new file mode 100644 index 000000000..86943db7e --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_enum_example.json @@ -0,0 +1,28 @@ +{ + "types": { + "StarknetDomain": [ + { "name": "name", "type": "shortstring" }, + { "name": "version", "type": "shortstring" }, + { "name": "chainId", "type": "shortstring" }, + { "name": "revision", "type": "shortstring" } + ], + "Example": [{ "name": "someEnum", "type": "enum", "contains": "MyEnum" }], + "MyEnum": [ + { "name": "Variant 1", "type": "()" }, + { "name": "Variant 2", "type": "(u128,u128*)" }, + { "name": "Variant 3", "type": "(u128)" } + ] + }, + "primaryType": "Example", + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1", + "revision": "1" + }, + "message": { + "someEnum": { + "Variant 2": [2, [0, 1]] + } + } +} diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_example.json new file mode 100644 index 000000000..fe898ab45 --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_example.json @@ -0,0 +1,37 @@ +{ + "types": { + "StarknetDomain": [ + { "name": "name", "type": "shortstring" }, + { "name": "version", "type": "shortstring" }, + { "name": "chainId", "type": "shortstring" }, + { "name": "revision", "type": "shortstring" } + ], + "Person": [ + { "name": "name", "type": "felt" }, + { "name": "wallet", "type": "felt" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "felt" } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1", + "revision": 1 + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } +} diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json new file mode 100644 index 000000000..891476c48 --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_felt_merkletree_example.json @@ -0,0 +1,29 @@ +{ + "primaryType": "Example", + "types": { + "Example": [ + { "name": "value", "type": "felt" }, + { "name": "root", "type": "merkletree", "contains": "felt" } + ], + "StarknetDomain": [ + { "name": "name", "type": "shortstring" }, + { "name": "version", "type": "shortstring" }, + { "name": "chainId", "type": "shortstring" }, + { "name": "revision", "type": "shortstring" } + ] + }, + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1", + "revision": "1" + }, + "message": { + "value": "0x2137", + "root": [ + "0x1", + "0x2", + "0x3" + ] + } +} diff --git a/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_preset_types_example.json b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_preset_types_example.json new file mode 100644 index 000000000..65d860fe1 --- /dev/null +++ b/starknet_py/tests/e2e/mock/typed_data/typed_data_rev_1_preset_types_example.json @@ -0,0 +1,37 @@ +{ + "types": { + "StarknetDomain": [ + { "name": "name", "type": "shortstring" }, + { "name": "version", "type": "shortstring" }, + { "name": "chainId", "type": "shortstring" }, + { "name": "revision", "type": "shortstring" } + ], + "Example": [ + { "name": "n0", "type": "TokenAmount" }, + { "name": "n1", "type": "NftId" } + ] + }, + "primaryType": "Example", + "domain": { + "name": "StarkNet Mail", + "version": "1", + "chainId": "1", + "revision": "1" + }, + "message": { + "n0": { + "token_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "amount": { + "low": "0x3e8", + "high": "0x0" + } + }, + "n1": { + "collection_address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + "token_id": { + "low": "0x3e8", + "high": "0x0" + } + } + } +} diff --git a/starknet_py/utils/constructor_args_translator.py b/starknet_py/utils/constructor_args_translator.py index 8959d073d..0db98fc3e 100644 --- a/starknet_py/utils/constructor_args_translator.py +++ b/starknet_py/utils/constructor_args_translator.py @@ -15,7 +15,7 @@ def translate_constructor_args( - abi: List, constructor_args: Optional[Union[List, dict]], *, cairo_version: int = 0 + abi: List, constructor_args: Optional[Union[List, dict]], *, cairo_version: int = 1 ) -> List[int]: serializer = ( _get_constructor_serializer_v1(abi) diff --git a/starknet_py/utils/merkle_tree.py b/starknet_py/utils/merkle_tree.py new file mode 100644 index 000000000..fca65d9c7 --- /dev/null +++ b/starknet_py/utils/merkle_tree.py @@ -0,0 +1,45 @@ +from dataclasses import dataclass, field +from typing import List, Tuple + +from starknet_py.hash.hash_method import HashMethod + + +@dataclass +class MerkleTree: + """ + Dataclass representing a MerkleTree object. + """ + + leaves: List[int] + hash_method: HashMethod + root_hash: int = field(init=False) + levels: List[List[int]] = field(init=False) + + def __post_init__(self): + self.root_hash, self.levels = self._build() + + def _build(self) -> Tuple[int, List[List[int]]]: + if not self.leaves: + raise ValueError("Cannot build Merkle tree from an empty list of leaves.") + + if len(self.leaves) == 1: + return self.leaves[0], [self.leaves] + + curr_level_nodes = self.leaves[:] + levels: List[List[int]] = [] + + while len(curr_level_nodes) > 1: + if len(curr_level_nodes) != len(self.leaves): + levels.append(curr_level_nodes[:]) + + new_nodes = [] + for i in range(0, len(curr_level_nodes), 2): + a, b = ( + curr_level_nodes[i], + curr_level_nodes[i + 1] if i + 1 < len(curr_level_nodes) else 0, + ) + new_nodes.append(self.hash_method.hash(*sorted([a, b]))) + + curr_level_nodes = new_nodes + levels = [self.leaves] + levels + [curr_level_nodes] + return curr_level_nodes[0], levels diff --git a/starknet_py/utils/merkle_tree_test.py b/starknet_py/utils/merkle_tree_test.py new file mode 100644 index 000000000..29b5899cd --- /dev/null +++ b/starknet_py/utils/merkle_tree_test.py @@ -0,0 +1,140 @@ +from typing import List + +import pytest +from poseidon_py.poseidon_hash import poseidon_hash + +from starknet_py.hash.hash_method import HashMethod +from starknet_py.hash.utils import pedersen_hash +from starknet_py.utils.merkle_tree import MerkleTree + + +@pytest.mark.parametrize( + "leaves, hash_method, expected_root_hash", + [ + ( + ["0x12", "0xa"], + HashMethod.PEDERSEN, + "0x586699e3ba6f118227e094ad423313a2d51871507dcbc23116f11cdd79d80f2", + ), + ( + ["0x12", "0xa"], + HashMethod.POSEIDON, + "0x6257f1f60f7c9fd49e2718c8ad19cd8dce6b1ba4b553b2123113f22b1e9c379", + ), + ( + [ + "0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026", + "0x3", + ], + HashMethod.PEDERSEN, + "0x551b4adb6c35d49c686a00b9192da9332b18c9b262507cad0ece37f3b6918d2", + ), + ( + [ + "0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026", + "0x3", + ], + HashMethod.POSEIDON, + "0xc118a3963c12777b0717d1dc89baa8b3ceed84dfd713a6bd1354676f03f021", + ), + ], +) +def test_calculate_hash( + leaves: List[str], hash_method: HashMethod, expected_root_hash: str +): + if hash_method == HashMethod.PEDERSEN: + apply_hash = pedersen_hash + elif hash_method == HashMethod.POSEIDON: + apply_hash = poseidon_hash + else: + raise ValueError(f"Unsupported hash method: {hash_method}.") + + a, b = int(leaves[0], 16), int(leaves[1], 16) + merkle_hash = hash_method.hash(*sorted([b, a])) + raw_hash = apply_hash(*sorted([b, a])) + + assert raw_hash == merkle_hash + assert int(expected_root_hash, 16) == merkle_hash + + +@pytest.mark.parametrize( + "hash_method", + [ + HashMethod.PEDERSEN, + HashMethod.POSEIDON, + ], +) +def test_build_from_0_elements(hash_method: HashMethod): + with pytest.raises( + ValueError, match="Cannot build Merkle tree from an empty list of leaves." + ): + MerkleTree([], hash_method) + + +@pytest.mark.parametrize( + "leaves, hash_method, expected_root_hash, expected_levels_count", + [ + (["0x1"], HashMethod.PEDERSEN, "0x1", 1), + (["0x1"], HashMethod.POSEIDON, "0x1", 1), + ( + ["0x1", "0x2"], + HashMethod.PEDERSEN, + "0x5bb9440e27889a364bcb678b1f679ecd1347acdedcbf36e83494f857cc58026", + 2, + ), + ( + ["0x1", "0x2"], + HashMethod.POSEIDON, + "0x5d44a3decb2b2e0cc71071f7b802f45dd792d064f0fc7316c46514f70f9891a", + 2, + ), + ( + ["0x1", "0x2", "0x3", "0x4"], + HashMethod.PEDERSEN, + "0x38118a340bbba28e678413cd3b07a9436a5e60fd6a7cbda7db958a6d501e274", + 3, + ), + ( + ["0x1", "0x2", "0x3", "0x4"], + HashMethod.POSEIDON, + "0xa4d02f1e82fc554b062b754d3a4995e0ed8fc7e5016a7ca2894a451a4bae64", + 3, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6"], + HashMethod.PEDERSEN, + "0x329d5b51e352537e8424bfd85b34d0f30b77d213e9b09e2976e6f6374ecb59", + 4, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6"], + HashMethod.POSEIDON, + "0x34d525f018d8d6b3e492b1c9cda9bbdc3bc7834b408a30a417186c698c34766", + 4, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6", "0x7"], + HashMethod.PEDERSEN, + "0x7f748c75e5bdb7ae28013f076b8ab650c4e01d3530c6e5ab665f9f1accbe7d4", + 4, + ), + ( + ["0x1", "0x2", "0x3", "0x4", "0x5", "0x6", "0x7"], + HashMethod.POSEIDON, + "0x3308a3c50c25883753f82b21f14c644ec375b88ea5b0f83d1e6afe74d0ed790", + 4, + ), + ], +) +def test_build_from_elements( + leaves: List[str], + hash_method: HashMethod, + expected_root_hash: str, + expected_levels_count: int, +): + tree = MerkleTree([int(leaf, 16) for leaf in leaves], hash_method) + + assert tree.root_hash is not None + assert tree.levels is not None + assert tree.root_hash == int(expected_root_hash, 16) + assert len(tree.levels) == expected_levels_count diff --git a/starknet_py/utils/typed_data.py b/starknet_py/utils/typed_data.py index a24031a28..ebdd90020 100644 --- a/starknet_py/utils/typed_data.py +++ b/starknet_py/utils/typed_data.py @@ -1,23 +1,115 @@ -from dataclasses import dataclass -from typing import Dict, List, Union, cast +import re +from abc import ABC +from dataclasses import dataclass, field +from enum import Enum +from typing import Dict, List, Optional, Union, cast from marshmallow import Schema, fields, post_load from starknet_py.cairo.felt import encode_shortstring +from starknet_py.constants import FIELD_PRIME +from starknet_py.hash.hash_method import HashMethod from starknet_py.hash.selector import get_selector_from_name -from starknet_py.hash.utils import compute_hash_on_elements -from starknet_py.net.models.typed_data import StarkNetDomain -from starknet_py.net.models.typed_data import TypedData as TypedDataDict +from starknet_py.net.client_utils import _to_rpc_felt +from starknet_py.net.models.typed_data import DomainDict, Revision, TypedDataDict +from starknet_py.net.schemas.common import RevisionField +from starknet_py.serialization.data_serializers import ByteArraySerializer +from starknet_py.utils.merkle_tree import MerkleTree @dataclass(frozen=True) -class Parameter: +class Parameter(ABC): + name: str + type: str + + +@dataclass(frozen=True) +class StandardParameter(Parameter): + pass + + +@dataclass(frozen=True) +class MerkleTreeParameter(Parameter): + type: str = field(default="merkletree", init=False) + contains: str + + +@dataclass(frozen=True) +class EnumParameter(Parameter): + type: str = field(default="enum", init=False) + contains: str + + +@dataclass +class Domain: """ - Dataclass representing a Parameter object + Dataclass representing a domain object (StarkNetDomain, StarknetDomain) """ name: str - type: str + version: str + chain_id: Union[str, int] + revision: Optional[Revision] = None + + def __post_init__(self): + self.resolved_revision = ( + Revision(self.revision) if self.revision else Revision.V0 + ) + self.separator_name = self._resolve_separator_name() + + def _resolve_separator_name(self): + if self.resolved_revision == Revision.V0: + return "StarkNetDomain" + return "StarknetDomain" + + @staticmethod + def from_dict(data: DomainDict) -> "Domain": + """ + Create Domain dataclass from dictionary. + + :param data: Domain dictionary. + :return: Domain dataclass instance. + """ + return cast(Domain, DomainSchema().load(data)) + + def to_dict(self) -> dict: + """ + Create Domain dictionary from dataclass. + + :return: Domain dictionary. + """ + return cast(Dict, DomainSchema().dump(obj=self)) + + +@dataclass(frozen=True) +class TypeContext: + """ + Dataclass representing a Context object + """ + + parent: str + key: str + + +class BasicType(Enum): + FELT = "felt" + SELECTOR = "selector" + MERKLE_TREE = "merkletree" + ENUM = "enum" + SHORT_STRING = "shortstring" + STRING = "string" + CONTRACT_ADDRESS = "ContractAddress" + CLASS_HASH = "ClassHash" + BOOL = "bool" + U128 = "u128" + I128 = "i128" + TIMESTAMP = "timestamp" + + +class PresetType(Enum): + U256 = "u256" + TOKEN_AMOUNT = "TokenAmount" + NFT_ID = "NftId" @dataclass(frozen=True) @@ -28,9 +120,26 @@ class TypedData: types: Dict[str, List[Parameter]] primary_type: str - domain: StarkNetDomain + domain: Domain message: dict + def __post_init__(self): + self._verify_types() + + @property + def _all_types(self): + preset_types = _get_preset_types(self.domain.resolved_revision) + return { + **preset_types, + **self.types, + } + + @property + def _hash_method(self) -> HashMethod: + if self.domain.resolved_revision == Revision.V0: + return HashMethod.PEDERSEN + return HashMethod.POSEIDON + @staticmethod def from_dict(data: TypedDataDict) -> "TypedData": """ @@ -41,61 +150,254 @@ def from_dict(data: TypedDataDict) -> "TypedData": """ return cast(TypedData, TypedDataSchema().load(data)) + def to_dict(self) -> dict: + """ + Create TypedData dictionary from dataclass. + + :return: TypedData dictionary. + """ + + return cast(Dict, TypedDataSchema().dump(obj=self)) + def _is_struct(self, type_name: str) -> bool: return type_name in self.types - def _encode_value(self, type_name: str, value: Union[int, str, dict, list]) -> int: + def _encode_value_v1( + self, + basic_type: BasicType, + value: Union[int, str, dict], + type_name: str, + context: Optional[TypeContext] = None, + ) -> Optional[int]: + if basic_type in ( + BasicType.FELT, + BasicType.SHORT_STRING, + BasicType.CONTRACT_ADDRESS, + BasicType.CLASS_HASH, + ) and isinstance(value, (int, str)): + return parse_felt(value) + + if basic_type in ( + BasicType.U128, + BasicType.TIMESTAMP, + ) and isinstance(value, (int, str)): + return encode_u128(value) + + if basic_type == BasicType.I128 and isinstance(value, (int, str)): + return encode_i128(value) + + if basic_type == BasicType.STRING and isinstance(value, str): + return self._encode_long_string(value) + + if basic_type == BasicType.ENUM and isinstance(value, dict): + if context is None: + raise ValueError(f"Context is not provided for '{type_name}' type.") + return self._encode_enum(value, context) + + return None + + # pylint: disable=no-self-use + def _encode_value_v0( + self, + basic_type: BasicType, + value: Union[int, str], + ) -> Optional[int]: + if basic_type in ( + BasicType.FELT, + BasicType.STRING, + ) and isinstance(value, (int, str)): + return parse_felt(value) + + return None + + def _encode_value( + self, + type_name: str, + value: Union[int, str, dict, list], + context: Optional[TypeContext] = None, + ) -> int: + if type_name in self._all_types and isinstance(value, dict): + return self.struct_hash(type_name, value) + if is_pointer(type_name) and isinstance(value, list): type_name = strip_pointer(type_name) + hashes = [self._encode_value(type_name, val) for val in value] + return self._hash_method.hash_many(hashes) - if self._is_struct(type_name): - return compute_hash_on_elements( - [self.struct_hash(type_name, data) for data in value] - ) - return compute_hash_on_elements([int(get_hex(val), 16) for val in value]) + if type_name not in _get_basic_type_names(self.domain.resolved_revision): + raise ValueError(f"Type [{type_name}] is not defined in types.") - if self._is_struct(type_name) and isinstance(value, dict): - return self.struct_hash(type_name, value) + basic_type = BasicType(type_name) + + encoded_value = None + if self.domain.resolved_revision == Revision.V0 and isinstance( + value, (str, int) + ): + encoded_value = self._encode_value_v0(basic_type, value) + elif self.domain.resolved_revision == Revision.V1 and isinstance( + value, (str, int, dict) + ): + encoded_value = self._encode_value_v1(basic_type, value, type_name, context) + + if encoded_value is not None: + return encoded_value + + if basic_type == BasicType.BOOL and isinstance(value, (bool, str, int)): + return encode_bool(value) - value = cast(Union[int, str], value) - return int(get_hex(value), 16) + if basic_type == BasicType.SELECTOR and isinstance(value, str): + return prepare_selector(value) + + if basic_type == BasicType.MERKLE_TREE and isinstance(value, list): + if context is None: + raise ValueError(f"Context is not provided for '{type_name}' type.") + return self._prepare_merkle_tree_root(value, context) + + raise ValueError( + f"Error occurred while encoding value with type name {type_name}." + ) def _encode_data(self, type_name: str, data: dict) -> List[int]: values = [] - for param in self.types[type_name]: - encoded_value = self._encode_value(param.type, data[param.name]) + for param in self._all_types[type_name]: + encoded_value = self._encode_value( + param.type, + data[param.name], + TypeContext(parent=type_name, key=param.name), + ) values.append(encoded_value) return values - def _get_dependencies(self, type_name: str) -> List[str]: - if type_name not in self.types: - # type_name is a primitive type, has no dependencies - return [] + # pylint: disable=too-many-branches + def _verify_types(self): + if self.domain.separator_name not in self.types: + raise ValueError(f"Types must contain '{self.domain.separator_name}'.") + + referenced_types = set() + for type_name in self.types: + for ref_type in self.types[type_name]: + if isinstance(ref_type, (EnumParameter, MerkleTreeParameter)): + referenced_types.add(ref_type.contains) + elif is_enum_variant_type(ref_type.type): + referenced_types.update(_extract_enum_types(ref_type.type)) + else: + referenced_types.add(strip_pointer(ref_type.type)) + + referenced_types.update([self.domain.separator_name, self.primary_type]) + + basic_type_names = _get_basic_type_names(self.domain.resolved_revision) + preset_type_names = _get_preset_types(self.domain.resolved_revision).keys() + + for type_name in self.types: + if not type_name: + raise ValueError("Type names cannot be empty.") + + if type_name in basic_type_names: + raise ValueError( + f"Types must not contain basic types. [{type_name}] was found." + ) - dependencies = set() + if type_name in preset_type_names: + raise ValueError( + f"Types must not contain preset types. [{type_name}] was found." + ) - def collect_deps(type_name: str) -> None: - for param in self.types[type_name]: - fixed_type = strip_pointer(param.type) - if fixed_type in self.types and fixed_type not in dependencies: - dependencies.add(fixed_type) - # recursive call - collect_deps(fixed_type) + if is_pointer(type_name): + raise ValueError( + f"Type names cannot end in *. [{type_name}] was found." + ) - # collect dependencies into a set - collect_deps(type_name) - return [type_name, *list(dependencies)] + if is_enum_variant_type(type_name): + raise ValueError( + f"Type names cannot be enclosed in parentheses. [{type_name}] was found." + ) + + if "," in type_name: + raise ValueError( + f"Type names cannot contain commas. [{type_name}] was found." + ) + + if type_name not in referenced_types: + raise ValueError( + f"Dangling types are not allowed. Unreferenced type [{type_name}] was found." + ) + + for ref_type in self.types[type_name]: + if isinstance(ref_type, EnumParameter): + self._validate_enum_type() + + def _validate_enum_type(self): + if self.domain.resolved_revision != Revision.V1: + raise ValueError( + f"'{BasicType.ENUM.name}' basic type is not supported in revision " + f"{self.domain.resolved_revision.value}." + ) + + def _get_dependencies(self, type_name: str) -> List[str]: + dependencies = [type_name] + to_visit = [type_name] + + while to_visit: + current_type = to_visit.pop(0) + params = self._all_types.get(current_type, []) + + for param in params: + if isinstance(param, EnumParameter): + extracted_types = [param.contains] + elif is_enum_variant_type(param.type): + extracted_types = _extract_enum_types(param.type) + else: + extracted_types = [param.type] + + extracted_types = [ + strip_pointer(extr_type) for extr_type in extracted_types + ] + for extracted_type in extracted_types: + if ( + extracted_type in self._all_types + and extracted_type not in dependencies + ): + dependencies.append(extracted_type) + to_visit.append(extracted_type) + + return list(dependencies) def _encode_type(self, type_name: str) -> str: primary, *dependencies = self._get_dependencies(type_name) types = [primary, *sorted(dependencies)] - def make_dependency_str(dependency): - lst = [f"{t.name}:{t.type}" for t in self.types[dependency]] - return f"{dependency}({','.join(lst)})" + def encode_dependency(dependency: str) -> str: + def escape(s: str) -> str: + if self.domain.resolved_revision == Revision.V0: + return s + return f'"{s}"' + + if dependency not in self._all_types: + raise ValueError(f"Dependency [{dependency}] is not defined in types.") + + encoded_params = [] + for param in self._all_types[dependency]: + target_type = ( + param.contains + if isinstance(param, EnumParameter) + and self.domain.resolved_revision == Revision.V1 + else param.type + ) + + if is_enum_variant_type(target_type): + type_str = _extract_enum_types(target_type) + type_str = f"({','.join([escape(x) for x in type_str])})" + + else: + type_str = escape(target_type) - return "".join([make_dependency_str(x) for x in types]) + encoded_params.append(f"{escape(param.name)}:{type_str}") + encoded_params = ",".join(encoded_params) + + return f"{escape(dependency)}({encoded_params})" + + return "".join([encode_dependency(x) for x in types]) def type_hash(self, type_name: str) -> int: """ @@ -114,7 +416,7 @@ def struct_hash(self, type_name: str, data: dict) -> int: :param data: Data defining the struct. :return: Hash of the struct. """ - return compute_hash_on_elements( + return self._hash_method.hash_many( [self.type_hash(type_name), *self._encode_data(type_name, data)] ) @@ -127,26 +429,113 @@ def message_hash(self, account_address: int) -> int: """ message = [ encode_shortstring("StarkNet Message"), - self.struct_hash("StarkNetDomain", cast(dict, self.domain)), + self.struct_hash(self.domain.separator_name, self.domain.to_dict()), account_address, self.struct_hash(self.primary_type, self.message), ] - return compute_hash_on_elements(message) + return self._hash_method.hash_many(message) + + def _prepare_merkle_tree_root(self, value: List, context: TypeContext) -> int: + merkle_tree_type = self._get_merkle_tree_leaves_type(context) + struct_hashes = [ + self._encode_value(merkle_tree_type, struct) for struct in value + ] + + return MerkleTree(struct_hashes, self._hash_method).root_hash + + def _get_merkle_tree_leaves_type(self, context: TypeContext) -> str: + target_type = self._resolve_type(context) + + if not isinstance(target_type, MerkleTreeParameter): + raise ValueError("Target type is not a merkletree type.") + + return target_type.contains + + def _resolve_type(self, context: TypeContext) -> Parameter: + parent, key = context.parent, context.key + + if parent not in self._all_types: + raise ValueError(f"Parent {parent} is not defined in types.") + + parent_type = self._all_types[parent] + + target_type = next((item for item in parent_type if item.name == key), None) + if target_type is None: + raise ValueError( + f"Key {key} is not defined in type {parent} or multiple definitions are present." + ) + + if not isinstance(target_type, (EnumParameter, MerkleTreeParameter)): + raise ValueError("Target type is not an enum or merkletree type.") + return target_type + + def _encode_enum(self, value: dict, context: TypeContext): + if len(value.keys()) != 1: + raise ValueError( + f"'{BasicType.ENUM.name}' value must contain a single variant." + ) + + variant_name, variant_data = next(iter(value.items())) + variants = self._get_enum_variants(context) + variant_definition = next( + (item for item in variants if item.name == variant_name), None + ) + + if variant_definition is None: + raise ValueError( + f"Variant [{variant_name}] is not defined in '${BasicType.ENUM.name}' " + f"type [{context.key}] or multiple definitions are present." + ) + encoded_subtypes = [] + extracted_enum_types = _extract_enum_types(variant_definition.type) -def get_hex(value: Union[int, str]) -> str: + for i, subtype in enumerate(extracted_enum_types): + subtype_data = variant_data[i] + encoded_subtypes.append(self._encode_value(subtype, subtype_data)) + + variant_index = variants.index(variant_definition) + return self._hash_method.hash_many([variant_index, *encoded_subtypes]) + + def _get_enum_variants(self, context: TypeContext) -> List[Parameter]: + enum_type = self._resolve_type(context) + if not isinstance(enum_type, EnumParameter): + raise ValueError(f"Type [{context.key}] is not an enum.") + if enum_type.contains not in self._all_types: + raise ValueError(f"Type [{enum_type.contains}] is not defined in types") + + return self._all_types[enum_type.contains] + + def _encode_long_string(self, value: str) -> int: + byte_array_serializer = ByteArraySerializer() + serialized_values = byte_array_serializer.serialize(value) + return self._hash_method.hash_many(serialized_values) + + +def _extract_enum_types(value: str) -> List[str]: + if not is_enum_variant_type(value): + raise ValueError(f"Type [{value}] is not an enum.") + + value = value[1:-1] + if not value: + return [] + + return value.split(",") + + +def parse_felt(value: Union[int, str]) -> int: if isinstance(value, int): - return hex(value) - if value[:2] == "0x": return value + if value.startswith("0x"): + return int(value, 16) if value.isnumeric(): - return hex(int(value)) - return hex(encode_shortstring(value)) + return int(value) + return encode_shortstring(value) def is_pointer(value: str) -> bool: - return len(value) > 0 and value[-1] == "*" + return value.endswith("*") def strip_pointer(value: str) -> str: @@ -155,6 +544,116 @@ def strip_pointer(value: str) -> str: return value +def is_enum_variant_type(value: str) -> bool: + return value.startswith("(") and value.endswith(")") + + +def prepare_selector(name: str) -> int: + try: + return int(_to_rpc_felt(name), 16) + except ValueError: + return get_selector_from_name(name) + + +def encode_bool(value: Union[bool, str, int]) -> int: + if isinstance(value, bool): + return 1 if value else 0 + if isinstance(value, int) and value in (0, 1): + return value + if isinstance(value, str) and value in ("0", "1"): + return int(value) + if isinstance(value, str) and value in ("false", "true"): + return 0 if value == "false" else 1 + if isinstance(value, str) and value in ("0x0", "0x1"): + return int(value, 16) + raise ValueError(f"Expected boolean value, got [{value}].") + + +def is_digit_string(s: str, signed=False) -> bool: + if signed: + return bool(re.fullmatch(r"-?\d+", s)) + return bool(re.fullmatch(r"\d+", s)) + + +def encode_u128(value: Union[str, int]) -> int: + def is_in_range(n: int): + return 0 <= n < 2**128 + + if isinstance(value, str) and value.startswith("0x"): + int_value = int(value, 16) + elif isinstance(value, str) and is_digit_string(value): + int_value = int(value) + elif isinstance(value, int): + int_value = value + else: + raise ValueError(f"Value [{value}] is not a valid number.") + + if is_in_range(int_value): + return int_value + raise ValueError(f"Value [{value}] is out of range for '{BasicType.U128}'.") + + +def encode_i128(value: Union[str, int]) -> int: + def is_in_range(n: int): + return (n < 2**127) or (n >= (FIELD_PRIME - (2**127))) + + if isinstance(value, str) and value.startswith("0x"): + int_value = int(value, 16) + elif isinstance(value, str) and is_digit_string(value, True): + int_value = int(value) + elif isinstance(value, int): + int_value = value + else: + raise ValueError(f"Value [{value}] is not a valid number.") + + if abs(int_value) >= FIELD_PRIME: + raise ValueError( + f"Values outside the range (-FIELD_PRIME, FIELD_PRIME) are not allowed, [{value}] given." + ) + int_value %= FIELD_PRIME + + if is_in_range(int_value): + return int_value + raise ValueError(f"Value [{value}] is out of range for '{BasicType.I128}'.") + + +def _get_basic_type_names(revision: Revision) -> List[str]: + basic_types_v0 = [ + BasicType.FELT, + BasicType.SELECTOR, + BasicType.MERKLE_TREE, + BasicType.STRING, + BasicType.BOOL, + ] + + basic_types_v1 = list(BasicType) + + basic_types = basic_types_v0 if revision == Revision.V0 else basic_types_v1 + return [basic_type.value for basic_type in basic_types] + + +def _get_preset_types( + revision: Revision, +) -> Dict[str, List[StandardParameter]]: + if revision == Revision.V0: + return {} + + return { + PresetType.U256.value: [ + StandardParameter(name="low", type="u128"), + StandardParameter(name="high", type="u128"), + ], + PresetType.TOKEN_AMOUNT.value: [ + StandardParameter(name="token_address", type="ContractAddress"), + StandardParameter(name="amount", type="u256"), + ], + PresetType.NFT_ID.value: [ + StandardParameter(name="collection_address", type="ContractAddress"), + StandardParameter(name="token_id", type="u256"), + ], + } + + # pylint: disable=unused-argument # pylint: disable=no-self-use @@ -162,10 +661,35 @@ def strip_pointer(value: str) -> str: class ParameterSchema(Schema): name = fields.String(data_key="name", required=True) type = fields.String(data_key="type", required=True) + contains = fields.String(data_key="contains", required=False) @post_load def make_dataclass(self, data, **kwargs) -> Parameter: - return Parameter(**data) + type_val = data["type"] + + if type_val == BasicType.MERKLE_TREE.value: + return MerkleTreeParameter(name=data["name"], contains=data["contains"]) + + if type_val == BasicType.ENUM.value: + return EnumParameter(name=data["name"], contains=data["contains"]) + + return StandardParameter(name=data["name"], type=type_val) + + +class DomainSchema(Schema): + name = fields.String(data_key="name", required=True) + version = fields.String(data_key="version", required=True) + chain_id = fields.String(attribute="chain_id", data_key="chainId", required=True) + revision = RevisionField(data_key="revision", required=False) + + @post_load + def make_dataclass(self, data, **kwargs) -> Domain: + return Domain( + name=data["name"], + version=data["version"], + chain_id=data["chain_id"], + revision=data.get("revision"), + ) class TypedDataSchema(Schema): @@ -175,9 +699,14 @@ class TypedDataSchema(Schema): values=fields.List(fields.Nested(ParameterSchema())), ) primary_type = fields.String(data_key="primaryType", required=True) - domain = fields.Dict(data_key="domain", required=True) + domain = fields.Nested(DomainSchema, required=True) message = fields.Dict(data_key="message", required=True) @post_load def make_dataclass(self, data, **kwargs) -> TypedData: - return TypedData(**data) + return TypedData( + types=data["types"], + primary_type=data["primary_type"], + domain=data["domain"], + message=data["message"], + ) diff --git a/starknet_py/utils/typed_data_test.py b/starknet_py/utils/typed_data_test.py index a3d6a98e4..9aefbeb23 100644 --- a/starknet_py/utils/typed_data_test.py +++ b/starknet_py/utils/typed_data_test.py @@ -2,17 +2,42 @@ # fmt: off import json +from enum import Enum from pathlib import Path +from typing import Dict, List, Union import pytest +from starknet_py.net.models.typed_data import Revision from starknet_py.tests.e2e.fixtures.constants import TYPED_DATA_DIR -from starknet_py.utils.typed_data import TypedData, get_hex +from starknet_py.utils.typed_data import ( + BasicType, + Domain, + Parameter, + PresetType, + StandardParameter, + TypedData, + encode_bool, + encode_i128, + encode_u128, + parse_felt, +) + + +class CasesRev0(Enum): + TD = "typed_data_rev_0_example.json" + TD_STRING = "typed_data_rev_0_long_string_example.json" + TD_FELT_ARR = "typed_data_rev_0_felt_array_example.json" + TD_STRUCT_ARR = "typed_data_rev_0_struct_array_example.json" + TD_STRUCT_MERKLE_TREE = "typed_data_rev_0_struct_merkletree_example.json" -TD = "typed_data_example.json" -TD_STRING = "typed_data_long_string_example.json" -TD_FELT_ARR = "typed_data_felt_array_example.json" -TD_STRUCT_ARR = "typed_data_struct_array_example.json" + +class CasesRev1(Enum): + TD = "typed_data_rev_1_example.json" + TD_FELT_MERKLE_TREE = "typed_data_rev_1_felt_merkletree_example.json" + TD_BASIC_TYPES = "typed_data_rev_1_basic_types_example.json" + TD_PRESET_TYPES = "typed_data_rev_1_preset_types_example.json" + TD_ENUM = "typed_data_rev_1_enum_example.json" def load_typed_data(file_name: str) -> TypedData: @@ -31,21 +56,32 @@ def load_typed_data(file_name: str) -> TypedData: "value, result", [(123, "0x7b"), ("123", "0x7b"), ("0x7b", "0x7b"), ("short_string", "0x73686f72745f737472696e67")], ) -def test_get_hex(value, result): - assert get_hex(value) == result +def test_parse_felt(value, result): + assert parse_felt(value) == int(result, 16) @pytest.mark.parametrize( "example, type_name, encoded_type", [ - (TD, "Mail", "Mail(from:Person,to:Person,contents:felt)Person(name:felt,wallet:felt)"), - (TD_FELT_ARR, "Mail", "Mail(from:Person,to:Person,felts_len:felt,felts:felt*)Person(name:felt,wallet:felt)"), - (TD_STRING, "Mail", "Mail(from:Person,to:Person,contents:String)Person(name:felt,wallet:felt)String(len:felt,data:felt*)"), - (TD_STRUCT_ARR, "Mail", "Mail(from:Person,to:Person,posts_len:felt,posts:Post*)Person(name:felt,wallet:felt)Post(title:felt,content:felt)") + (CasesRev0.TD, "Mail", "Mail(from:Person,to:Person,contents:felt)Person(name:felt,wallet:felt)"), + (CasesRev0.TD_STRUCT_MERKLE_TREE, "Session", "Session(key:felt,expires:felt,root:merkletree)"), + (CasesRev0.TD_FELT_ARR, "Mail", + "Mail(from:Person,to:Person,felts_len:felt,felts:felt*)Person(name:felt,wallet:felt)"), + (CasesRev0.TD_STRING, "Mail", + "Mail(from:Person,to:Person,contents:String)Person(name:felt,wallet:felt)String(len:felt,data:felt*)"), + (CasesRev0.TD_STRUCT_ARR, "Mail", + "Mail(from:Person,to:Person,posts_len:felt,posts:Post*)Person(name:felt,wallet:felt)Post(title:felt,content:felt)"), + (CasesRev1.TD, "Mail", + """"Mail"("from":"Person","to":"Person","contents":"felt")"Person"("name":"felt","wallet":"felt")"""), + (CasesRev1.TD_BASIC_TYPES, "Example", + """"Example"("n0":"felt","n1":"bool","n2":"string","n3":"selector","n4":"u128","n5":"i128","n6":"ContractAddress","n7":"ClassHash","n8":"timestamp","n9":"shortstring")"""), + (CasesRev1.TD_ENUM, "Example", + """"Example"("someEnum":"MyEnum")"MyEnum"("Variant 1":(),"Variant 2":("u128","u128*"),"Variant 3":("u128"))"""), + (CasesRev1.TD_FELT_MERKLE_TREE, "Example", """"Example"("value":"felt","root":"merkletree")""") ], ) def test_encode_type(example, type_name, encoded_type): - typed_data = load_typed_data(example) + typed_data = load_typed_data(example.value) res = typed_data._encode_type(type_name) # pylint: disable=protected-access assert res == encoded_type @@ -54,18 +90,32 @@ def test_encode_type(example, type_name, encoded_type): @pytest.mark.parametrize( "example, type_name, type_hash", [ - (TD, "StarkNetDomain", "0x1bfc207425a47a5dfa1a50a4f5241203f50624ca5fdf5e18755765416b8e288"), - (TD, "Person", "0x2896dbe4b96a67110f454c01e5336edc5bbc3635537efd690f122f4809cc855"), - (TD, "Mail", "0x13d89452df9512bf750f539ba3001b945576243288137ddb6c788457d4b2f79"), - (TD_STRING, "String", "0x1933fe9de7e181d64298eecb44fc43b4cec344faa26968646761b7278df4ae2"), - (TD_STRING, "Mail", "0x1ac6f84a5d41cee97febb378ddabbe1390d4e8036df8f89dee194e613411b09"), - (TD_FELT_ARR, "Mail", "0x5b03497592c0d1fe2f3667b63099761714a895c7df96ec90a85d17bfc7a7a0"), - (TD_STRUCT_ARR, "Post", "0x1d71e69bf476486b43cdcfaf5a85c00bb2d954c042b281040e513080388356d"), - (TD_STRUCT_ARR, "Mail", "0x873b878e35e258fc99e3085d5aaad3a81a0c821f189c08b30def2cde55ff27"), + (CasesRev0.TD, "StarkNetDomain", "0x1bfc207425a47a5dfa1a50a4f5241203f50624ca5fdf5e18755765416b8e288"), + (CasesRev0.TD, "Person", "0x2896dbe4b96a67110f454c01e5336edc5bbc3635537efd690f122f4809cc855"), + (CasesRev0.TD, "Mail", "0x13d89452df9512bf750f539ba3001b945576243288137ddb6c788457d4b2f79"), + (CasesRev0.TD_STRING, "String", "0x1933fe9de7e181d64298eecb44fc43b4cec344faa26968646761b7278df4ae2"), + (CasesRev0.TD_STRING, "Mail", "0x1ac6f84a5d41cee97febb378ddabbe1390d4e8036df8f89dee194e613411b09"), + (CasesRev0.TD_FELT_ARR, "Mail", "0x5b03497592c0d1fe2f3667b63099761714a895c7df96ec90a85d17bfc7a7a0"), + (CasesRev0.TD_STRUCT_ARR, "Post", "0x1d71e69bf476486b43cdcfaf5a85c00bb2d954c042b281040e513080388356d"), + (CasesRev0.TD_STRUCT_ARR, "Mail", "0x873b878e35e258fc99e3085d5aaad3a81a0c821f189c08b30def2cde55ff27"), + (CasesRev0.TD_STRUCT_MERKLE_TREE, "Session", + "0x1aa0e1c56b45cf06a54534fa1707c54e520b842feb21d03b7deddb6f1e340c"), + (CasesRev0.TD_STRUCT_MERKLE_TREE, "Policy", + "0x2f0026e78543f036f33e26a8f5891b88c58dc1e20cbbfaf0bb53274da6fa568"), + (CasesRev1.TD, "StarknetDomain", "0x1ff2f602e42168014d405a94f75e8a93d640751d71d16311266e140d8b0a210"), + (CasesRev1.TD, "Person", "0x30f7aa21b8d67cb04c30f962dd29b95ab320cb929c07d1605f5ace304dadf34"), + (CasesRev1.TD, "Mail", "0x560430bf7a02939edd1a5c104e7b7a55bbab9f35928b1cf5c7c97de3a907bd"), + ( + CasesRev1.TD_BASIC_TYPES, "Example", + "0x1f94cd0be8b4097a41486170fdf09a4cd23aefbc74bb2344718562994c2c111"), + (CasesRev1.TD_PRESET_TYPES, "Example", "0x1a25a8bb84b761090b1fadaebe762c4b679b0d8883d2bedda695ea340839a55"), + (CasesRev1.TD_ENUM, "Example", "0x380a54d417fb58913b904675d94a8a62e2abc3467f4b5439de0fd65fafdd1a8"), + (CasesRev1.TD_FELT_MERKLE_TREE, "Example", + "0x160b9c0e8a7c561f9c5d9e3cc2990a1b4d26e94aa319e9eb53e163cd06c71be"), ], ) def test_type_hash(example, type_name, type_hash): - typed_data = load_typed_data(example) + typed_data = load_typed_data(example.value) res = typed_data.type_hash(type_name) assert hex(res) == type_hash @@ -73,29 +123,323 @@ def test_type_hash(example, type_name, type_hash): @pytest.mark.parametrize( "example, type_name, attr_name, struct_hash", [ - (TD, "StarkNetDomain", "domain", "0x54833b121883a3e3aebff48ec08a962f5742e5f7b973469c1f8f4f55d470b07"), - (TD, "Mail", "message", "0x4758f1ed5e7503120c228cbcaba626f61514559e9ef5ed653b0b885e0f38aec"), - (TD_STRING, "Mail", "message", "0x1d16b9b96f7cb7a55950b26cc8e01daa465f78938c47a09d5a066ca58f9936f"), - (TD_FELT_ARR, "Mail", "message", "0x26186b02dddb59bf12114f771971b818f48fad83c373534abebaaa39b63a7ce"), - (TD_STRUCT_ARR, "Mail", "message", "0x5650ec45a42c4776a182159b9d33118a46860a6e6639bb8166ff71f3c41eaef") + (CasesRev0.TD, "StarkNetDomain", "domain", + "0x54833b121883a3e3aebff48ec08a962f5742e5f7b973469c1f8f4f55d470b07"), + (CasesRev0.TD, "Mail", "message", "0x4758f1ed5e7503120c228cbcaba626f61514559e9ef5ed653b0b885e0f38aec"), + (CasesRev0.TD_STRING, "Mail", "message", + "0x1d16b9b96f7cb7a55950b26cc8e01daa465f78938c47a09d5a066ca58f9936f"), + (CasesRev0.TD_FELT_ARR, "Mail", "message", + "0x26186b02dddb59bf12114f771971b818f48fad83c373534abebaaa39b63a7ce"), + (CasesRev0.TD_STRUCT_ARR, "Mail", "message", + "0x5650ec45a42c4776a182159b9d33118a46860a6e6639bb8166ff71f3c41eaef"), + (CasesRev0.TD_STRUCT_MERKLE_TREE, "Session", "message", + "0x73602062421caf6ad2e942253debfad4584bff58930981364dcd378021defe8"), + (CasesRev1.TD, "StarknetDomain", "domain", + "0x555f72e550b308e50c1a4f8611483a174026c982a9893a05c185eeb85399657"), + (CasesRev1.TD_PRESET_TYPES, "Example", "message", + "0x74fba3f77f8a6111a9315bac313bf75ecfa46d1234e0fda60312fb6a6517667"), + (CasesRev1.TD_ENUM, "Example", "message", + "0x3d4384ff5cec32b86462e89f5a803b55ff0048c4f5a10ba9d6cd381317d9c3"), + (CasesRev1.TD_FELT_MERKLE_TREE, "Example", "message", + "0x40ef40c56c0469799a916f0b7e3bc4f1bbf28bf659c53fb8c5ee4d8d1b4f5f0") ], ) def test_struct_hash(example, type_name, attr_name, struct_hash): - typed_data = load_typed_data(example) - res = typed_data.struct_hash(type_name, getattr(typed_data, attr_name)) + typed_data = load_typed_data(example.value) + data = getattr(typed_data, attr_name) + if isinstance(data, Domain): + data = data.to_dict() + res = typed_data.struct_hash(type_name, data) assert hex(res) == struct_hash @pytest.mark.parametrize( "example, account_address, msg_hash", [ - (TD, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", "0x6fcff244f63e38b9d88b9e3378d44757710d1b244282b435cb472053c8d78d0"), - (TD_STRING, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", "0x691b977ee0ee645647336f01d724274731f544ad0d626b078033d2541ee641d"), - (TD_FELT_ARR, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", "0x30ab43ef724b08c3b0a9bbe425e47c6173470be75d1d4c55fd5bf9309896bce"), - (TD_STRUCT_ARR, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", "0x5914ed2764eca2e6a41eb037feefd3d2e33d9af6225a9e7fe31ac943ff712c") + (CasesRev0.TD, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x6fcff244f63e38b9d88b9e3378d44757710d1b244282b435cb472053c8d78d0"), + (CasesRev0.TD_STRING, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x691b977ee0ee645647336f01d724274731f544ad0d626b078033d2541ee641d"), + (CasesRev0.TD_FELT_ARR, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x30ab43ef724b08c3b0a9bbe425e47c6173470be75d1d4c55fd5bf9309896bce"), + (CasesRev0.TD_STRUCT_ARR, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x5914ed2764eca2e6a41eb037feefd3d2e33d9af6225a9e7fe31ac943ff712c"), + (CasesRev0.TD_STRUCT_MERKLE_TREE, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x5d28fa1b31f92e63022f7d85271606e52bed89c046c925f16b09e644dc99794"), + (CasesRev1.TD, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x7f6e8c3d8965b5535f5cc68f837c04e3bbe568535b71aa6c621ddfb188932b8"), + (CasesRev1.TD_BASIC_TYPES, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x2d80b87b8bc32068247c779b2ef0f15f65c9c449325e44a9df480fb01eb43ec"), + (CasesRev1.TD_PRESET_TYPES, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x185b339d5c566a883561a88fb36da301051e2c0225deb325c91bb7aa2f3473a"), + (CasesRev1.TD_ENUM, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x3df10475ad5a8f49db4345a04a5b09164d2e24b09f6e1e236bc1ccd87627cc"), + (CasesRev1.TD_FELT_MERKLE_TREE, "0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826", + "0x4f706783e0d7d0e61433d41343a248a213e9ab341d50ba978dfc055f26484c9") ], ) def test_message_hash(example, account_address, msg_hash): - typed_data = load_typed_data(example) + typed_data = load_typed_data(example.value) res = typed_data.message_hash(int(account_address, 16)) assert hex(res) == msg_hash + + +domain_type_v0 = { + "StarkNetDomain": [ + StandardParameter(name="name", type="felt"), + StandardParameter(name="version", type="felt"), + StandardParameter(name="chainId", type="felt"), + ] +} + +domain_type_v1: Dict[str, List[Parameter]] = { + "StarknetDomain": [ + StandardParameter(name="name", type="shortstring"), + StandardParameter(name="version", type="shortstring"), + StandardParameter(name="chainId", type="shortstring"), + StandardParameter(name="revision", type="shortstring"), + ] +} + +domain_v0 = Domain( + name="DomainV0", + version="1", + chain_id=1234, +) + +domain_v1 = Domain( + name="DomainV1", + version="1", + chain_id="1234", + revision=Revision.V1, +) + + +def _make_typed_data(included_type: str, revision: Revision): + domain_type, domain = (domain_type_v0, domain_v0) if revision == Revision.V0 else ( + domain_type_v1, domain_v1) + + types = {**domain_type, included_type: []} + message = {included_type: 1} + + return TypedData( + types=types, + primary_type=included_type, + domain=domain, + message=message, + ) + + +@pytest.mark.parametrize( + "included_type, revision", + [ + ("", Revision.V1), + ("myType*", Revision.V1), + ("(myType)", Revision.V1), + ("()", Revision.V1), + ], +) +def test_invalid_type_names(included_type: str, revision: Revision): + with pytest.raises(ValueError): + _make_typed_data(included_type, revision) + + +@pytest.mark.parametrize( + "included_type, revision", + [ + (BasicType.FELT, Revision.V0), + (BasicType.STRING, Revision.V0), + (BasicType.SELECTOR, Revision.V0), + (BasicType.MERKLE_TREE, Revision.V0), + (BasicType.BOOL, Revision.V0), + (BasicType.FELT, Revision.V1), + (BasicType.STRING, Revision.V1), + (BasicType.SELECTOR, Revision.V1), + (BasicType.MERKLE_TREE, Revision.V1), + (BasicType.BOOL, Revision.V1), + (BasicType.SHORT_STRING, Revision.V1), + (BasicType.U128, Revision.V1), + (BasicType.I128, Revision.V1), + (BasicType.TIMESTAMP, Revision.V1), + (BasicType.CONTRACT_ADDRESS, Revision.V1), + (BasicType.CLASS_HASH, Revision.V1), + (BasicType.ENUM, Revision.V1) + ], +) +def test_basic_types_redefinition(included_type: BasicType, revision: Revision): + with pytest.raises(ValueError, match=fr"Types must not contain basic types. \[{included_type.value}\] was found."): + _make_typed_data(included_type.value, revision) + + +@pytest.mark.parametrize( + "included_type, revision", + [ + (PresetType.U256, Revision.V1), + (PresetType.TOKEN_AMOUNT, Revision.V1), + (PresetType.NFT_ID, Revision.V1), + ], +) +def test_preset_types_redefinition(included_type: PresetType, revision: Revision): + with pytest.raises(ValueError, match=fr"Types must not contain preset types. \[{included_type.value}\] was found."): + _make_typed_data(included_type.value, revision) + + +def test_custom_type_definition(): + _make_typed_data("myType", Revision.V0) + + +@pytest.mark.parametrize( + "revision", + list(Revision), +) +def test_missing_domain_type(revision: Revision): + domain = domain_v0 if revision == Revision.V0 else domain_v1 + + with pytest.raises(ValueError, match=f"Types must contain '{domain.separator_name}'."): + TypedData( + types={}, + primary_type="felt", + domain=domain, + message={}, + ) + + +def test_dangling_type(): + with pytest.raises(ValueError, match=r"Dangling types are not allowed. Unreferenced type \[dangling\] was found."): + TypedData( + types={ + **domain_type_v1, + "dangling": [], + "mytype": [] + }, + primary_type="mytype", + domain=domain_v1, + message={"mytype": 1}, + ) + + +def test_missing_dependency(): + typed_data = TypedData( + types={ + **domain_type_v1, + "house": [StandardParameter(name="fridge", type="ice cream")] + }, + primary_type="house", + domain=domain_v1, + message={"fridge": 1}, + ) + + with pytest.raises(ValueError, match=r"Type \[ice cream\] is not defined in types."): + typed_data.struct_hash("house", {"fridge": 1}) + + +@pytest.mark.parametrize( + "value, expected", + [ + (True, 1), + (False, 0), + ("true", 1), + ("false", 0), + ("0x1", 1), + ("0x0", 0), + ("1", 1), + ("0", 0), + (1, 1), + (0, 0) + + ] +) +def test_encode_bool(value: Union[bool, str, int], expected: int): + assert encode_bool(value) == expected + + +@pytest.mark.parametrize( + "value", + [ + -2, + 2, + "-2", + "2", + "0x123", + "anyvalue", + ] +) +def test_encode_invalid_bool(value: Union[bool, str, int]): + with pytest.raises(ValueError, match=fr"Expected boolean value, got \[{value}\]."): + encode_bool(value) + + +@pytest.mark.parametrize( + "value, expected", + [ + (0, 0), + (1, 1), + (1000000, 1000000), + ("0x0", 0), + ("0x1", 1), + ("0x64", 100), + (2 ** 128 - 1, 2 ** 128 - 1), + ] +) +def test_encode_u128(value: Union[str, int], expected: str): + assert encode_u128(value) == expected + + +@pytest.mark.parametrize( + "value", + [ + -1, + "-1", + 1.23, + -1.23, + "1.23", + "-1.23", + "example", + "0xwrong", + 2 ** 128, + hex(2 ** 128), + ] +) +def test_encode_invalid_u128(value: Union[str, int]): + with pytest.raises(ValueError): + encode_u128(value) + + +@pytest.mark.parametrize( + "value, expected", + [ + (0, 0), + (1, 1), + (1000000, 1000000), + ("0x0", 0), + ("0x1", 1), + ("0x64", 100), + (2 ** 127 - 1, 2 ** 127 - 1), + (-1, 3618502788666131213697322783095070105623107215331596699973092056135872020480), + (-1000000, 3618502788666131213697322783095070105623107215331596699973092056135871020481), + (-(2 ** 127), 3618502788666131213697322783095070105452966031871127468241404752419987914753), + ] +) +def test_encode_i128(value: Union[str, int], expected: str): + assert encode_i128(value) == expected + + +@pytest.mark.parametrize( + "value", + [ + "example", + "0xwrong", + 1.23, + -1.23, + "1.23", + "-1.23", + -2 ** 127 - 1, + 2 ** 127, + str(-2 ** 127 - 1), + str(2 ** 127), + + ] +) +def test_encode_invalid_i128(value: Union[str, int]): + with pytest.raises(ValueError): + encode_i128(value)