diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e76c83db..97b38ad8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,7 +15,6 @@ jobs: matrix: package: - "starknet-curve" - - "starknet-crypto-codegen" - "starknet-crypto" - "starknet-core" - "starknet-providers" diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 00000000..0069c07a --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,27 @@ +on: + push: + branches: + - "master" + pull_request: + +name: "Build documentation" + +jobs: + build-doc: + name: "Build documentation" + runs-on: "ubuntu-latest" + + steps: + - name: "Checkout source code" + uses: "actions/checkout@v3" + + - name: "Setup stable toolchain" + uses: "actions-rs/toolchain@v1" + with: + toolchain: "stable" + profile: "minimal" + override: true + + - name: "Build docs" + run: | + RUSTDOCFLAGS="-D warnings -A rustdoc::missing-crate-level-docs" cargo doc --all diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 1a8df083..e96896c7 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -81,7 +81,7 @@ jobs: - name: "Set allowed lints" run: | if [ "${{ matrix.toolchain }}" == "nightly" ]; then - echo "ALLOWED=-A non_local_definitions" >> $GITHUB_ENV + echo "ALLOWED=-A non_local_definitions -A clippy::too_long_first_doc_paragraph -A clippy::needless_return" >> $GITHUB_ENV else echo "ALLOWED=" >> $GITHUB_ENV fi diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4f93f2c2..62c058ca 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-11] + os: [ubuntu-latest, macos-13, macos-14] toolchain: [stable, nightly] steps: @@ -25,6 +25,10 @@ jobs: profile: minimal override: true + - name: Test starknet-crypto with pedersen_no_lookup + run: | + cargo test -p starknet-crypto --features pedersen_no_lookup + - name: Run cargo tests uses: nick-fields/retry@v2 with: @@ -53,6 +57,10 @@ jobs: profile: minimal override: true + - name: Test starknet-crypto with pedersen_no_lookup + run: | + cargo test -p starknet-crypto --features pedersen_no_lookup + - name: Run cargo tests uses: nick-fields/retry@v2 with: diff --git a/.github/workflows/udeps.yml b/.github/workflows/udeps.yml deleted file mode 100644 index 203d0c15..00000000 --- a/.github/workflows/udeps.yml +++ /dev/null @@ -1,35 +0,0 @@ -on: - push: - branches: - - master - pull_request: - -name: Unused deps -jobs: - udeps: - runs-on: ubuntu-latest - steps: - - name: Checkout source code - uses: actions/checkout@v2 - - - name: Setup nightly toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - override: true - - - uses: Swatinem/rust-cache@v1 - with: - cache-on-failure: true - - - name: Install udeps - uses: actions-rs/cargo@v1 - with: - command: install - args: cargo-udeps - - - name: Run udeps - uses: actions-rs/cargo@v1 - with: - command: udeps diff --git a/Cargo.lock b/Cargo.lock index 2ab904b9..f2df0c1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,30 +4,45 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -45,19 +60,19 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] @@ -73,35 +88,34 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.0.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -112,15 +126,15 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.21.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -134,6 +148,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -142,9 +165,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" @@ -179,9 +202,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -197,9 +220,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cast" @@ -209,9 +232,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.99" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -221,22 +247,22 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", "iana-time-zone", - "num-integer", "num-traits", "serde", - "winapi", + "windows-targets 0.52.6", ] [[package]] name = "ciborium" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -245,15 +271,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -271,13 +297,13 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex", - "indexmap", + "indexmap 1.9.3", "textwrap", ] @@ -322,7 +348,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b3aeeec621f4daec552e9d28befd58020a78cfc364827d06a753e8bc13c6c4b" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", "bech32", "bs58", "const-hex", @@ -337,8 +363,9 @@ dependencies = [ [[package]] name = "coins-ledger" -version = "0.11.1" -source = "git+https://github.com/xJonathanLEI/coins?rev=0e3be5db0b18b683433de6b666556b99c726e785#0e3be5db0b18b683433de6b666556b99c726e785" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9bc0994d0aa0f4ade5f3a9baf4a8d936f250278c85a1124b401860454246ab" dependencies = [ "async-trait", "byteorder", @@ -386,26 +413,36 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -452,9 +489,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-bigint" -version = "0.5.1" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2538c4e68e52548bacb3e83ac549f903d44f011ac9d5abb5e132e67d0808f7" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", @@ -483,9 +520,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.94" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" dependencies = [ "cc", "cxxbridge-flags", @@ -495,9 +532,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.94" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" dependencies = [ "cc", "codespan-reporting", @@ -505,31 +542,31 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "cxxbridge-flags" -version = "1.0.94" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" [[package]] name = "cxxbridge-macro" -version = "1.0.94" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "darling" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -537,27 +574,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -570,6 +607,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "digest" version = "0.10.7" @@ -598,9 +645,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "elliptic-curve" @@ -623,13 +670,19 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "eth-keystore" version = "0.5.0" @@ -703,12 +756,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide", ] [[package]] @@ -719,9 +772,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -734,36 +787,36 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-task", @@ -784,9 +837,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -797,9 +850,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "group" @@ -814,9 +867,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.18" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -824,7 +877,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.5.0", "slab", "tokio", "tokio-util", @@ -833,9 +886,13 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "hashbrown" @@ -843,6 +900,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -854,12 +917,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -896,9 +956,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -907,9 +967,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -918,21 +978,21 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -945,7 +1005,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", @@ -954,10 +1014,11 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ + "futures-util", "http", "hyper", "rustls", @@ -967,26 +1028,25 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -997,9 +1057,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1050,7 +1110,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", "serde", ] @@ -1065,9 +1136,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "itertools" @@ -1080,15 +1151,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1116,23 +1187,39 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "lambdaworks-crypto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb5d4f22241504f7c7b8d2c3a7d7835d7c07117f10bff2a7d96a9ef6ef217c3" +dependencies = [ + "lambdaworks-math", + "serde", + "sha2", + "sha3", +] + [[package]] name = "lambdaworks-math" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "358e172628e713b80a530a59654154bfc45783a6ed70ea284839800cebdf8f97" +dependencies = [ + "serde", + "serde_json", +] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" @@ -1154,18 +1241,18 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" dependencies = [ "cc", ] [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1173,15 +1260,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" @@ -1199,32 +1286,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "miniz_oxide" -version = "0.6.2" +name = "minicov" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" dependencies = [ - "adler", + "cc", + "walkdir", ] [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1242,22 +1331,26 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] @@ -1271,21 +1364,11 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - [[package]] name = "object" -version = "0.36.0" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -1298,21 +1381,21 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "parity-scale-codec" -version = "3.4.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec", "bitvec", @@ -1324,9 +1407,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1336,9 +1419,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1346,15 +1429,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.52.6", ] [[package]] @@ -1368,9 +1451,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -1400,17 +1483,26 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "primitive-types" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", @@ -1421,68 +1513,43 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "once_cell", "toml_edit", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax", "unarray", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1534,27 +1601,35 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ - "regex-syntax 0.7.1", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.7.1" +name = "regex-automata" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] name = "regex-syntax" @@ -1564,11 +1639,11 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.16" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -1590,6 +1665,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", "tokio-rustls", "tower-service", @@ -1613,17 +1690,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1669,30 +1746,40 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] name = "rustls-pemfile" -version = "1.0.2" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "base64 0.21.0", + "ring", + "untrusted", ] [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "salsa20" @@ -1720,15 +1807,15 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scratch" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" [[package]] name = "scrypt" @@ -1744,9 +1831,9 @@ dependencies = [ [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", @@ -1766,33 +1853,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1822,15 +1916,17 @@ dependencies = [ [[package]] name = "serde_with" -version = "2.3.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "chrono", "hex", - "indexmap", + "indexmap 1.9.3", + "indexmap 2.5.0", "serde", + "serde_derive", "serde_json", "serde_with_macros", "time", @@ -1838,14 +1934,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "2.3.2" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -1869,11 +1965,17 @@ dependencies = [ "keccak", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1890,28 +1992,18 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.9" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1925,9 +2017,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" @@ -1941,12 +2033,13 @@ dependencies = [ [[package]] name = "starknet" -version = "0.11.0" +version = "0.12.0" dependencies = [ "serde_json", "starknet-accounts", "starknet-contract", "starknet-core", + "starknet-core-derive", "starknet-crypto", "starknet-macros", "starknet-providers", @@ -1957,7 +2050,7 @@ dependencies = [ [[package]] name = "starknet-accounts" -version = "0.10.0" +version = "0.11.0" dependencies = [ "async-trait", "auto_impl", @@ -1974,7 +2067,7 @@ dependencies = [ [[package]] name = "starknet-contract" -version = "0.10.0" +version = "0.11.0" dependencies = [ "rand", "serde", @@ -1991,28 +2084,40 @@ dependencies = [ [[package]] name = "starknet-core" -version = "0.11.1" +version = "0.12.0" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", + "bincode", "criterion", "crypto-bigint", "flate2", "hex", "hex-literal", + "num-traits", "serde", "serde_json", "serde_json_pythonic", "serde_with", "sha3", "starknet-core", + "starknet-core-derive", "starknet-crypto", "starknet-types-core", "wasm-bindgen-test", ] +[[package]] +name = "starknet-core-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "starknet-crypto" -version = "0.7.0" +version = "0.7.3" dependencies = [ "criterion", "crypto-bigint", @@ -2026,25 +2131,15 @@ dependencies = [ "serde", "serde_json", "sha2", - "starknet-crypto-codegen", "starknet-curve", "starknet-types-core", "wasm-bindgen-test", "zeroize", ] -[[package]] -name = "starknet-crypto-codegen" -version = "0.4.0" -dependencies = [ - "starknet-curve", - "starknet-types-core", - "syn 2.0.63", -] - [[package]] name = "starknet-curve" -version = "0.5.0" +version = "0.5.1" dependencies = [ "starknet-types-core", ] @@ -2061,15 +2156,15 @@ dependencies = [ [[package]] name = "starknet-macros" -version = "0.2.0" +version = "0.2.1" dependencies = [ "starknet-core", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "starknet-providers" -version = "0.11.0" +version = "0.12.0" dependencies = [ "async-trait", "auto_impl", @@ -2090,7 +2185,7 @@ dependencies = [ [[package]] name = "starknet-signers" -version = "0.9.0" +version = "0.10.0" dependencies = [ "async-trait", "auto_impl", @@ -2100,6 +2195,7 @@ dependencies = [ "eth-keystore", "getrandom", "rand", + "semver", "starknet-core", "starknet-crypto", "thiserror", @@ -2108,10 +2204,11 @@ dependencies = [ [[package]] name = "starknet-types-core" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe29a53d28ff630e4c7827788f14b28f9386d27cb9d05186a5f2e73218c34677" +checksum = "9b889ee5734db8b3c8a6551135c16764bf4ce1ab4955fffbb2ac5b6706542b64" dependencies = [ + "lambdaworks-crypto", "lambdaworks-math", "num-bigint", "num-integer", @@ -2136,15 +2233,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2159,15 +2256,42 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.63" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -2176,46 +2300,49 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "time" -version = "0.3.20" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ + "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -2223,16 +2350,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -2257,9 +2385,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2272,89 +2400,85 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml_datetime" -version = "0.6.1" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.19.8" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239410c8609e8125456927e6707163a3b1fdb40561e4b803bc041f466ccfdc13" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ - "indexmap", + "indexmap 2.5.0", "toml_datetime", "winnow", ] [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2368,29 +2492,29 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "uint" @@ -2412,42 +2536,42 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.3.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -2472,15 +2596,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -2488,11 +2612,10 @@ dependencies = [ [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -2504,34 +2627,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -2541,9 +2665,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2551,31 +2675,32 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.63", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-bindgen-test" -version = "0.3.34" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" +checksum = "68497a05fb21143a08a7d24fc81763384a3072ee43c44e86aad1744d6adef9d9" dependencies = [ "console_error_panic_hook", "js-sys", + "minicov", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -2584,42 +2709,30 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.34" +version = "0.3.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" +checksum = "4b8220be1fa9e4c889b30fd207d4906657e7e90b12e0e6b0c8b8d8709f5de021" dependencies = [ "proc-macro2", "quote", + "syn 2.0.77", ] [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "winapi" @@ -2639,11 +2752,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2653,21 +2766,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.6", ] [[package]] @@ -2676,7 +2780,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] @@ -2685,203 +2789,156 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.6", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.4.1" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.10.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "winapi", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] @@ -2893,6 +2950,27 @@ dependencies = [ "tap", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index 1d9542a6..87f2d69c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet" -version = "0.11.0" +version = "0.12.0" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -17,6 +17,7 @@ exclude = [".github/**", "images/**"] [workspace] members = [ "starknet-core", + "starknet-core-derive", "starknet-providers", "starknet-contract", "starknet-crypto", @@ -24,7 +25,6 @@ members = [ "starknet-accounts", "starknet-macros", "starknet-curve", - "starknet-crypto-codegen", "examples/starknet-wasm", "examples/starknet-cxx/starknet-cxx", ] @@ -33,17 +33,18 @@ members = [ all-features = true [dependencies] -starknet-crypto = { version = "0.7.0", path = "./starknet-crypto" } -starknet-core = { version = "0.11.1", path = "./starknet-core", default-features = false } -starknet-providers = { version = "0.11.0", path = "./starknet-providers" } -starknet-contract = { version = "0.10.0", path = "./starknet-contract" } -starknet-signers = { version = "0.9.0", path = "./starknet-signers" } -starknet-accounts = { version = "0.10.0", path = "./starknet-accounts" } -starknet-macros = { version = "0.2.0", path = "./starknet-macros" } +starknet-crypto = { version = "0.7.3", path = "./starknet-crypto" } +starknet-core = { version = "0.12.0", path = "./starknet-core", default-features = false } +starknet-core-derive = { version = "0.1.0", path = "./starknet-core-derive", features = ["import_from_starknet"] } +starknet-providers = { version = "0.12.0", path = "./starknet-providers" } +starknet-contract = { version = "0.11.0", path = "./starknet-contract" } +starknet-signers = { version = "0.10.0", path = "./starknet-signers" } +starknet-accounts = { version = "0.11.0", path = "./starknet-accounts" } +starknet-macros = { version = "0.2.1", path = "./starknet-macros" } [dev-dependencies] serde_json = "1.0.74" -starknet-signers = { version = "0.9.0", path = "./starknet-signers", features = ["ledger"] } +starknet-signers = { version = "0.10.0", path = "./starknet-signers", features = ["ledger"] } tokio = { version = "1.15.0", features = ["full"] } url = "2.2.2" @@ -54,3 +55,78 @@ no_unknown_fields = [ "starknet-core/no_unknown_fields", "starknet-providers/no_unknown_fields", ] + +[workspace.lints] +rust.missing_debug_implementations = "warn" +rust.missing_docs = "allow" +rust.unreachable_pub = "allow" +rust.unused_must_use = "deny" +rust.rust_2018_idioms = { level = "deny", priority = -1 } +rustdoc.all = "warn" + +[workspace.lints.clippy] +# These are some of clippy's nursery (i.e., experimental) lints that we like. +# By default, nursery lints are allowed. Some of the lints below have made good +# suggestions which we fixed. The others didn't have any findings, so we can +# assume they don't have that many false positives. Let's enable them to +# prevent future problems. +branches_sharing_code = "warn" +clear_with_drain = "warn" +derive_partial_eq_without_eq = "warn" +empty_line_after_outer_attr = "warn" +equatable_if_let = "warn" +imprecise_flops = "warn" +iter_on_empty_collections = "warn" +iter_with_drain = "warn" +large_stack_frames = "warn" +manual_clamp = "warn" +mutex_integer = "warn" +needless_pass_by_ref_mut = "warn" +nonstandard_macro_braces = "warn" +or_fun_call = "warn" +path_buf_push_overwrite = "warn" +read_zero_byte_vec = "warn" +redundant_clone = "warn" +suboptimal_flops = "warn" +suspicious_operation_groupings = "warn" +trailing_empty_array = "warn" +trait_duplication_in_bounds = "warn" +transmute_undefined_repr = "warn" +trivial_regex = "warn" +tuple_array_conversions = "warn" +uninhabited_references = "warn" +unused_peekable = "warn" +unused_rounding = "warn" +useless_let_if_seq = "warn" +use_self = "warn" +missing_const_for_fn = "warn" +empty_line_after_doc_comments = "warn" +iter_on_single_items = "warn" +match_same_arms = "warn" +doc_markdown = "warn" +unnecessary_struct_initialization = "warn" +string_lit_as_bytes = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +type_repetition_in_bounds = "allow" +manual_string_new = "warn" +naive_bytecount = "warn" +needless_bitwise_bool = "warn" +zero_sized_map_values = "warn" +single_char_pattern = "warn" +needless_continue = "warn" + +# These are nursery lints which have findings. Allow them for now. Some are not +# quite mature enough for use in our codebase and some we don't really want. +# Explicitly listing should make it easier to fix in the future. +as_ptr_cast_mut = "allow" +cognitive_complexity = "allow" +collection_is_never_read = "allow" +debug_assert_with_mut_call = "allow" +fallible_impl_from = "allow" +future_not_send = "allow" +needless_collect = "allow" +non_send_fields_in_send_ty = "allow" +redundant_pub_crate = "allow" +significant_drop_in_scrutinee = "allow" +significant_drop_tightening = "allow" diff --git a/README.md b/README.md index d5639993..c6d4e2a8 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ starknet = { git = "https://github.com/xJonathanLEI/starknet-rs" } - [x] Sequencer gateway / feeder gateway client - [x] Full node JSON-RPC API client - [x] Smart contract deployment -- [x] Signer for using [IAccount](https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/openzeppelin/account/IAccount.cairo) account contracts +- [x] Signer for using [IAccount](https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.6.1/src/openzeppelin/account/IAccount.cairo) account contracts - [ ] Strongly-typed smart contract binding code generation from ABI - [x] Ledger hardware wallet support @@ -52,6 +52,7 @@ This workspace contains the following crates: - `starknet-accounts`: Types for handling Starknet account abstraction - `starknet-curve`: Starknet curve operations - `starknet-macros`: Useful macros for using the `starknet` crates +- `starknet-core-derive`: Derive macros for traits in `starknet-core` ## WebAssembly @@ -92,17 +93,23 @@ Examples can be found in the [examples folder](./examples): 6. [Query the latest block number with JSON-RPC](./examples/jsonrpc.rs) -7. [Call a contract view function](./examples/erc20_balance.rs) +7. [Encoding and decoding Cairo types](./examples/serde.rs) -8. [Deploy an Argent X account to a pre-funded address](./examples/deploy_argent_account.rs) +8. [Batched JSON-RPC requests](./examples/batch.rs) -9. [Inspect public key with Ledger](./examples/ledger_public_key.rs) +9. [Call a contract view function](./examples/erc20_balance.rs) -10. [Deploy an OpenZeppelin account with Ledger](./examples/deploy_account_with_ledger.rs) +10. [Deploy an Argent X account to a pre-funded address](./examples/deploy_argent_account.rs) -11. [Parsing a JSON-RPC request on the server side](./examples/parse_jsonrpc_request.rs) +11. [Inspect public key with Ledger](./examples/ledger_public_key.rs) -12. [Inspecting a erased provider-specific error type](./examples/downcast_provider_error.rs) +12. [Deploy an OpenZeppelin account with Ledger](./examples/deploy_account_with_ledger.rs) + +13. [Transfer ERC20 tokens with Ledger](./examples/transfer_with_ledger.rs) + +14. [Parsing a JSON-RPC request on the server side](./examples/parse_jsonrpc_request.rs) + +15. [Inspecting a erased provider-specific error type](./examples/downcast_provider_error.rs) ## License diff --git a/assets/CORE_DERIVE_README.md b/assets/CORE_DERIVE_README.md new file mode 120000 index 00000000..430edd5f --- /dev/null +++ b/assets/CORE_DERIVE_README.md @@ -0,0 +1 @@ +../starknet-core-derive/README.md \ No newline at end of file diff --git a/examples/batch.rs b/examples/batch.rs new file mode 100644 index 00000000..5dc1b78a --- /dev/null +++ b/examples/batch.rs @@ -0,0 +1,36 @@ +use starknet::providers::{ + jsonrpc::{HttpTransport, JsonRpcClient}, + Provider, ProviderRequestData, ProviderResponseData, Url, +}; +use starknet_core::types::{ + requests::{BlockNumberRequest, GetBlockTransactionCountRequest}, + BlockId, +}; + +#[tokio::main] +async fn main() { + let provider = JsonRpcClient::new(HttpTransport::new( + Url::parse("https://starknet-sepolia.public.blastapi.io/rpc/v0_7").unwrap(), + )); + + let responses = provider + .batch_requests([ + ProviderRequestData::BlockNumber(BlockNumberRequest), + ProviderRequestData::GetBlockTransactionCount(GetBlockTransactionCountRequest { + block_id: BlockId::Number(100), + }), + ]) + .await + .unwrap(); + + match (&responses[0], &responses[1]) { + ( + ProviderResponseData::BlockNumber(block_number), + ProviderResponseData::GetBlockTransactionCount(count), + ) => { + println!("The latest block is #{}", block_number); + println!("Block #100 has {} transactions", count); + } + _ => panic!("unexpected response type"), + } +} diff --git a/examples/declare_cairo0_contract.rs b/examples/declare_cairo0_contract.rs index bb7dadfc..1bf94b0d 100644 --- a/examples/declare_cairo0_contract.rs +++ b/examples/declare_cairo0_contract.rs @@ -45,5 +45,6 @@ async fn main() { .await .unwrap(); - dbg!(result); + println!("Transaction hash: {:#064x}", result.transaction_hash); + println!("Class hash: {:#064x}", result.class_hash); } diff --git a/examples/declare_cairo1_contract.rs b/examples/declare_cairo1_contract.rs index f6f2fb3d..86ab0fdd 100644 --- a/examples/declare_cairo1_contract.rs +++ b/examples/declare_cairo1_contract.rs @@ -53,5 +53,6 @@ async fn main() { .await .unwrap(); - dbg!(result); + println!("Transaction hash: {:#064x}", result.transaction_hash); + println!("Class hash: {:#064x}", result.class_hash); } diff --git a/examples/deploy_account_with_ledger.rs b/examples/deploy_account_with_ledger.rs index 0f25ad2b..ef4ba9ae 100644 --- a/examples/deploy_account_with_ledger.rs +++ b/examples/deploy_account_with_ledger.rs @@ -51,6 +51,7 @@ async fn main() { match result { Ok(tx) => { println!("Transaction hash: {:#064x}", tx.transaction_hash); + println!("Account: {:#064x}", tx.contract_address); } Err(err) => { eprintln!("Error: {err}"); diff --git a/examples/deploy_argent_account.rs b/examples/deploy_argent_account.rs index 8eff33cc..b296c645 100644 --- a/examples/deploy_argent_account.rs +++ b/examples/deploy_argent_account.rs @@ -11,8 +11,8 @@ use starknet::{ #[tokio::main] async fn main() { - // Latest hash as of 2023-09-15. For demo only. - let class_hash = felt!("0x01a736d6ed154502257f02b1ccdf4d9d1089f80811cd6acad48e6b6a9d1f2003"); + // Latest hash as of 2024-12-01. For demo only. + let class_hash = felt!("0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f"); // Anything you like here as salt let salt = felt!("12345678"); @@ -25,10 +25,9 @@ async fn main() { Felt::from_hex("YOUR_PRIVATE_KEY_IN_HEX_HERE").unwrap(), )); - let factory = - ArgentAccountFactory::new(class_hash, chain_id::SEPOLIA, Felt::ZERO, signer, provider) - .await - .unwrap(); + let factory = ArgentAccountFactory::new(class_hash, chain_id::SEPOLIA, None, signer, provider) + .await + .unwrap(); let deployment = factory.deploy_v1(salt); @@ -47,6 +46,7 @@ async fn main() { match result { Ok(tx) => { println!("Transaction hash: {:#064x}", tx.transaction_hash); + println!("Account: {:#064x}", tx.contract_address); } Err(err) => { eprintln!("Error: {err}"); diff --git a/examples/mint_tokens.rs b/examples/mint_tokens.rs index 33e86ea2..7a2deaec 100644 --- a/examples/mint_tokens.rs +++ b/examples/mint_tokens.rs @@ -1,8 +1,8 @@ use starknet::{ - accounts::{Account, Call, ExecutionEncoding, SingleOwnerAccount}, + accounts::{Account, ExecutionEncoding, SingleOwnerAccount}, core::{ chain_id, - types::{BlockId, BlockTag, Felt}, + types::{BlockId, BlockTag, Call, Felt}, utils::get_selector_from_name, }, providers::{ @@ -51,5 +51,5 @@ async fn main() { .await .unwrap(); - dbg!(result); + println!("Transaction hash: {:#064x}", result.transaction_hash); } diff --git a/examples/parse_jsonrpc_request.rs b/examples/parse_jsonrpc_request.rs index d325cf2c..6a69dd2a 100644 --- a/examples/parse_jsonrpc_request.rs +++ b/examples/parse_jsonrpc_request.rs @@ -1,4 +1,4 @@ -use starknet_providers::jsonrpc::{JsonRpcRequest, JsonRpcRequestData}; +use starknet_providers::{jsonrpc::JsonRpcRequest, ProviderRequestData}; fn main() { // Let's pretend this is the raw request body coming from HTTP @@ -17,7 +17,7 @@ fn main() { println!("Request received: {:#?}", parsed_request); match parsed_request.data { - JsonRpcRequestData::GetBlockTransactionCount(req) => { + ProviderRequestData::GetBlockTransactionCount(req) => { println!( "starknet_getBlockTransactionCount request received for block: {:?}", req.block_id diff --git a/examples/serde.rs b/examples/serde.rs new file mode 100644 index 00000000..63f9c843 --- /dev/null +++ b/examples/serde.rs @@ -0,0 +1,46 @@ +use starknet::{ + core::{ + codec::{Decode, Encode}, + types::Felt, + }, + macros::felt, +}; + +#[derive(Debug, Eq, PartialEq, Encode, Decode)] +struct CairoType { + a: Felt, + b: Option, + c: Vec, + d: [u8; 2], +} + +fn main() { + let instance = CairoType { + a: felt!("123456789"), + b: Some(100), + c: vec![false, true], + d: [3, 4], + }; + + let mut serialized = vec![]; + instance.encode(&mut serialized).unwrap(); + + assert_eq!( + serialized, + [ + felt!("123456789"), + felt!("0"), + felt!("100"), + felt!("2"), + felt!("0"), + felt!("1"), + felt!("2"), + felt!("3"), + felt!("4"), + ] + ); + + let restored = CairoType::decode(&serialized).unwrap(); + + assert_eq!(instance, restored); +} diff --git a/examples/starknet-cxx/starknet-cxx/src/lib.rs b/examples/starknet-cxx/starknet-cxx/src/lib.rs index f34221b5..2bce3874 100644 --- a/examples/starknet-cxx/starknet-cxx/src/lib.rs +++ b/examples/starknet-cxx/starknet-cxx/src/lib.rs @@ -1,5 +1,5 @@ //! This is a quick demo on exposing `starknet-crypto` to C++ with the cxx bridge: -//! https://github.com/xJonathanLEI/starknet-rs/issues/325 +//! //! //! This wrapper crate expose functions that operate on strings, which is bad and probably hurts //! performance. It's possible to make the C++ side create `Felt` instances and operate on diff --git a/examples/starknet-wasm/Cargo.toml b/examples/starknet-wasm/Cargo.toml index cf4f900c..31231388 100644 --- a/examples/starknet-wasm/Cargo.toml +++ b/examples/starknet-wasm/Cargo.toml @@ -19,6 +19,6 @@ crate-type = ["cdylib", "rlib"] default = ["console_error_panic_hook"] [dependencies] -starknet-crypto = { version = "0.7.0", path = "../../starknet-crypto" } +starknet-crypto = { version = "0.7.3", path = "../../starknet-crypto" } console_error_panic_hook = { version = "0.1.7", optional = true } wasm-bindgen = "0.2.84" diff --git a/examples/starknet-wasm/src/lib.rs b/examples/starknet-wasm/src/lib.rs index a32699fa..b4ba8248 100644 --- a/examples/starknet-wasm/src/lib.rs +++ b/examples/starknet-wasm/src/lib.rs @@ -1,4 +1,5 @@ #![allow(clippy::unused_unit)] +#![allow(unexpected_cfgs)] use starknet_crypto::Felt; use wasm_bindgen::prelude::*; diff --git a/examples/transfer_with_ledger.rs b/examples/transfer_with_ledger.rs new file mode 100644 index 00000000..d6fd1fff --- /dev/null +++ b/examples/transfer_with_ledger.rs @@ -0,0 +1,57 @@ +use starknet::{ + accounts::{Account, ExecutionEncoding, SingleOwnerAccount}, + core::{ + chain_id, + types::{BlockId, BlockTag, Call, Felt}, + utils::get_selector_from_name, + }, + macros::felt, + providers::{ + jsonrpc::{HttpTransport, JsonRpcClient}, + Url, + }, + signers::LedgerSigner, +}; + +#[tokio::main] +async fn main() { + let provider = JsonRpcClient::new(HttpTransport::new( + Url::parse("https://starknet-sepolia.public.blastapi.io/rpc/v0_7").unwrap(), + )); + + let signer = LedgerSigner::new( + "m/2645'/1195502025'/1470455285'/0'/0'/0" + .try_into() + .expect("unable to parse path"), + ) + .await + .expect("failed to initialize Starknet Ledger app"); + let address = Felt::from_hex("YOUR_ACCOUNT_CONTRACT_ADDRESS_IN_HEX_HERE").unwrap(); + let eth_token_address = + Felt::from_hex("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7") + .unwrap(); + + let mut account = SingleOwnerAccount::new( + provider, + signer, + address, + chain_id::SEPOLIA, + ExecutionEncoding::New, + ); + + // `SingleOwnerAccount` defaults to checking nonce and estimating fees against the latest + // block. Optionally change the target block to pending with the following line: + account.set_block_id(BlockId::Tag(BlockTag::Pending)); + + let result = account + .execute_v1(vec![Call { + to: eth_token_address, + selector: get_selector_from_name("transfer").unwrap(), + calldata: vec![felt!("0x1234"), felt!("100"), Felt::ZERO], + }]) + .send() + .await + .unwrap(); + + println!("Transaction hash: {:#064x}", result.transaction_hash); +} diff --git a/scripts/run_bench_wasm.sh b/scripts/run_bench_wasm.sh index a36ae83f..008b6175 100755 --- a/scripts/run_bench_wasm.sh +++ b/scripts/run_bench_wasm.sh @@ -27,5 +27,10 @@ benches=( ) for bench in ${benches[@]}; do - $RUNTIME run --dir=. $REPO_ROOT/target/bench-wasm/$bench.wasm -- --bench + if [[ "$RUNTIME" == "wasmtime" ]]; then + # https://github.com/bytecodealliance/wasmtime/issues/7384 + $RUNTIME run --dir=. -- $REPO_ROOT/target/bench-wasm/$bench.wasm --bench + else + $RUNTIME run --dir=. $REPO_ROOT/target/bench-wasm/$bench.wasm -- --bench + fi done diff --git a/src/lib.rs b/src/lib.rs index cf90871b..ce1f491c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,8 @@ //! //! Contains procedural macros useful for this crate. +#![deny(missing_docs)] + #[doc = include_str!("../assets/CORE_README.md")] pub mod core { pub use starknet_core::*; diff --git a/starknet-accounts/Cargo.toml b/starknet-accounts/Cargo.toml index 55827140..d25a9ee3 100644 --- a/starknet-accounts/Cargo.toml +++ b/starknet-accounts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-accounts" -version = "0.10.0" +version = "0.11.0" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -14,10 +14,10 @@ keywords = ["ethereum", "starknet", "web3"] exclude = ["test-data/**"] [dependencies] -starknet-core = { version = "0.11.1", path = "../starknet-core" } -starknet-crypto = { version = "0.7.0", path = "../starknet-crypto" } -starknet-providers = { version = "0.11.0", path = "../starknet-providers" } -starknet-signers = { version = "0.9.0", path = "../starknet-signers" } +starknet-core = { version = "0.12.0", path = "../starknet-core" } +starknet-crypto = { version = "0.7.3", path = "../starknet-crypto" } +starknet-providers = { version = "0.12.0", path = "../starknet-providers" } +starknet-signers = { version = "0.10.0", path = "../starknet-signers" } async-trait = "0.1.68" auto_impl = "1.0.1" thiserror = "1.0.40" @@ -27,3 +27,6 @@ serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" tokio = { version = "1.27.0", features = ["full"] } url = "2.3.1" + +[lints] +workspace = true diff --git a/starknet-accounts/src/account/declaration.rs b/starknet-accounts/src/account/declaration.rs index 5162320f..b47296fa 100644 --- a/starknet-accounts/src/account/declaration.rs +++ b/starknet-accounts/src/account/declaration.rs @@ -12,10 +12,12 @@ use starknet_core::{ BroadcastedDeclareTransactionV2, BroadcastedDeclareTransactionV3, BroadcastedTransaction, DataAvailabilityMode, DeclareTransactionResult, FeeEstimate, Felt, FlattenedSierraClass, ResourceBounds, ResourceBoundsMapping, SimulatedTransaction, SimulationFlag, + SimulationFlagForEstimateFee, }, }; use starknet_crypto::PoseidonHasher; use starknet_providers::Provider; +use starknet_signers::SignerInteractivityContext; use std::sync::Arc; /// Cairo string for "declare" @@ -51,7 +53,11 @@ const QUERY_VERSION_THREE: Felt = Felt::from_raw([ ]); impl<'a, A> DeclarationV2<'a, A> { - pub fn new( + /// Constructs a new [`DeclarationV2`]. + /// + /// Users would typically use [`declare_v2`](fn.declare_v2) on an [`Account`] instead of + /// directly calling this method. + pub const fn new( contract_class: Arc, compiled_class_hash: Felt, account: &'a A, @@ -66,6 +72,7 @@ impl<'a, A> DeclarationV2<'a, A> { } } + /// Returns a new [`DeclarationV2`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -73,6 +80,7 @@ impl<'a, A> DeclarationV2<'a, A> { } } + /// Returns a new [`DeclarationV2`] with the `max_fee`. pub fn max_fee(self, max_fee: Felt) -> Self { Self { max_fee: Some(max_fee), @@ -80,6 +88,9 @@ impl<'a, A> DeclarationV2<'a, A> { } } + /// Returns a new [`DeclarationV2`] with the fee estimate multiplier. The multiplier is used + /// when transaction fee is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self { Self { fee_estimate_multiplier, @@ -87,8 +98,8 @@ impl<'a, A> DeclarationV2<'a, A> { } } - /// Calling this function after manually specifying `nonce` and `max_fee` turns [DeclarationV2] - /// into [PreparedDeclarationV2]. Returns `Err` if either field is `None`. + /// Calling this function after manually specifying `nonce` and `max_fee` turns [`DeclarationV2`] + /// into [`PreparedDeclarationV2`]. Returns `Err` if either field is `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; let max_fee = self.max_fee.ok_or(NotPreparedError)?; @@ -109,6 +120,7 @@ impl<'a, A> DeclarationV2<'a, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -123,6 +135,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -142,6 +156,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { self.prepare().await?.send().await } @@ -195,6 +210,10 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Other); + let prepared = PreparedDeclarationV2 { account: self.account, inner: RawDeclarationV2 { @@ -204,13 +223,19 @@ where max_fee: Felt::ZERO, }, }; - let declare = prepared.get_declare_request(true).await?; + let declare = prepared.get_declare_request(true, skip_signature).await?; self.account .provider() .estimate_fee_single( BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V2(declare)), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.account.block_id(), ) .await @@ -223,6 +248,19 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Other) + { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedDeclarationV2 { account: self.account, inner: RawDeclarationV2 { @@ -232,7 +270,7 @@ where max_fee: self.max_fee.unwrap_or_default(), }, }; - let declare = prepared.get_declare_request(true).await?; + let declare = prepared.get_declare_request(true, skip_signature).await?; let mut flags = vec![]; @@ -256,7 +294,11 @@ where } impl<'a, A> DeclarationV3<'a, A> { - pub fn new( + /// Constructs a new [`DeclarationV3`]. + /// + /// Users would typically use [`declare_v3`](fn.declare_v3) on an [`Account`] instead of + /// directly calling this method. + pub const fn new( contract_class: Arc, compiled_class_hash: Felt, account: &'a A, @@ -273,6 +315,7 @@ impl<'a, A> DeclarationV3<'a, A> { } } + /// Returns a new [`DeclarationV3`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -280,6 +323,7 @@ impl<'a, A> DeclarationV3<'a, A> { } } + /// Returns a new [`DeclarationV3`] with the `gas`. pub fn gas(self, gas: u64) -> Self { Self { gas: Some(gas), @@ -287,6 +331,7 @@ impl<'a, A> DeclarationV3<'a, A> { } } + /// Returns a new [`DeclarationV3`] with the `gas_price`. pub fn gas_price(self, gas_price: u128) -> Self { Self { gas_price: Some(gas_price), @@ -294,6 +339,9 @@ impl<'a, A> DeclarationV3<'a, A> { } } + /// Returns a new [`DeclarationV3`] with the gas amount estimate multiplier. The multiplier is + /// used when the gas amount is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self { Self { gas_estimate_multiplier, @@ -301,6 +349,9 @@ impl<'a, A> DeclarationV3<'a, A> { } } + /// Returns a new [`DeclarationV3`] with the gas price estimate multiplier. The multiplier is + /// used when the gas price is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self { Self { gas_price_estimate_multiplier, @@ -309,7 +360,7 @@ impl<'a, A> DeclarationV3<'a, A> { } /// Calling this function after manually specifying `nonce`, `gas` and `gas_price` turns - /// [DeclarationV3] into [PreparedDeclarationV3]. Returns `Err` if any field is `None`. + /// [`DeclarationV3`] into [`PreparedDeclarationV3`]. Returns `Err` if any field is `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; let gas = self.gas.ok_or(NotPreparedError)?; @@ -332,6 +383,7 @@ impl<'a, A> DeclarationV3<'a, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -346,6 +398,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -365,6 +419,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { self.prepare().await?.send().await } @@ -431,8 +486,8 @@ where let gas_price = u64::from_le_bytes(gas_price_bytes[..8].try_into().unwrap()); - (((overall_fee + gas_price - 1) / gas_price) as f64 - * self.gas_estimate_multiplier) as u64 + (overall_fee.div_ceil(gas_price) as f64 * self.gas_estimate_multiplier) + as u64 } }; @@ -470,6 +525,10 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Other); + let prepared = PreparedDeclarationV3 { account: self.account, inner: RawDeclarationV3 { @@ -480,13 +539,19 @@ where gas_price: 0, }, }; - let declare = prepared.get_declare_request(true).await?; + let declare = prepared.get_declare_request(true, skip_signature).await?; self.account .provider() .estimate_fee_single( BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V3(declare)), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.account.block_id(), ) .await @@ -499,6 +564,19 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Other) + { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedDeclarationV3 { account: self.account, inner: RawDeclarationV3 { @@ -509,7 +587,7 @@ where gas_price: self.gas_price.unwrap_or_default(), }, }; - let declare = prepared.get_declare_request(true).await?; + let declare = prepared.get_declare_request(true, skip_signature).await?; let mut flags = vec![]; @@ -533,7 +611,11 @@ where } impl<'a, A> LegacyDeclaration<'a, A> { - pub fn new(contract_class: Arc, account: &'a A) -> Self { + /// Constructs a new [`LegacyDeclaration`]. + /// + /// Users would typically use [`declare_legacy`](fn.declare_legacy) on an [`Account`] instead of + /// directly calling this method. + pub const fn new(contract_class: Arc, account: &'a A) -> Self { Self { account, contract_class, @@ -543,6 +625,7 @@ impl<'a, A> LegacyDeclaration<'a, A> { } } + /// Returns a new [`LegacyDeclaration`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -550,6 +633,7 @@ impl<'a, A> LegacyDeclaration<'a, A> { } } + /// Returns a new [`LegacyDeclaration`] with the `max_fee`. pub fn max_fee(self, max_fee: Felt) -> Self { Self { max_fee: Some(max_fee), @@ -557,6 +641,9 @@ impl<'a, A> LegacyDeclaration<'a, A> { } } + /// Returns a new [`LegacyDeclaration`] with the fee estimate multiplier. The multiplier is used + /// when transaction fee is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self { Self { fee_estimate_multiplier, @@ -565,7 +652,7 @@ impl<'a, A> LegacyDeclaration<'a, A> { } /// Calling this function after manually specifying `nonce` and `max_fee` turns - /// [LegacyDeclaration] into [PreparedLegacyDeclaration]. Returns `Err` if either field is + /// [`LegacyDeclaration`] into [`PreparedLegacyDeclaration`]. Returns `Err` if either field is /// `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; @@ -586,6 +673,7 @@ impl<'a, A> LegacyDeclaration<'a, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -600,6 +688,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -619,6 +709,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { self.prepare().await?.send().await } @@ -673,6 +764,10 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Other); + let prepared = PreparedLegacyDeclaration { account: self.account, inner: RawLegacyDeclaration { @@ -681,13 +776,19 @@ where max_fee: Felt::ZERO, }, }; - let declare = prepared.get_declare_request(true).await?; + let declare = prepared.get_declare_request(true, skip_signature).await?; self.account .provider() .estimate_fee_single( BroadcastedTransaction::Declare(BroadcastedDeclareTransaction::V1(declare)), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.account.block_id(), ) .await @@ -700,6 +801,19 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Other) + { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedLegacyDeclaration { account: self.account, inner: RawLegacyDeclaration { @@ -708,7 +822,7 @@ where max_fee: self.max_fee.unwrap_or_default(), }, }; - let declare = prepared.get_declare_request(true).await?; + let declare = prepared.get_declare_request(true, skip_signature).await?; let mut flags = vec![]; @@ -732,6 +846,7 @@ where } impl RawDeclarationV2 { + /// Calculates transaction hash given `chain_id`, `address`, and `query_only`. pub fn transaction_hash(&self, chain_id: Felt, address: Felt, query_only: bool) -> Felt { compute_hash_on_elements(&[ PREFIX_DECLARE, @@ -750,24 +865,29 @@ impl RawDeclarationV2 { ]) } + /// Gets a reference to the flattened Sierra (Cairo 1) class being declared. pub fn contract_class(&self) -> &FlattenedSierraClass { &self.contract_class } - pub fn compiled_class_hash(&self) -> Felt { + /// Gets the CASM class hash corresponding to the Sierra class being declared. + pub const fn compiled_class_hash(&self) -> Felt { self.compiled_class_hash } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the declaration request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn max_fee(&self) -> Felt { + /// Gets the `max_fee` of the declaration request. + pub const fn max_fee(&self) -> Felt { self.max_fee } } impl RawDeclarationV3 { + /// Calculates transaction hash given `chain_id`, `address`, and `query_only`. pub fn transaction_hash(&self, chain_id: Felt, address: Felt, query_only: bool) -> Felt { let mut hasher = PoseidonHasher::new(); @@ -821,28 +941,34 @@ impl RawDeclarationV3 { hasher.finalize() } + /// Gets a reference to the flattened Sierra (Cairo 1) class being declared. pub fn contract_class(&self) -> &FlattenedSierraClass { &self.contract_class } - pub fn compiled_class_hash(&self) -> Felt { + /// Gets the CASM class hash corresponding to the Sierra class being declared. + pub const fn compiled_class_hash(&self) -> Felt { self.compiled_class_hash } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the declaration request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn gas(&self) -> u64 { + /// Gets the `gas` of the declaration request. + pub const fn gas(&self) -> u64 { self.gas } - pub fn gas_price(&self) -> u128 { + /// Gets the `gas_price` of the declaration request. + pub const fn gas_price(&self) -> u128 { self.gas_price } } impl RawLegacyDeclaration { + /// Calculates transaction hash given `chain_id`, `address`, and `query_only`. pub fn transaction_hash( &self, chain_id: Felt, @@ -865,20 +991,23 @@ impl RawLegacyDeclaration { ])) } + /// Gets a reference to the legacy (Cairo 0) class being declared. pub fn contract_class(&self) -> &LegacyContractClass { &self.contract_class } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the declaration request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn max_fee(&self) -> Felt { + /// Gets the `max_fee` of the declaration request. + pub const fn max_fee(&self) -> Felt { self.max_fee } } -impl<'a, A> PreparedDeclarationV2<'a, A> +impl PreparedDeclarationV2<'_, A> where A: Account, { @@ -890,12 +1019,13 @@ where } } -impl<'a, A> PreparedDeclarationV2<'a, A> +impl PreparedDeclarationV2<'_, A> where A: ConnectedAccount, { + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { - let tx_request = self.get_declare_request(false).await?; + let tx_request = self.get_declare_request(false, false).await?; self.account .provider() .add_declare_transaction(BroadcastedDeclareTransaction::V2(tx_request)) @@ -903,19 +1033,21 @@ where .map_err(AccountError::Provider) } - pub async fn get_declare_request( + async fn get_declare_request( &self, query_only: bool, + skip_signature: bool, ) -> Result> { - let signature = self - .account - .sign_declaration_v2(&self.inner, query_only) - .await - .map_err(AccountError::Signing)?; - Ok(BroadcastedDeclareTransactionV2 { max_fee: self.inner.max_fee, - signature, + signature: if skip_signature { + vec![] + } else { + self.account + .sign_declaration_v2(&self.inner, query_only) + .await + .map_err(AccountError::Signing)? + }, nonce: self.inner.nonce, contract_class: self.inner.contract_class.clone(), compiled_class_hash: self.inner.compiled_class_hash, @@ -925,7 +1057,7 @@ where } } -impl<'a, A> PreparedDeclarationV3<'a, A> +impl PreparedDeclarationV3<'_, A> where A: Account, { @@ -937,12 +1069,13 @@ where } } -impl<'a, A> PreparedDeclarationV3<'a, A> +impl PreparedDeclarationV3<'_, A> where A: ConnectedAccount, { + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { - let tx_request = self.get_declare_request(false).await?; + let tx_request = self.get_declare_request(false, false).await?; self.account .provider() .add_declare_transaction(BroadcastedDeclareTransaction::V3(tx_request)) @@ -950,20 +1083,22 @@ where .map_err(AccountError::Provider) } - pub async fn get_declare_request( + async fn get_declare_request( &self, query_only: bool, + skip_signature: bool, ) -> Result> { - let signature = self - .account - .sign_declaration_v3(&self.inner, query_only) - .await - .map_err(AccountError::Signing)?; - Ok(BroadcastedDeclareTransactionV3 { sender_address: self.account.address(), compiled_class_hash: self.inner.compiled_class_hash, - signature, + signature: if skip_signature { + vec![] + } else { + self.account + .sign_declaration_v3(&self.inner, query_only) + .await + .map_err(AccountError::Signing)? + }, nonce: self.inner.nonce, contract_class: self.inner.contract_class.clone(), resource_bounds: ResourceBoundsMapping { @@ -991,7 +1126,7 @@ where } } -impl<'a, A> PreparedLegacyDeclaration<'a, A> +impl PreparedLegacyDeclaration<'_, A> where A: Account, { @@ -1003,12 +1138,13 @@ where } } -impl<'a, A> PreparedLegacyDeclaration<'a, A> +impl PreparedLegacyDeclaration<'_, A> where A: ConnectedAccount, { + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { - let tx_request = self.get_declare_request(false).await?; + let tx_request = self.get_declare_request(false, false).await?; self.account .provider() .add_declare_transaction(BroadcastedDeclareTransaction::V1(tx_request)) @@ -1016,21 +1152,23 @@ where .map_err(AccountError::Provider) } - pub async fn get_declare_request( + async fn get_declare_request( &self, query_only: bool, + skip_signature: bool, ) -> Result> { - let signature = self - .account - .sign_legacy_declaration(&self.inner, query_only) - .await - .map_err(AccountError::Signing)?; - let compressed_class = self.inner.contract_class.compress().unwrap(); Ok(BroadcastedDeclareTransactionV1 { max_fee: self.inner.max_fee, - signature, + signature: if skip_signature { + vec![] + } else { + self.account + .sign_legacy_declaration(&self.inner, query_only) + .await + .map_err(AccountError::Signing)? + }, nonce: self.inner.nonce, contract_class: Arc::new(compressed_class), sender_address: self.account.address(), diff --git a/starknet-accounts/src/account/execution.rs b/starknet-accounts/src/account/execution.rs index cef482fc..4c019c01 100644 --- a/starknet-accounts/src/account/execution.rs +++ b/starknet-accounts/src/account/execution.rs @@ -2,19 +2,20 @@ use super::{ super::NotPreparedError, Account, AccountError, ConnectedAccount, ExecutionV1, ExecutionV3, PreparedExecutionV1, PreparedExecutionV3, RawExecutionV1, RawExecutionV3, }; -use crate::{Call, ExecutionEncoder}; +use crate::ExecutionEncoder; use starknet_core::{ crypto::compute_hash_on_elements, types::{ BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV1, - BroadcastedInvokeTransactionV3, BroadcastedTransaction, DataAvailabilityMode, FeeEstimate, - Felt, InvokeTransactionResult, ResourceBounds, ResourceBoundsMapping, SimulatedTransaction, - SimulationFlag, + BroadcastedInvokeTransactionV3, BroadcastedTransaction, Call, DataAvailabilityMode, + FeeEstimate, Felt, InvokeTransactionResult, ResourceBounds, ResourceBoundsMapping, + SimulatedTransaction, SimulationFlag, SimulationFlagForEstimateFee, }, }; use starknet_crypto::PoseidonHasher; use starknet_providers::Provider; +use starknet_signers::SignerInteractivityContext; /// Cairo string for "invoke" const PREFIX_INVOKE: Felt = Felt::from_raw([ @@ -41,7 +42,11 @@ const QUERY_VERSION_THREE: Felt = Felt::from_raw([ ]); impl<'a, A> ExecutionV1<'a, A> { - pub fn new(calls: Vec, account: &'a A) -> Self { + /// Constructs a new [`ExecutionV1`]. + /// + /// Users would typically use [`execute_v1`](fn.execute_v1) on an [`Account`] instead of + /// directly calling this method. + pub const fn new(calls: Vec, account: &'a A) -> Self { Self { account, calls, @@ -51,6 +56,7 @@ impl<'a, A> ExecutionV1<'a, A> { } } + /// Returns a new [`ExecutionV1`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -58,6 +64,7 @@ impl<'a, A> ExecutionV1<'a, A> { } } + /// Returns a new [`ExecutionV1`] with the `max_fee`. pub fn max_fee(self, max_fee: Felt) -> Self { Self { max_fee: Some(max_fee), @@ -65,6 +72,9 @@ impl<'a, A> ExecutionV1<'a, A> { } } + /// Returns a new [`ExecutionV1`] with the fee estimate multiplier. The multiplier is used + /// when transaction fee is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self { Self { fee_estimate_multiplier, @@ -72,8 +82,8 @@ impl<'a, A> ExecutionV1<'a, A> { } } - /// Calling this function after manually specifying `nonce` and `max_fee` turns [ExecutionV1] into - /// [PreparedExecutionV1]. Returns `Err` if either field is `None`. + /// Calling this function after manually specifying `nonce` and `max_fee` turns [`ExecutionV1`] into + /// [`PreparedExecutionV1`]. Returns `Err` if either field is `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; let max_fee = self.max_fee.ok_or(NotPreparedError)?; @@ -90,7 +100,11 @@ impl<'a, A> ExecutionV1<'a, A> { } impl<'a, A> ExecutionV3<'a, A> { - pub fn new(calls: Vec, account: &'a A) -> Self { + /// Constructs a new [`ExecutionV3`]. + /// + /// Users would typically use [`execute_v3`](fn.execute_v3) on an [`Account`] instead of + /// directly calling this method. + pub const fn new(calls: Vec, account: &'a A) -> Self { Self { account, calls, @@ -102,6 +116,7 @@ impl<'a, A> ExecutionV3<'a, A> { } } + /// Returns a new [`ExecutionV3`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -109,6 +124,7 @@ impl<'a, A> ExecutionV3<'a, A> { } } + /// Returns a new [`ExecutionV3`] with the `gas`. pub fn gas(self, gas: u64) -> Self { Self { gas: Some(gas), @@ -116,6 +132,7 @@ impl<'a, A> ExecutionV3<'a, A> { } } + /// Returns a new [`ExecutionV3`] with the `gas_price`. pub fn gas_price(self, gas_price: u128) -> Self { Self { gas_price: Some(gas_price), @@ -123,6 +140,9 @@ impl<'a, A> ExecutionV3<'a, A> { } } + /// Returns a new [`ExecutionV3`] with the gas amount estimate multiplier. The multiplier is + /// used when the gas amount is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self { Self { gas_estimate_multiplier, @@ -130,6 +150,9 @@ impl<'a, A> ExecutionV3<'a, A> { } } + /// Returns a new [`ExecutionV3`] with the gas price estimate multiplier. The multiplier is + /// used when the gas price is not manually specified and must be fetched from a [`Provider`] + /// instead. pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self { Self { gas_price_estimate_multiplier, @@ -138,7 +161,7 @@ impl<'a, A> ExecutionV3<'a, A> { } /// Calling this function after manually specifying `nonce`, `gas` and `gas_price` turns - /// [ExecutionV3] into [PreparedExecutionV3]. Returns `Err` if any field is `None`. + /// [`ExecutionV3`] into [`PreparedExecutionV3`]. Returns `Err` if any field is `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; let gas = self.gas.ok_or(NotPreparedError)?; @@ -160,6 +183,7 @@ impl<'a, A> ExecutionV1<'a, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -174,6 +198,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -193,6 +219,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { self.prepare().await?.send().await } @@ -245,6 +272,10 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }); + let prepared = PreparedExecutionV1 { account: self.account, inner: RawExecutionV1 { @@ -254,7 +285,7 @@ where }, }; let invoke = prepared - .get_invoke_request(true) + .get_invoke_request(true, skip_signature) .await .map_err(AccountError::Signing)?; @@ -262,7 +293,13 @@ where .provider() .estimate_fee_single( BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V1(invoke)), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.account.block_id(), ) .await @@ -275,6 +312,19 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }) + { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedExecutionV1 { account: self.account, inner: RawExecutionV1 { @@ -284,7 +334,7 @@ where }, }; let invoke = prepared - .get_invoke_request(true) + .get_invoke_request(true, skip_signature) .await .map_err(AccountError::Signing)?; @@ -313,6 +363,7 @@ impl<'a, A> ExecutionV3<'a, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -327,6 +378,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -346,6 +399,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { self.prepare().await?.send().await } @@ -412,8 +466,8 @@ where let gas_price = u64::from_le_bytes(gas_price_bytes[..8].try_into().unwrap()); - ((((overall_fee + gas_price - 1) / gas_price) as f64) - * self.gas_estimate_multiplier) as u64 + ((overall_fee.div_ceil(gas_price) as f64) * self.gas_estimate_multiplier) + as u64 } }; @@ -450,6 +504,10 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }); + let prepared = PreparedExecutionV3 { account: self.account, inner: RawExecutionV3 { @@ -460,7 +518,7 @@ where }, }; let invoke = prepared - .get_invoke_request(true) + .get_invoke_request(true, skip_signature) .await .map_err(AccountError::Signing)?; @@ -468,7 +526,13 @@ where .provider() .estimate_fee_single( BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V3(invoke)), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.account.block_id(), ) .await @@ -481,6 +545,19 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self + .account + .is_signer_interactive(SignerInteractivityContext::Execution { calls: &self.calls }) + { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedExecutionV3 { account: self.account, inner: RawExecutionV3 { @@ -491,7 +568,7 @@ where }, }; let invoke = prepared - .get_invoke_request(true) + .get_invoke_request(true, skip_signature) .await .map_err(AccountError::Signing)?; @@ -517,6 +594,7 @@ where } impl RawExecutionV1 { + /// Calculates transaction hash given `chain_id`, `address`, `query_only`, and `encoder`. pub fn transaction_hash( &self, chain_id: Felt, @@ -543,20 +621,24 @@ impl RawExecutionV1 { ]) } + /// Gets a reference to the list of contract calls included in the execution. pub fn calls(&self) -> &[Call] { &self.calls } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the execution request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn max_fee(&self) -> Felt { + /// Gets the `max_fee` of the execution request. + pub const fn max_fee(&self) -> Felt { self.max_fee } } impl RawExecutionV3 { + /// Calculates transaction hash given `chain_id`, `address`, `query_only`, and `encoder`. pub fn transaction_hash( &self, chain_id: Felt, @@ -627,24 +709,28 @@ impl RawExecutionV3 { hasher.finalize() } + /// Gets a reference to the list of contract calls included in the execution. pub fn calls(&self) -> &[Call] { &self.calls } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the execution request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn gas(&self) -> u64 { + /// Gets the `gas` of the execution request. + pub const fn gas(&self) -> u64 { self.gas } - pub fn gas_price(&self) -> u128 { + /// Gets the `gas_price` of the execution request. + pub const fn gas_price(&self) -> u128 { self.gas_price } } -impl<'a, A> PreparedExecutionV1<'a, A> +impl PreparedExecutionV1<'_, A> where A: Account, { @@ -660,7 +746,7 @@ where } } -impl<'a, A> PreparedExecutionV3<'a, A> +impl PreparedExecutionV3<'_, A> where A: Account, { @@ -676,13 +762,14 @@ where } } -impl<'a, A> PreparedExecutionV1<'a, A> +impl PreparedExecutionV1<'_, A> where A: ConnectedAccount, { + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { let tx_request = self - .get_invoke_request(false) + .get_invoke_request(false, false) .await .map_err(AccountError::Signing)?; self.account @@ -695,18 +782,20 @@ where // The `simulate` function is temporarily removed until it's supported in [Provider] // TODO: add `simulate` back once transaction simulation in supported - pub async fn get_invoke_request( + async fn get_invoke_request( &self, query_only: bool, + skip_signature: bool, ) -> Result { - let signature = self - .account - .sign_execution_v1(&self.inner, query_only) - .await?; - Ok(BroadcastedInvokeTransactionV1 { max_fee: self.inner.max_fee, - signature, + signature: if skip_signature { + vec![] + } else { + self.account + .sign_execution_v1(&self.inner, query_only) + .await? + }, nonce: self.inner.nonce, sender_address: self.account.address(), calldata: self.account.encode_calls(&self.inner.calls), @@ -715,13 +804,14 @@ where } } -impl<'a, A> PreparedExecutionV3<'a, A> +impl PreparedExecutionV3<'_, A> where A: ConnectedAccount, { + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { let tx_request = self - .get_invoke_request(false) + .get_invoke_request(false, false) .await .map_err(AccountError::Signing)?; self.account @@ -734,19 +824,21 @@ where // The `simulate` function is temporarily removed until it's supported in [Provider] // TODO: add `simulate` back once transaction simulation in supported - pub async fn get_invoke_request( + async fn get_invoke_request( &self, query_only: bool, + skip_signature: bool, ) -> Result { - let signature = self - .account - .sign_execution_v3(&self.inner, query_only) - .await?; - Ok(BroadcastedInvokeTransactionV3 { sender_address: self.account.address(), calldata: self.account.encode_calls(&self.inner.calls), - signature, + signature: if skip_signature { + vec![] + } else { + self.account + .sign_execution_v3(&self.inner, query_only) + .await? + }, nonce: self.inner.nonce, resource_bounds: ResourceBoundsMapping { l1_gas: ResourceBounds { diff --git a/starknet-accounts/src/account/mod.rs b/starknet-accounts/src/account/mod.rs index 30e39730..4979377d 100644 --- a/starknet-accounts/src/account/mod.rs +++ b/starknet-accounts/src/account/mod.rs @@ -1,12 +1,11 @@ -use crate::Call; - use async_trait::async_trait; use auto_impl::auto_impl; use starknet_core::types::{ contract::{legacy::LegacyContractClass, CompressProgramError, ComputeClassHashError}, - BlockId, BlockTag, Felt, FlattenedSierraClass, + BlockId, BlockTag, Call, Felt, FlattenedSierraClass, }; use starknet_providers::{Provider, ProviderError}; +use starknet_signers::SignerInteractivityContext; use std::{error::Error, sync::Arc}; mod declaration; @@ -19,105 +18,209 @@ mod execution; #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] pub trait Account: ExecutionEncoder + Sized { + /// Possible errors for signing transactions. type SignError: Error + Send + Sync; + /// Gets the account contract's address. fn address(&self) -> Felt; + /// Gets the chain ID of the network where the account contract was deployed. fn chain_id(&self) -> Felt; + /// Signs an execution request to authorize an `INVOKE` v1 transaction that pays transaction + /// fees in `ETH`. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_execution_v1( &self, execution: &RawExecutionV1, query_only: bool, ) -> Result, Self::SignError>; + /// Signs an execution request to authorize an `INVOKE` v3 transaction that pays transaction + /// fees in `STRK`. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_execution_v3( &self, execution: &RawExecutionV3, query_only: bool, ) -> Result, Self::SignError>; + /// Signs an execution request to authorize an `DECLARE` v2 transaction that pays transaction + /// fees in `ETH` for declaring Cairo 1 classes. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_declaration_v2( &self, declaration: &RawDeclarationV2, query_only: bool, ) -> Result, Self::SignError>; + /// Signs an execution request to authorize an `DECLARE` v3 transaction that pays transaction + /// fees in `STRK` for declaring Cairo 1 classes. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_declaration_v3( &self, declaration: &RawDeclarationV3, query_only: bool, ) -> Result, Self::SignError>; + /// Signs an execution request to authorize an `DECLARE` v1 transaction that pays transaction + /// fees in `ETH` for declaring Cairo 0 classes. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_legacy_declaration( &self, legacy_declaration: &RawLegacyDeclaration, query_only: bool, ) -> Result, Self::SignError>; - fn execute_v1(&self, calls: Vec) -> ExecutionV1 { + /// Whether the underlying signer implementation is interactive, such as a hardware wallet. + /// Implementations should return `true` if the signing operation is very expensive, even if not + /// strictly "interactive" as in requiring human input. + /// + /// This affects how an account makes decision on whether to request a real signature for + /// estimation/simulation purposes. + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool; + + /// Generates an instance of [`ExecutionV1`] for sending `INVOKE` v1 transactions. Pays + /// transaction fees in `ETH`. + fn execute_v1(&self, calls: Vec) -> ExecutionV1<'_, Self> { ExecutionV1::new(calls, self) } - fn execute_v3(&self, calls: Vec) -> ExecutionV3 { + /// Generates an instance of [`ExecutionV3`] for sending `INVOKE` v3 transactions. Pays + /// transaction fees in `STRK`. + fn execute_v3(&self, calls: Vec) -> ExecutionV3<'_, Self> { ExecutionV3::new(calls, self) } + /// Generates an instance of [`ExecutionV1`] for sending `INVOKE` v1 transactions. Pays + /// transaction fees in `ETH`. #[deprecated = "use version specific variants (`execute_v1` & `execute_v3`) instead"] - fn execute(&self, calls: Vec) -> ExecutionV1 { + fn execute(&self, calls: Vec) -> ExecutionV1<'_, Self> { self.execute_v1(calls) } + /// Generates an instance of [`DeclarationV2`] for sending `DECLARE` v2 transactions. Pays + /// transaction fees in `ETH`. + /// + /// To declare a Sierra (Cairo 1) class, a `compiled_class_hash` must be provided. This can be + /// obtained by compiling the Sierra class to obtain a CASM class, and then hashing it. + /// + /// The compilation of Sierra to CASM can either be done interactively via the + /// `starknet-sierra-compile` command from the Cairo toolchain, or programmatically through the + /// Cairo crates. + /// + /// Hashing the resulting CASM class is supported in the `starknet-core` crate. It can also be + /// done interactively via Starkli with its `starkli class-hash` command. + /// + /// This method is only used for declaring Sierra (Cairo 1) classes. To declare legacy (Cairo 0) + /// classes use [`declare_legacy`](fn.declare_legacy) instead. fn declare_v2( &self, contract_class: Arc, compiled_class_hash: Felt, - ) -> DeclarationV2 { + ) -> DeclarationV2<'_, Self> { DeclarationV2::new(contract_class, compiled_class_hash, self) } + /// Generates an instance of [`DeclarationV3`] for sending `DECLARE` v3 transactions. Pays + /// transaction fees in `STRK`. + /// + /// To declare a Sierra (Cairo 1) class, a `compiled_class_hash` must be provided. This can be + /// obtained by compiling the Sierra class to obtain a CASM class, and then hashing it. + /// + /// The compilation of Sierra to CASM can either be done interactively via the + /// `starknet-sierra-compile` command from the Cairo toolchain, or programmatically through the + /// Cairo crates. + /// + /// Hashing the resulting CASM class is supported in the `starknet-core` crate. It can also be + /// done interactively via Starkli with its `starkli class-hash` command. + /// + /// This method is only used for declaring Sierra (Cairo 1) classes. To declare legacy (Cairo 0) + /// classes use [`declare_legacy`](fn.declare_legacy) instead. fn declare_v3( &self, contract_class: Arc, compiled_class_hash: Felt, - ) -> DeclarationV3 { + ) -> DeclarationV3<'_, Self> { DeclarationV3::new(contract_class, compiled_class_hash, self) } - #[deprecated = "use version specific variants (`declare_v1` & `declare_v3`) instead"] + /// Generates an instance of [`DeclarationV2`] for sending `DECLARE` v2 transactions. Pays + /// transaction fees in `ETH`. + /// + /// To declare a Sierra (Cairo 1) class, a `compiled_class_hash` must be provided. This can be + /// obtained by compiling the Sierra class to obtain a CASM class, and then hashing it. + /// + /// The compilation of Sierra to CASM can either be done interactively via the + /// `starknet-sierra-compile` command from the Cairo toolchain, or programmatically through the + /// Cairo crates. + /// + /// Hashing the resulting CASM class is supported in the `starknet-core` crate. It can also be + /// done interactively via Starkli with its `starkli class-hash` command. + /// + /// This method is only used for declaring Sierra (Cairo 1) classes. To declare legacy (Cairo 0) + /// classes use [`declare_legacy`](fn.declare_legacy) instead. + #[deprecated = "use version specific variants (`declare_v2` & `declare_v3`) instead"] fn declare( &self, contract_class: Arc, compiled_class_hash: Felt, - ) -> DeclarationV2 { + ) -> DeclarationV2<'_, Self> { self.declare_v2(contract_class, compiled_class_hash) } - fn declare_legacy(&self, contract_class: Arc) -> LegacyDeclaration { + /// Generates an instance of [`LegacyDeclaration`] for sending `DECLARE` v1 transactions. Pays + /// transaction fees in `ETH`. + /// + /// This method is only used for declaring legacy (Cairo 0) classes. To declare Sierra (Cairo 1) + /// classes use [`declare_v2`](fn.declare_v2) or [`declare_v3`](fn.declare_v3) instead. + fn declare_legacy( + &self, + contract_class: Arc, + ) -> LegacyDeclaration<'_, Self> { LegacyDeclaration::new(contract_class, self) } } +/// An abstraction over different ways to encode [`Vec`] into [`Vec`]. +/// +/// Standard Cairo 0 and Cairo 1 account contracts encodes calls differently. Custom account +/// contract implementations might also impose arbitrary encoding rules. #[auto_impl(&, Box, Arc)] pub trait ExecutionEncoder { + /// Encodes the list of [`Call`] into a list of [`Felt`] to be used as calldata to the account's + /// `__execute__` entrypoint. fn encode_calls(&self, calls: &[Call]) -> Vec; } -/// An [Account] implementation that also comes with a [Provider]. Functionalities that require a -/// connection to the sequencer or node are offloaded to this trait to keep the base [Account] +/// An [`Account`] implementation that also comes with a [`Provider`]. Functionalities that require +/// a connection to the sequencer or node are offloaded to this trait to keep the base [`Account`] /// clean and flexible. #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] pub trait ConnectedAccount: Account { + /// The [`Provider`] type attached to this account. type Provider: Provider + Sync; + /// Gets a reference to the attached [`Provider`] instance. fn provider(&self) -> &Self::Provider; - /// Block ID to use when checking nonce and estimating fees. + /// Gets block ID to use when checking nonce and estimating fees. fn block_id(&self) -> BlockId { BlockId::Tag(BlockTag::Latest) } + /// Gets the next available nonce to be used. async fn get_nonce(&self) -> Result { self.provider() .get_nonce(self.block_id(), self.address()) @@ -127,7 +230,7 @@ pub trait ConnectedAccount: Account { /// Abstraction over `INVOKE` transactions from accounts for invoking contracts. This struct uses /// v1 `INVOKE` transactions under the hood, and hence pays transaction fees in ETH. To use v3 -/// transactions for STRK fee payment, use [ExecutionV3] instead. +/// transactions for STRK fee payment, use [`ExecutionV3`] instead. /// /// This is an intermediate type allowing users to optionally specify `nonce` and/or `max_fee`. #[must_use] @@ -142,7 +245,7 @@ pub struct ExecutionV1<'a, A> { /// Abstraction over `INVOKE` transactions from accounts for invoking contracts. This struct uses /// v3 `INVOKE` transactions under the hood, and hence pays transaction fees in STRK. To use v1 -/// transactions for ETH fee payment, use [ExecutionV1] instead. +/// transactions for ETH fee payment, use [`ExecutionV1`] instead. /// /// This is an intermediate type allowing users to optionally specify `nonce`, `gas`, and/or /// `gas_price`. @@ -160,7 +263,7 @@ pub struct ExecutionV3<'a, A> { /// Abstraction over `DECLARE` transactions from accounts for invoking contracts. This struct uses /// v2 `DECLARE` transactions under the hood, and hence pays transaction fees in ETH. To use v3 -/// transactions for STRK fee payment, use [DeclarationV3] instead. +/// transactions for STRK fee payment, use [`DeclarationV3`] instead. /// /// An intermediate type allowing users to optionally specify `nonce` and/or `max_fee`. #[must_use] @@ -176,7 +279,7 @@ pub struct DeclarationV2<'a, A> { /// Abstraction over `DECLARE` transactions from accounts for invoking contracts. This struct uses /// v3 `DECLARE` transactions under the hood, and hence pays transaction fees in STRK. To use v2 -/// transactions for ETH fee payment, use [DeclarationV2] instead. +/// transactions for ETH fee payment, use [`DeclarationV2`] instead. /// /// This is an intermediate type allowing users to optionally specify `nonce`, `gas`, and/or /// `gas_price`. @@ -204,7 +307,7 @@ pub struct LegacyDeclaration<'a, A> { fee_estimate_multiplier: f64, } -/// [ExecutionV1] but with `nonce` and `max_fee` already determined. +/// [`ExecutionV1`] but with `nonce` and `max_fee` already determined. #[derive(Debug)] pub struct RawExecutionV1 { calls: Vec, @@ -212,7 +315,7 @@ pub struct RawExecutionV1 { max_fee: Felt, } -/// [ExecutionV3] but with `nonce`, `gas` and `gas_price` already determined. +/// [`ExecutionV3`] but with `nonce`, `gas` and `gas_price` already determined. #[derive(Debug)] pub struct RawExecutionV3 { calls: Vec, @@ -221,7 +324,7 @@ pub struct RawExecutionV3 { gas_price: u128, } -/// [DeclarationV2] but with `nonce` and `max_fee` already determined. +/// [`DeclarationV2`] but with `nonce` and `max_fee` already determined. #[derive(Debug)] pub struct RawDeclarationV2 { contract_class: Arc, @@ -230,7 +333,7 @@ pub struct RawDeclarationV2 { max_fee: Felt, } -/// [DeclarationV3] but with `nonce`, `gas` and `gas_price` already determined. +/// [`DeclarationV3`] but with `nonce`, `gas` and `gas_price` already determined. #[derive(Debug)] pub struct RawDeclarationV3 { contract_class: Arc, @@ -240,7 +343,7 @@ pub struct RawDeclarationV3 { gas_price: u128, } -/// [LegacyDeclaration] but with `nonce` and `max_fee` already determined. +/// [`LegacyDeclaration`] but with `nonce` and `max_fee` already determined. #[derive(Debug)] pub struct RawLegacyDeclaration { contract_class: Arc, @@ -248,51 +351,57 @@ pub struct RawLegacyDeclaration { max_fee: Felt, } -/// [RawExecutionV1] but with an account associated. +/// [`RawExecutionV1`] but with an account associated. #[derive(Debug)] pub struct PreparedExecutionV1<'a, A> { account: &'a A, inner: RawExecutionV1, } -/// [RawExecutionV3] but with an account associated. +/// [`RawExecutionV3`] but with an account associated. #[derive(Debug)] pub struct PreparedExecutionV3<'a, A> { account: &'a A, inner: RawExecutionV3, } -/// [RawDeclarationV2] but with an account associated. +/// [`RawDeclarationV2`] but with an account associated. #[derive(Debug)] pub struct PreparedDeclarationV2<'a, A> { account: &'a A, inner: RawDeclarationV2, } -/// [RawDeclarationV3] but with an account associated. +/// [`RawDeclarationV3`] but with an account associated. #[derive(Debug)] pub struct PreparedDeclarationV3<'a, A> { account: &'a A, inner: RawDeclarationV3, } -/// [RawLegacyDeclaration] but with an account associated. +/// [`RawLegacyDeclaration`] but with an account associated. #[derive(Debug)] pub struct PreparedLegacyDeclaration<'a, A> { account: &'a A, inner: RawLegacyDeclaration, } +/// Errors using Starknet accounts. #[derive(Debug, thiserror::Error)] pub enum AccountError { + /// An error is encountered when signing a request. #[error(transparent)] Signing(S), + /// An error is encountered with communicating with the network. #[error(transparent)] Provider(ProviderError), + /// Unable to calculate the class hash for declaration. #[error(transparent)] ClassHashCalculation(ComputeClassHashError), + /// Unable to compress the legacy (Cairo 0) class for declaration. #[error(transparent)] ClassCompression(CompressProgramError), + /// Transaction fee calculation overflow. #[error("fee calculation overflow")] FeeOutOfRange, } @@ -354,6 +463,10 @@ where .sign_legacy_declaration(legacy_declaration, query_only) .await } + + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + (*self).is_signer_interactive(context) + } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -417,6 +530,10 @@ where .sign_legacy_declaration(legacy_declaration, query_only) .await } + + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + self.as_ref().is_signer_interactive(context) + } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -480,6 +597,10 @@ where .sign_legacy_declaration(legacy_declaration, query_only) .await } + + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + self.as_ref().is_signer_interactive(context) + } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] diff --git a/starknet-accounts/src/call.rs b/starknet-accounts/src/call.rs deleted file mode 100644 index 6d86ed53..00000000 --- a/starknet-accounts/src/call.rs +++ /dev/null @@ -1,8 +0,0 @@ -use starknet_core::types::Felt; - -#[derive(Debug, Clone)] -pub struct Call { - pub to: Felt, - pub selector: Felt, - pub calldata: Vec, -} diff --git a/starknet-accounts/src/factory/argent.rs b/starknet-accounts/src/factory/argent.rs index bed65a97..f6677ec5 100644 --- a/starknet-accounts/src/factory/argent.rs +++ b/starknet-accounts/src/factory/argent.rs @@ -4,28 +4,50 @@ use crate::{ }; use async_trait::async_trait; -use starknet_core::types::{BlockId, BlockTag, Felt}; +use starknet_core::{ + codec::Encode, + types::{BlockId, BlockTag, Felt}, +}; use starknet_providers::Provider; -use starknet_signers::Signer; +use starknet_signers::{Signer, SignerInteractivityContext}; +/// [`AccountFactory`] implementation for deploying `Argent X` account contracts (v0.4.0). +#[derive(Debug)] pub struct ArgentAccountFactory { class_hash: Felt, chain_id: Felt, owner_public_key: Felt, - guardian_public_key: Felt, + guardian_public_key: Option, signer: S, provider: P, block_id: BlockId, } +/// Constructor parameters for Argent account v0.4.0. +#[derive(Encode)] +#[starknet(core = "starknet_core")] +struct ArgentAccountConstructorParams { + owner: ArgentSigner, + guardian: Option, +} + +/// A simplified version of `argent::signer::signer_signature::Signer` that only supports the simple +/// Starknet signer. +#[derive(Encode)] +#[starknet(core = "starknet_core")] +enum ArgentSigner { + Starknet(Felt), +} + impl ArgentAccountFactory where S: Signer, { + /// Constructs a new [`ArgentAccountFactory`]. pub async fn new( class_hash: Felt, chain_id: Felt, - guardian_public_key: Felt, + guardian_public_key: Option, signer: S, provider: P, ) -> Result { @@ -41,6 +63,7 @@ where }) } + /// Sets a new block ID to run queries against. pub fn set_block_id(&mut self, block_id: BlockId) -> &Self { self.block_id = block_id; self @@ -62,7 +85,17 @@ where } fn calldata(&self) -> Vec { - vec![self.owner_public_key, self.guardian_public_key] + let mut calldata = vec![]; + + // Encoding this sturct never fails + ArgentAccountConstructorParams { + owner: ArgentSigner::Starknet(self.owner_public_key), + guardian: self.guardian_public_key.map(ArgentSigner::Starknet), + } + .encode(&mut calldata) + .unwrap(); + + calldata } fn chain_id(&self) -> Felt { @@ -73,6 +106,11 @@ where &self.provider } + fn is_signer_interactive(&self) -> bool { + self.signer + .is_interactive(SignerInteractivityContext::Other) + } + fn block_id(&self) -> BlockId { self.block_id } diff --git a/starknet-accounts/src/factory/mod.rs b/starknet-accounts/src/factory/mod.rs index aef251a4..e1d7d09c 100644 --- a/starknet-accounts/src/factory/mod.rs +++ b/starknet-accounts/src/factory/mod.rs @@ -8,7 +8,7 @@ use starknet_core::{ BroadcastedDeployAccountTransactionV1, BroadcastedDeployAccountTransactionV3, BroadcastedTransaction, DataAvailabilityMode, DeployAccountTransactionResult, FeeEstimate, Felt, NonZeroFelt, ResourceBounds, ResourceBoundsMapping, SimulatedTransaction, - SimulationFlag, StarknetError, + SimulationFlag, SimulationFlagForEstimateFee, StarknetError, }, }; use starknet_crypto::PoseidonHasher; @@ -18,7 +18,7 @@ use std::error::Error; pub mod argent; pub mod open_zeppelin; -/// Cairo string for "deploy_account" +/// Cairo string for `deploy_account` const PREFIX_DEPLOY_ACCOUNT: Felt = Felt::from_raw([ 461298303000467581, 18446744073709551615, @@ -42,7 +42,7 @@ const QUERY_VERSION_THREE: Felt = Felt::from_raw([ 18446744073700081569, ]); -/// Cairo string for "STARKNET_CONTRACT_ADDRESS" +/// Cairo string for `STARKNET_CONTRACT_ADDRESS` const PREFIX_CONTRACT_ADDRESS: Felt = Felt::from_raw([ 533439743893157637, 8635008616843941496, @@ -58,55 +58,86 @@ const ADDR_BOUND: NonZeroFelt = NonZeroFelt::from_raw([ 18446743986131443745, ]); -/// This trait enables deploying account contracts using the `DeployAccount` transaction type. +/// Abstraction over different ways of deploying account contracts using the `DEPLOY_ACCOUNT` +/// transaction type. #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] pub trait AccountFactory: Sized { + /// The [`Provider`] type attached to this account factory. type Provider: Provider + Sync; + /// Possible errors for signing transactions. type SignError: Error + Send + Sync; + /// Gets the class hash of the account contract. fn class_hash(&self) -> Felt; + /// Gets the constructor calldata for the deployment transaction. fn calldata(&self) -> Vec; + /// Gets the chain ID of the target network. fn chain_id(&self) -> Felt; + /// Gets a reference to the attached [`Provider`] instance. fn provider(&self) -> &Self::Provider; + /// Whether the underlying signer implementation is interactive, such as a hardware wallet. + /// Implementations should return `true` if the signing operation is very expensive, even if not + /// strictly "interactive" as in requiring human input. + /// + /// This affects how an account factory makes decision on whether to request a real signature + /// for estimation/simulation purposes. + fn is_signer_interactive(&self) -> bool; + /// Block ID to use when estimating fees. fn block_id(&self) -> BlockId { BlockId::Tag(BlockTag::Latest) } + /// Signs an execution request to authorize an `DEPLOY_ACCOUNT` v1 transaction that pays + /// transaction fees in `ETH`. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_deployment_v1( &self, deployment: &RawAccountDeploymentV1, query_only: bool, ) -> Result, Self::SignError>; + /// Signs an execution request to authorize an `DEPLOY_ACCOUNT` v3 transaction that pays + /// transaction fees in `STRK`. + /// + /// If `query_only` is `true`, the commitment must be constructed in a way that a real state- + /// changing transaction cannot be authenticated. This is to prevent replay attacks. async fn sign_deployment_v3( &self, deployment: &RawAccountDeploymentV3, query_only: bool, ) -> Result, Self::SignError>; - fn deploy_v1(&self, salt: Felt) -> AccountDeploymentV1 { + /// Generates an instance of [`AccountDeploymentV1`] for sending `DEPLOY_ACCOUNT` v1 + /// transactions. Pays transaction fees in `ETH`. + fn deploy_v1(&self, salt: Felt) -> AccountDeploymentV1<'_, Self> { AccountDeploymentV1::new(salt, self) } - fn deploy_v3(&self, salt: Felt) -> AccountDeploymentV3 { + /// Generates an instance of [`AccountDeploymentV3`] for sending `DEPLOY_ACCOUNT` v3 + /// transactions. Pays transaction fees in `STRK`. + fn deploy_v3(&self, salt: Felt) -> AccountDeploymentV3<'_, Self> { AccountDeploymentV3::new(salt, self) } + /// Generates an instance of [`AccountDeploymentV1`] for sending `DEPLOY_ACCOUNT` v1 + /// transactions. Pays transaction fees in `ETH`. #[deprecated = "use version specific variants (`deploy_v1` & `deploy_v3`) instead"] - fn deploy(&self, salt: Felt) -> AccountDeploymentV1 { + fn deploy(&self, salt: Felt) -> AccountDeploymentV1<'_, Self> { self.deploy_v1(salt) } } /// Abstraction over `DEPLOY_ACCOUNT` transactions for account contract deployment. This struct uses /// v1 `DEPLOY_ACCOUNT` transactions under the hood, and hence pays transaction fees in ETH. To use -/// v3 transactions for STRK fee payment, use [AccountDeploymentV3] instead. +/// v3 transactions for STRK fee payment, use [`AccountDeploymentV3`] instead. /// /// An intermediate type allowing users to optionally specify `nonce` and/or `max_fee`. #[must_use] @@ -123,7 +154,7 @@ pub struct AccountDeploymentV1<'f, F> { /// Abstraction over `DEPLOY_ACCOUNT` transactions for account contract deployment. This struct uses /// v3 `DEPLOY_ACCOUNT` transactions under the hood, and hence pays transaction fees in STRK. To use -/// v1 transactions for ETH fee payment, use [AccountDeploymentV1] instead. +/// v1 transactions for ETH fee payment, use [`AccountDeploymentV1`] instead. /// /// This is an intermediate type allowing users to optionally specify `nonce`, `gas`, and/or /// `gas_price`. @@ -141,7 +172,7 @@ pub struct AccountDeploymentV3<'f, F> { gas_price_estimate_multiplier: f64, } -/// [AccountDeploymentV1] but with `nonce` and `max_fee` already determined. +/// [`AccountDeploymentV1`] but with `nonce` and `max_fee` already determined. #[derive(Debug, Clone)] pub struct RawAccountDeploymentV1 { salt: Felt, @@ -149,7 +180,7 @@ pub struct RawAccountDeploymentV1 { max_fee: Felt, } -/// [AccountDeploymentV3] but with `nonce`, `gas` and `gas_price` already determined. +/// [`AccountDeploymentV3`] but with `nonce`, `gas` and `gas_price` already determined. #[derive(Debug, Clone)] pub struct RawAccountDeploymentV3 { salt: Felt, @@ -158,32 +189,40 @@ pub struct RawAccountDeploymentV3 { gas_price: u128, } -/// [RawAccountDeploymentV1] but with a factory associated. +/// [`RawAccountDeploymentV1`] but with a factory associated. #[derive(Debug)] pub struct PreparedAccountDeploymentV1<'f, F> { factory: &'f F, inner: RawAccountDeploymentV1, } -/// [RawAccountDeploymentV3] but with a factory associated. +/// [`RawAccountDeploymentV3`] but with a factory associated. #[derive(Debug)] pub struct PreparedAccountDeploymentV3<'f, F> { factory: &'f F, inner: RawAccountDeploymentV3, } +/// Errors using Starknet account factories. #[derive(Debug, thiserror::Error)] pub enum AccountFactoryError { + /// An error is encountered when signing a request. #[error(transparent)] Signing(S), + /// An error is encountered with communicating with the network. #[error(transparent)] Provider(ProviderError), + /// Transaction fee calculation overflow. #[error("fee calculation overflow")] FeeOutOfRange, } impl<'f, F> AccountDeploymentV1<'f, F> { - pub fn new(salt: Felt, factory: &'f F) -> Self { + /// Constructs a new [`AccountDeploymentV1`]. + /// + /// Users would typically use [`deploy_v1`](fn.deploy_v1) on an [`AccountFactory`] instead of + /// directly calling this method. + pub const fn new(salt: Felt, factory: &'f F) -> Self { Self { factory, salt, @@ -193,21 +232,26 @@ impl<'f, F> AccountDeploymentV1<'f, F> { } } - pub fn nonce(self, nonce: Felt) -> Self { + /// Returns a new [`AccountDeploymentV1`] with the `nonce`. + pub const fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), ..self } } - pub fn max_fee(self, max_fee: Felt) -> Self { + /// Returns a new [`AccountDeploymentV1`] with the `max_fee`. + pub const fn max_fee(self, max_fee: Felt) -> Self { Self { max_fee: Some(max_fee), ..self } } - pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self { + /// Returns a new [`AccountDeploymentV1`] with the fee estimate multiplier. The multiplier is + /// used when transaction fee is not manually specified and must be fetched from a [`Provider`] + /// instead. + pub const fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self { Self { fee_estimate_multiplier, ..self @@ -215,7 +259,7 @@ impl<'f, F> AccountDeploymentV1<'f, F> { } /// Calling this function after manually specifying `nonce` and `max_fee` turns - /// [AccountDeploymentV1] into [PreparedAccountDeploymentV1]. Returns `Err` if either field is + /// [`AccountDeploymentV1`] into [`PreparedAccountDeploymentV1`]. Returns `Err` if either field is /// `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; @@ -233,7 +277,11 @@ impl<'f, F> AccountDeploymentV1<'f, F> { } impl<'f, F> AccountDeploymentV3<'f, F> { - pub fn new(salt: Felt, factory: &'f F) -> Self { + /// Constructs a new [`AccountDeploymentV3`]. + /// + /// Users would typically use [`deploy_v3`](fn.deploy_v3) on an [`AccountFactory`] instead of + /// directly calling this method. + pub const fn new(salt: Felt, factory: &'f F) -> Self { Self { factory, salt, @@ -245,35 +293,44 @@ impl<'f, F> AccountDeploymentV3<'f, F> { } } - pub fn nonce(self, nonce: Felt) -> Self { + /// Returns a new [`AccountDeploymentV3`] with the `nonce`. + pub const fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), ..self } } - pub fn gas(self, gas: u64) -> Self { + /// Returns a new [`AccountDeploymentV3`] with the `gas`. + pub const fn gas(self, gas: u64) -> Self { Self { gas: Some(gas), ..self } } - pub fn gas_price(self, gas_price: u128) -> Self { + /// Returns a new [`AccountDeploymentV3`] with the `gas_price`. + pub const fn gas_price(self, gas_price: u128) -> Self { Self { gas_price: Some(gas_price), ..self } } - pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self { + /// Returns a new [`AccountDeploymentV3`] with the gas amount estimate multiplier. The + /// multiplier is used when the gas amount is not manually specified and must be fetched from a + /// [`Provider`] instead. + pub const fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self { Self { gas_estimate_multiplier, ..self } } - pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self { + /// Returns a new [`AccountDeploymentV3`] with the gas price estimate multiplier. The + /// multiplier is used when the gas price is not manually specified and must be fetched from a + /// [`Provider`] instead. + pub const fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self { Self { gas_price_estimate_multiplier, ..self @@ -281,7 +338,7 @@ impl<'f, F> AccountDeploymentV3<'f, F> { } /// Calling this function after manually specifying `nonce` and `max_fee` turns - /// [AccountDeploymentV3] into [PreparedAccountDeploymentV3]. Returns `Err` if either field is + /// [`AccountDeploymentV3`] into [`PreparedAccountDeploymentV3`]. Returns `Err` if either field is /// `None`. pub fn prepared(self) -> Result, NotPreparedError> { let nonce = self.nonce.ok_or(NotPreparedError)?; @@ -313,6 +370,8 @@ where ) } + /// Fetches the next available nonce from a [`Provider`]. In most cases this would be `0` but + /// it can also be non-zero if previous reverted deployment attempts exist. pub async fn fetch_nonce(&self) -> Result { match self .factory @@ -326,6 +385,7 @@ where } } + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -339,6 +399,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -357,6 +419,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send( &self, ) -> Result> { @@ -413,6 +476,8 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self.factory.is_signer_interactive(); + let prepared = PreparedAccountDeploymentV1 { factory: self.factory, inner: RawAccountDeploymentV1 { @@ -422,7 +487,7 @@ where }, }; let deploy = prepared - .get_deploy_request(true) + .get_deploy_request(true, skip_signature) .await .map_err(AccountFactoryError::Signing)?; @@ -432,7 +497,13 @@ where BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V1( deploy, )), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.factory.block_id(), ) .await @@ -445,6 +516,16 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self.factory.is_signer_interactive() { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedAccountDeploymentV1 { factory: self.factory, inner: RawAccountDeploymentV1 { @@ -454,7 +535,7 @@ where }, }; let deploy = prepared - .get_deploy_request(true) + .get_deploy_request(true, skip_signature) .await .map_err(AccountFactoryError::Signing)?; @@ -494,6 +575,8 @@ where ) } + /// Fetches the next available nonce from a [`Provider`]. In most cases this would be `0` but + /// it can also be non-zero if previous reverted deployment attempts exist. pub async fn fetch_nonce(&self) -> Result { match self .factory @@ -507,6 +590,7 @@ where } } + /// Estimates transaction fees from a [`Provider`]. pub async fn estimate_fee(&self) -> Result> { // Resolves nonce let nonce = match self.nonce { @@ -520,6 +604,8 @@ where self.estimate_fee_with_nonce(nonce).await } + /// Simulates the transaction from a [`Provider`]. Transaction validation and fee transfer can + /// be skipped. pub async fn simulate( &self, skip_validate: bool, @@ -538,6 +624,7 @@ where .await } + /// Signs and broadcasts the transaction to the network. pub async fn send( &self, ) -> Result> { @@ -607,8 +694,8 @@ where let gas_price = u64::from_le_bytes(gas_price_bytes[..8].try_into().unwrap()); - ((((overall_fee + gas_price - 1) / gas_price) as f64) - * self.gas_estimate_multiplier) as u64 + ((overall_fee.div_ceil(gas_price) as f64) * self.gas_estimate_multiplier) + as u64 } }; @@ -645,6 +732,8 @@ where &self, nonce: Felt, ) -> Result> { + let skip_signature = self.factory.is_signer_interactive(); + let prepared = PreparedAccountDeploymentV3 { factory: self.factory, inner: RawAccountDeploymentV3 { @@ -655,7 +744,7 @@ where }, }; let deploy = prepared - .get_deploy_request(true) + .get_deploy_request(true, skip_signature) .await .map_err(AccountFactoryError::Signing)?; @@ -665,7 +754,13 @@ where BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V3( deploy, )), - [], + if skip_signature { + // Validation would fail since real signature was not requested + vec![SimulationFlagForEstimateFee::SkipValidate] + } else { + // With the correct signature in place, run validation for accurate results + vec![] + }, self.factory.block_id(), ) .await @@ -678,6 +773,16 @@ where skip_validate: bool, skip_fee_charge: bool, ) -> Result> { + let skip_signature = if self.factory.is_signer_interactive() { + // If signer is interactive, we would try to minimize signing requests. However, if the + // caller has decided to not skip validation, it's best we still request a real + // signature, as otherwise the simulation would most likely fail. + skip_validate + } else { + // Signing with non-interactive signers is cheap so always request signatures. + false + }; + let prepared = PreparedAccountDeploymentV3 { factory: self.factory, inner: RawAccountDeploymentV3 { @@ -688,7 +793,7 @@ where }, }; let deploy = prepared - .get_deploy_request(true) + .get_deploy_request(true, skip_signature) .await .map_err(AccountFactoryError::Signing)?; @@ -716,39 +821,48 @@ where } impl RawAccountDeploymentV1 { - pub fn salt(&self) -> Felt { + /// Gets the `salt` of the deployment request. + pub const fn salt(&self) -> Felt { self.salt } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the deployment request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn max_fee(&self) -> Felt { + /// Gets the `max_fee` of the deployment request. + pub const fn max_fee(&self) -> Felt { self.max_fee } } impl RawAccountDeploymentV3 { - pub fn salt(&self) -> Felt { + /// Gets the `salt` of the deployment request. + pub const fn salt(&self) -> Felt { self.salt } - pub fn nonce(&self) -> Felt { + /// Gets the `nonce` of the deployment request. + pub const fn nonce(&self) -> Felt { self.nonce } - pub fn gas(&self) -> u64 { + /// Gets the `gas` of the deployment request. + pub const fn gas(&self) -> u64 { self.gas } - pub fn gas_price(&self) -> u128 { + /// Gets the `gas_price` of the deployment request. + pub const fn gas_price(&self) -> u128 { self.gas_price } } impl<'f, F> PreparedAccountDeploymentV1<'f, F> { - pub fn from_raw(raw_deployment: RawAccountDeploymentV1, factory: &'f F) -> Self { + /// Constructs [`PreparedAccountDeploymentV1`] by attaching a factory to + /// [`RawAccountDeploymentV1`]. + pub const fn from_raw(raw_deployment: RawAccountDeploymentV1, factory: &'f F) -> Self { Self { factory, inner: raw_deployment, @@ -757,7 +871,9 @@ impl<'f, F> PreparedAccountDeploymentV1<'f, F> { } impl<'f, F> PreparedAccountDeploymentV3<'f, F> { - pub fn from_raw(raw_deployment: RawAccountDeploymentV3, factory: &'f F) -> Self { + /// Constructs [`PreparedAccountDeploymentV3`] by attaching a factory to + /// [`RawAccountDeploymentV3`]. + pub const fn from_raw(raw_deployment: RawAccountDeploymentV3, factory: &'f F) -> Self { Self { factory, inner: raw_deployment, @@ -765,7 +881,7 @@ impl<'f, F> PreparedAccountDeploymentV3<'f, F> { } } -impl<'f, F> PreparedAccountDeploymentV1<'f, F> +impl PreparedAccountDeploymentV1<'_, F> where F: AccountFactory, { @@ -778,6 +894,7 @@ where ) } + /// Calculates transaction hash given `query_only`. pub fn transaction_hash(&self, query_only: bool) -> Felt { let mut calldata_to_hash = vec![self.factory.class_hash(), self.inner.salt]; calldata_to_hash.append(&mut self.factory.calldata()); @@ -798,11 +915,12 @@ where ]) } + /// Signs and broadcasts the transaction to the network. pub async fn send( &self, ) -> Result> { let tx_request = self - .get_deploy_request(false) + .get_deploy_request(false, false) .await .map_err(AccountFactoryError::Signing)?; self.factory @@ -815,15 +933,17 @@ where async fn get_deploy_request( &self, query_only: bool, + skip_signature: bool, ) -> Result { - let signature = self - .factory - .sign_deployment_v1(&self.inner, query_only) - .await?; - Ok(BroadcastedDeployAccountTransactionV1 { max_fee: self.inner.max_fee, - signature, + signature: if skip_signature { + vec![] + } else { + self.factory + .sign_deployment_v1(&self.inner, query_only) + .await? + }, nonce: self.inner.nonce, contract_address_salt: self.inner.salt, constructor_calldata: self.factory.calldata(), @@ -833,7 +953,7 @@ where } } -impl<'f, F> PreparedAccountDeploymentV3<'f, F> +impl PreparedAccountDeploymentV3<'_, F> where F: AccountFactory, { @@ -846,6 +966,7 @@ where ) } + /// Calculates transaction hash given `query_only`. pub fn transaction_hash(&self, query_only: bool) -> Felt { let mut hasher = PoseidonHasher::new(); @@ -907,11 +1028,12 @@ where hasher.finalize() } + /// Signs and broadcasts the transaction to the network. pub async fn send( &self, ) -> Result> { let tx_request = self - .get_deploy_request(false) + .get_deploy_request(false, false) .await .map_err(AccountFactoryError::Signing)?; self.factory @@ -924,14 +1046,16 @@ where async fn get_deploy_request( &self, query_only: bool, + skip_signature: bool, ) -> Result { - let signature = self - .factory - .sign_deployment_v3(&self.inner, query_only) - .await?; - Ok(BroadcastedDeployAccountTransactionV3 { - signature, + signature: if skip_signature { + vec![] + } else { + self.factory + .sign_deployment_v3(&self.inner, query_only) + .await? + }, nonce: self.inner.nonce, contract_address_salt: self.inner.salt, constructor_calldata: self.factory.calldata(), diff --git a/starknet-accounts/src/factory/open_zeppelin.rs b/starknet-accounts/src/factory/open_zeppelin.rs index 72882e45..6c20a9f4 100644 --- a/starknet-accounts/src/factory/open_zeppelin.rs +++ b/starknet-accounts/src/factory/open_zeppelin.rs @@ -6,8 +6,10 @@ use crate::{ use async_trait::async_trait; use starknet_core::types::{BlockId, BlockTag, Felt}; use starknet_providers::Provider; -use starknet_signers::Signer; +use starknet_signers::{Signer, SignerInteractivityContext}; +/// [`AccountFactory`] implementation for deploying `OpenZeppelin` account contracts. +#[derive(Debug)] pub struct OpenZeppelinAccountFactory { class_hash: Felt, chain_id: Felt, @@ -21,6 +23,7 @@ impl OpenZeppelinAccountFactory where S: Signer, { + /// Constructs a new [`OpenZeppelinAccountFactory`]. pub async fn new( class_hash: Felt, chain_id: Felt, @@ -38,6 +41,7 @@ where }) } + /// Sets a new block ID to run queries against. pub fn set_block_id(&mut self, block_id: BlockId) -> &Self { self.block_id = block_id; self @@ -70,6 +74,11 @@ where &self.provider } + fn is_signer_interactive(&self) -> bool { + self.signer + .is_interactive(SignerInteractivityContext::Other) + } + fn block_id(&self) -> BlockId { self.block_id } diff --git a/starknet-accounts/src/lib.rs b/starknet-accounts/src/lib.rs index b0f4483f..869e5c90 100644 --- a/starknet-accounts/src/lib.rs +++ b/starknet-accounts/src/lib.rs @@ -1,3 +1,7 @@ +//! Library for deploying and using Starknet account contracts. + +#![deny(missing_docs)] + mod account; pub use account::{ Account, AccountError, ConnectedAccount, DeclarationV2, DeclarationV3, ExecutionEncoder, @@ -6,9 +10,6 @@ pub use account::{ RawDeclarationV3, RawExecutionV1, RawExecutionV3, RawLegacyDeclaration, }; -mod call; -pub use call::Call; - mod factory; pub use factory::{ argent::ArgentAccountFactory, open_zeppelin::OpenZeppelinAccountFactory, AccountDeploymentV1, @@ -16,9 +17,11 @@ pub use factory::{ PreparedAccountDeploymentV3, RawAccountDeploymentV1, RawAccountDeploymentV3, }; +/// Module containing types for using an account contract with only one signer. pub mod single_owner; pub use single_owner::{ExecutionEncoding, SingleOwnerAccount}; +/// Error when calling `prepared()` on a type when not all fields are populated. #[derive(Debug, thiserror::Error)] #[error("Not all fields are prepared")] pub struct NotPreparedError; diff --git a/starknet-accounts/src/single_owner.rs b/starknet-accounts/src/single_owner.rs index 9aa7a5b7..d954bd70 100644 --- a/starknet-accounts/src/single_owner.rs +++ b/starknet-accounts/src/single_owner.rs @@ -1,13 +1,15 @@ use crate::{ - Account, Call, ConnectedAccount, ExecutionEncoder, RawDeclarationV2, RawDeclarationV3, + Account, ConnectedAccount, ExecutionEncoder, RawDeclarationV2, RawDeclarationV3, RawExecutionV1, RawExecutionV3, RawLegacyDeclaration, }; use async_trait::async_trait; -use starknet_core::types::{contract::ComputeClassHashError, BlockId, BlockTag, Felt}; +use starknet_core::types::{contract::ComputeClassHashError, BlockId, BlockTag, Call, Felt}; use starknet_providers::Provider; -use starknet_signers::Signer; +use starknet_signers::{Signer, SignerInteractivityContext}; +/// A generic [`Account`] implementation for controlling account contracts that only have one signer +/// using ECDSA the STARK curve. #[derive(Debug, Clone)] pub struct SingleOwnerAccount where @@ -22,10 +24,13 @@ where encoding: ExecutionEncoding, } +/// Errors signing an execution/declaration request. #[derive(Debug, thiserror::Error)] pub enum SignError { + /// An error encountered by the signer implementation. #[error(transparent)] Signer(S), + /// Failure to compute the class hash of the class being declared. #[error(transparent)] ClassHash(ComputeClassHashError), } @@ -47,14 +52,14 @@ where { /// Create a new account controlled by a single signer. /// - /// ### Arguments + /// ### Parameters /// - /// * `provider`: A `Provider` implementation that provides access to the Starknet network. - /// * `signer`: A `Signer` implementation that can generate valid signatures for this account. - /// * `address`: Account contract address. - /// * `chain_id`: Network chain ID. - /// * `encoding`: How `__execute__` calldata should be encoded. - pub fn new( + /// - `provider`: A `Provider` implementation that provides access to the Starknet network. + /// - `signer`: A `Signer` implementation that can generate valid signatures for this account. + /// - `address`: Account contract address. + /// - `chain_id`: Network chain ID. + /// - `encoding`: How `__execute__` calldata should be encoded. + pub const fn new( provider: P, signer: S, address: Felt, @@ -71,6 +76,7 @@ where } } + /// Sets a new block ID to run queries against. pub fn set_block_id(&mut self, block_id: BlockId) -> &Self { self.block_id = block_id; self @@ -170,6 +176,10 @@ where Ok(vec![signature.r, signature.s]) } + + fn is_signer_interactive(&self, context: SignerInteractivityContext<'_>) -> bool { + self.signer.is_interactive(context) + } } impl ExecutionEncoder for SingleOwnerAccount @@ -183,13 +193,13 @@ where match self.encoding { ExecutionEncoding::Legacy => { let mut concated_calldata: Vec = vec![]; - for call in calls.iter() { + for call in calls { execute_calldata.push(call.to); // to execute_calldata.push(call.selector); // selector execute_calldata.push(concated_calldata.len().into()); // data_offset execute_calldata.push(call.calldata.len().into()); // data_len - for item in call.calldata.iter() { + for item in &call.calldata { concated_calldata.push(*item); } } @@ -198,7 +208,7 @@ where execute_calldata.extend_from_slice(&concated_calldata); } ExecutionEncoding::New => { - for call in calls.iter() { + for call in calls { execute_calldata.push(call.to); // to execute_calldata.push(call.selector); // selector diff --git a/starknet-accounts/tests/single_owner_account.rs b/starknet-accounts/tests/single_owner_account.rs index c779c491..8b1eb038 100644 --- a/starknet-accounts/tests/single_owner_account.rs +++ b/starknet-accounts/tests/single_owner_account.rs @@ -1,5 +1,5 @@ use starknet_accounts::{ - Account, AccountError, Call, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount, + Account, AccountError, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount, }; use starknet_core::{ types::{ @@ -7,7 +7,7 @@ use starknet_core::{ legacy::{LegacyContractClass, RawLegacyAbiEntry, RawLegacyFunction}, SierraClass, }, - BlockId, BlockTag, Felt, StarknetError, + BlockId, BlockTag, Call, Felt, StarknetError, }, utils::get_selector_from_name, }; @@ -32,7 +32,7 @@ fn create_sequencer_client() -> SequencerGatewayProvider { fn create_jsonrpc_client() -> JsonRpcClient { let rpc_url = std::env::var("STARKNET_RPC") - .unwrap_or("https://juno.rpc.sepolia.starknet.rs/rpc/v0_7".into()); + .unwrap_or_else(|_| "https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_7".into()); JsonRpcClient::new(HttpTransport::new(url::Url::parse(&rpc_url).unwrap())) } @@ -354,8 +354,8 @@ async fn can_execute_eth_transfer_invoke_v3_inner( selector: get_selector_from_name("transfer").unwrap(), calldata: vec![Felt::from_hex("0x1234").unwrap(), Felt::ONE, Felt::ZERO], }]) - .gas(200000) - .gas_price(500000000000000) + .gas(100000) + .gas_price(900000000000000) .send() .await .unwrap(); @@ -544,8 +544,8 @@ async fn can_declare_cairo1_contract_v3_inner( Arc::new(flattened_class), Felt::from_hex(&hashes.compiled_class_hash).unwrap(), ) - .gas(200000) - .gas_price(500000000000000) + .gas(100000) + .gas_price(900000000000000) .send() .await .unwrap(); diff --git a/starknet-contract/Cargo.toml b/starknet-contract/Cargo.toml index 290e3e55..82ac3cbc 100644 --- a/starknet-contract/Cargo.toml +++ b/starknet-contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-contract" -version = "0.10.0" +version = "0.11.0" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -14,16 +14,19 @@ keywords = ["ethereum", "starknet", "web3"] exclude = ["test-data/**"] [dependencies] -starknet-core = { version = "0.11.1", path = "../starknet-core" } -starknet-providers = { version = "0.11.0", path = "../starknet-providers" } -starknet-accounts = { version = "0.10.0", path = "../starknet-accounts" } +starknet-core = { version = "0.12.0", path = "../starknet-core" } +starknet-providers = { version = "0.12.0", path = "../starknet-providers" } +starknet-accounts = { version = "0.11.0", path = "../starknet-accounts" } serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" -serde_with = "2.3.2" +serde_with = "3.9.0" thiserror = "1.0.40" [dev-dependencies] rand = { version = "0.8.5", features=["std_rng"] } -starknet-signers = { version = "0.9.0", path = "../starknet-signers" } +starknet-signers = { version = "0.10.0", path = "../starknet-signers" } tokio = { version = "1.27.0", features = ["full"] } url = "2.3.1" + +[lints] +workspace = true diff --git a/starknet-contract/src/factory.rs b/starknet-contract/src/factory.rs index 0cbd6065..a3a8ff64 100644 --- a/starknet-contract/src/factory.rs +++ b/starknet-contract/src/factory.rs @@ -1,6 +1,6 @@ -use starknet_accounts::{Account, AccountError, Call, ConnectedAccount, ExecutionV1, ExecutionV3}; +use starknet_accounts::{Account, AccountError, ConnectedAccount, ExecutionV1, ExecutionV3}; use starknet_core::{ - types::{FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction}, + types::{Call, FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction}, utils::{get_udc_deployed_address, UdcUniqueSettings, UdcUniqueness}, }; @@ -20,6 +20,9 @@ const SELECTOR_DEPLOYCONTRACT: Felt = Felt::from_raw([ 18249998464715511309, ]); +/// A contract factory that acts as a blueprint for deploying Starknet smart contracts using the +/// Universal Deployer Contract. +#[derive(Debug)] pub struct ContractFactory { class_hash: Felt, udc_address: Felt, @@ -28,8 +31,9 @@ pub struct ContractFactory { /// Abstraction over contract deployment via the UDC. This type uses `INVOKE` v1 transactions under /// the hood, and hence pays transaction fees in ETH. To use v3 transactions for STRK fee payment, -/// use [DeploymentV3] instead. +/// use [`DeploymentV3`] instead. #[must_use] +#[derive(Debug)] pub struct DeploymentV1<'f, A> { factory: &'f ContractFactory, constructor_calldata: Vec, @@ -43,8 +47,9 @@ pub struct DeploymentV1<'f, A> { /// Abstraction over contract deployment via the UDC. This type uses `INVOKE` v3 transactions under /// the hood, and hence pays transaction fees in STRK. To use v1 transactions for ETH fee payment, -/// use [DeploymentV1] instead. +/// use [`DeploymentV1`] instead. #[must_use] +#[derive(Debug)] pub struct DeploymentV3<'f, A> { factory: &'f ContractFactory, constructor_calldata: Vec, @@ -59,11 +64,16 @@ pub struct DeploymentV3<'f, A> { } impl ContractFactory { - pub fn new(class_hash: Felt, account: A) -> Self { + /// Constructs a new [`ContractFactory`] from a class hash and an account. + /// + /// The [`ContractFactory`] created uses the default address for the Universal Deployer + /// Contract. To use a custom UDC deployment, use [`new_with_udc`](fn.new_with_udc) instead. + pub const fn new(class_hash: Felt, account: A) -> Self { Self::new_with_udc(class_hash, account, UDC_ADDRESS) } - pub fn new_with_udc(class_hash: Felt, account: A, udc_address: Felt) -> Self { + /// Constructs a new [`ContractFactory`] with a custom Universal Deployer Contract address. + pub const fn new_with_udc(class_hash: Felt, account: A, udc_address: Felt) -> Self { Self { class_hash, udc_address, @@ -76,12 +86,14 @@ impl ContractFactory where A: Account, { - pub fn deploy_v1( + /// Generates an instance of [`DeploymentV1`] for sending `INVOKE` v1 transactions for the + /// contract deployment. Pays transaction fees in `ETH`. + pub const fn deploy_v1( &self, constructor_calldata: Vec, salt: Felt, unique: bool, - ) -> DeploymentV1 { + ) -> DeploymentV1<'_, A> { DeploymentV1 { factory: self, constructor_calldata, @@ -93,12 +105,14 @@ where } } - pub fn deploy_v3( + /// Generates an instance of [`DeploymentV3`] for sending `INVOKE` v3 transactions for the + /// contract deployment. Pays transaction fees in `STRK`. + pub const fn deploy_v3( &self, constructor_calldata: Vec, salt: Felt, unique: bool, - ) -> DeploymentV3 { + ) -> DeploymentV3<'_, A> { DeploymentV3 { factory: self, constructor_calldata, @@ -112,18 +126,21 @@ where } } + /// Generates an instance of [`DeploymentV1`] for sending `INVOKE` v1 transactions for the + /// contract deployment. Pays transaction fees in `ETH`. #[deprecated = "use version specific variants (`deploy_v1` & `deploy_v3`) instead"] - pub fn deploy( + pub const fn deploy( &self, constructor_calldata: Vec, salt: Felt, unique: bool, - ) -> DeploymentV1 { + ) -> DeploymentV1<'_, A> { self.deploy_v1(constructor_calldata, salt, unique) } } -impl<'f, A> DeploymentV1<'f, A> { +impl DeploymentV1<'_, A> { + /// Returns a new [`DeploymentV1`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -131,6 +148,7 @@ impl<'f, A> DeploymentV1<'f, A> { } } + /// Returns a new [`DeploymentV1`] with the `max_fee`. pub fn max_fee(self, max_fee: Felt) -> Self { Self { max_fee: Some(max_fee), @@ -138,6 +156,9 @@ impl<'f, A> DeploymentV1<'f, A> { } } + /// Returns a new [`DeploymentV1`] with the fee estimate multiplier. The multiplier is used + /// when transaction fee is not manually specified and must be fetched from a + /// [`Provider`](starknet_providers::Provider) instead. pub fn fee_estimate_multiplier(self, fee_estimate_multiplier: f64) -> Self { Self { fee_estimate_multiplier, @@ -146,7 +167,8 @@ impl<'f, A> DeploymentV1<'f, A> { } } -impl<'f, A> DeploymentV3<'f, A> { +impl DeploymentV3<'_, A> { + /// Returns a new [`DeploymentV3`] with the `nonce`. pub fn nonce(self, nonce: Felt) -> Self { Self { nonce: Some(nonce), @@ -154,6 +176,7 @@ impl<'f, A> DeploymentV3<'f, A> { } } + /// Returns a new [`DeploymentV3`] with the `gas`. pub fn gas(self, gas: u64) -> Self { Self { gas: Some(gas), @@ -161,6 +184,7 @@ impl<'f, A> DeploymentV3<'f, A> { } } + /// Returns a new [`DeploymentV3`] with the `gas_price`. pub fn gas_price(self, gas_price: u128) -> Self { Self { gas_price: Some(gas_price), @@ -168,6 +192,9 @@ impl<'f, A> DeploymentV3<'f, A> { } } + /// Returns a new [`DeploymentV3`] with the gas amount estimate multiplier. The multiplier is + /// used when the gas amount is not manually specified and must be fetched from a + /// [`Provider`](starknet_providers::Provider) instead. pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self { Self { gas_estimate_multiplier, @@ -175,6 +202,9 @@ impl<'f, A> DeploymentV3<'f, A> { } } + /// Returns a new [`DeploymentV3`] with the gas price estimate multiplier. The multiplier is + /// used when the gas price is not manually specified and must be fetched from a + /// [`Provider`](starknet_providers::Provider) instead. pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self { Self { gas_price_estimate_multiplier, @@ -183,7 +213,7 @@ impl<'f, A> DeploymentV3<'f, A> { } } -impl<'f, A> DeploymentV1<'f, A> +impl DeploymentV1<'_, A> where A: Account, { @@ -205,7 +235,7 @@ where } } -impl<'f, A> DeploymentV3<'f, A> +impl DeploymentV3<'_, A> where A: Account, { @@ -227,50 +257,58 @@ where } } -impl<'f, A> DeploymentV1<'f, A> +impl DeploymentV1<'_, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`](starknet_providers::Provider). pub async fn estimate_fee(&self) -> Result> { - let execution: ExecutionV1 = self.into(); + let execution: ExecutionV1<'_, A> = self.into(); execution.estimate_fee().await } + /// Simulates the transaction from a [`Provider`](starknet_providers::Provider). Transaction + /// validation and fee transfer can be skipped. pub async fn simulate( &self, skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let execution: ExecutionV1 = self.into(); + let execution: ExecutionV1<'_, A> = self.into(); execution.simulate(skip_validate, skip_fee_charge).await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { - let execution: ExecutionV1 = self.into(); + let execution: ExecutionV1<'_, A> = self.into(); execution.send().await } } -impl<'f, A> DeploymentV3<'f, A> +impl DeploymentV3<'_, A> where A: ConnectedAccount + Sync, { + /// Estimates transaction fees from a [`Provider`](starknet_providers::Provider). pub async fn estimate_fee(&self) -> Result> { - let execution: ExecutionV3 = self.into(); + let execution: ExecutionV3<'_, A> = self.into(); execution.estimate_fee().await } + /// Simulates the transaction from a [`Provider`](starknet_providers::Provider). Transaction + /// validation and fee transfer can be skipped. pub async fn simulate( &self, skip_validate: bool, skip_fee_charge: bool, ) -> Result> { - let execution: ExecutionV3 = self.into(); + let execution: ExecutionV3<'_, A> = self.into(); execution.simulate(skip_validate, skip_fee_charge).await } + /// Signs and broadcasts the transaction to the network. pub async fn send(&self) -> Result> { - let execution: ExecutionV3 = self.into(); + let execution: ExecutionV3<'_, A> = self.into(); execution.send().await } } diff --git a/starknet-contract/src/lib.rs b/starknet-contract/src/lib.rs index cec06190..2a37967e 100644 --- a/starknet-contract/src/lib.rs +++ b/starknet-contract/src/lib.rs @@ -1,2 +1,12 @@ +//! Library for deploying and interacting with Starknet contracts. +//! +//! Currently, this crate only provides a single type [`ContractFactory`] for deploying contracts +//! using the Universal Deployer Contract. +//! +//! In the future, features like ABI-based contract binding generation will be added to allow type- +//! safe interaction with Starknet smart contracts. + +#![deny(missing_docs)] + mod factory; -pub use factory::ContractFactory; +pub use factory::{ContractFactory, DeploymentV1, DeploymentV3}; diff --git a/starknet-contract/tests/contract_deployment.rs b/starknet-contract/tests/contract_deployment.rs index 14ab3f26..d656776b 100644 --- a/starknet-contract/tests/contract_deployment.rs +++ b/starknet-contract/tests/contract_deployment.rs @@ -17,7 +17,7 @@ const CHAIN_ID: Felt = Felt::from_raw([ #[tokio::test] async fn can_deploy_contract_to_alpha_sepolia_with_invoke_v1() { let rpc_url = std::env::var("STARKNET_RPC") - .unwrap_or("https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_6".into()); + .unwrap_or_else(|_| "https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_6".into()); let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(&rpc_url).unwrap())); let signer = LocalWallet::from(SigningKey::from_secret_scalar( Felt::from_hex("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(), @@ -55,7 +55,7 @@ async fn can_deploy_contract_to_alpha_sepolia_with_invoke_v1() { #[tokio::test] async fn can_deploy_contract_to_alpha_sepolia_with_invoke_v3() { let rpc_url = std::env::var("STARKNET_RPC") - .unwrap_or("https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_6".into()); + .unwrap_or_else(|_| "https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_6".into()); let provider = JsonRpcClient::new(HttpTransport::new(Url::parse(&rpc_url).unwrap())); let signer = LocalWallet::from(SigningKey::from_secret_scalar( Felt::from_hex("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap(), @@ -80,8 +80,8 @@ async fn can_deploy_contract_to_alpha_sepolia_with_invoke_v3() { let result = factory .deploy_v3(vec![Felt::ONE], Felt::from_bytes_be(&salt_buffer), true) - .gas(200000) - .gas_price(500000000000000) + .gas(100000) + .gas_price(900000000000000) .send() .await; diff --git a/starknet-core-derive/Cargo.toml b/starknet-core-derive/Cargo.toml new file mode 100644 index 00000000..bcc687fa --- /dev/null +++ b/starknet-core-derive/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "starknet-core-derive" +version = "0.1.0" +authors = ["Jonathan LEI "] +license = "MIT OR Apache-2.0" +edition = "2021" +readme = "README.md" +repository = "https://github.com/xJonathanLEI/starknet-rs" +homepage = "https://starknet.rs/" +description = """ +Procedural macros for `starknet-core` +""" +keywords = ["ethereum", "starknet", "web3"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.88" +quote = "1.0.37" +syn = "2.0.15" + +[features] +default = [] +import_from_starknet = [] + +[lints] +workspace = true diff --git a/starknet-core-derive/README.md b/starknet-core-derive/README.md new file mode 100644 index 00000000..42911dd7 --- /dev/null +++ b/starknet-core-derive/README.md @@ -0,0 +1,14 @@ +# Procedural macros for `starknet-core` + +This crate provides procedural macros for deriving the `Encode` and `Decode` traits from `starknet-core`. This allows defining a type like: + +```rust +#[derive(Debug, PartialEq, Eq, Decode, Encode)] +struct CairoType { + a: Felt, + b: U256, + c: bool, +} +``` + +and using the `::encode()` and `::decode()` methods, without manually implementing the corresponding traits. diff --git a/starknet-core-derive/src/lib.rs b/starknet-core-derive/src/lib.rs new file mode 100644 index 00000000..b7aadf85 --- /dev/null +++ b/starknet-core-derive/src/lib.rs @@ -0,0 +1,373 @@ +//! Procedural derive macros for the `starknet-core` crate. + +#![deny(missing_docs)] + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{ + parse::{Error as ParseError, Parse, ParseStream}, + parse_macro_input, DeriveInput, Fields, LitInt, LitStr, Meta, Token, +}; + +#[derive(Default)] +struct Args { + core: Option, +} + +impl Args { + fn merge(&mut self, other: Self) { + if let Some(core) = other.core { + if self.core.is_some() { + panic!("starknet attribute `core` defined more than once"); + } else { + self.core = Some(core); + } + } + } +} + +impl Parse for Args { + fn parse(input: ParseStream<'_>) -> Result { + let mut core: Option = None; + + while !input.is_empty() { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::core) { + let _ = input.parse::()?; + let _ = input.parse::()?; + let value = input.parse::()?; + + match core { + Some(_) => { + return Err(ParseError::new( + Span::call_site(), + "starknet attribute `core` defined more than once", + )) + } + None => { + core = Some(value); + } + } + } else { + return Err(lookahead.error()); + } + } + + Ok(Self { core }) + } +} + +mod kw { + syn::custom_keyword!(core); +} + +/// Derives the `Encode` trait. +#[proc_macro_derive(Encode, attributes(starknet))] +pub fn derive_encode(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + let ident = &input.ident; + + let core = derive_core_path(&input); + + let impl_block = match input.data { + syn::Data::Struct(data) => { + let field_impls = data.fields.iter().enumerate().map(|(ind_field, field)| { + let field_ident = match &field.ident { + Some(field_ident) => quote! { self.#field_ident }, + None => { + let ind_field = syn::Index::from(ind_field); + quote! { self.#ind_field } + } + }; + let field_type = &field.ty; + + quote! { + <#field_type as #core::codec::Encode>::encode(&#field_ident, writer)?; + } + }); + + quote! { + #(#field_impls)* + } + } + syn::Data::Enum(data) => { + let variant_impls = + data.variants + .iter() + .enumerate() + .map(|(ind_variant, variant)| { + let variant_ident = &variant.ident; + let ind_variant = int_to_felt(ind_variant, &core); + + match &variant.fields { + Fields::Named(fields_named) => { + let names = fields_named + .named + .iter() + .map(|field| field.ident.as_ref().unwrap()); + + let field_impls = fields_named.named.iter().map(|field| { + let field_ident = field.ident.as_ref().unwrap(); + let field_type = &field.ty; + + quote! { + <#field_type as #core::codec::Encode> + ::encode(#field_ident, writer)?; + } + }); + + quote! { + Self::#variant_ident { #(#names),* } => { + writer.write(#ind_variant); + #(#field_impls)* + }, + } + } + Fields::Unnamed(fields_unnamed) => { + let names = fields_unnamed.unnamed.iter().enumerate().map( + |(ind_field, _)| { + syn::Ident::new( + &format!("field_{}", ind_field), + Span::call_site(), + ) + }, + ); + + let field_impls = fields_unnamed.unnamed.iter().enumerate().map( + |(ind_field, field)| { + let field_ident = syn::Ident::new( + &format!("field_{}", ind_field), + Span::call_site(), + ); + let field_type = &field.ty; + + quote! { + <#field_type as #core::codec::Encode> + ::encode(#field_ident, writer)?; + } + }, + ); + + quote! { + Self::#variant_ident( #(#names),* ) => { + writer.write(#ind_variant); + #(#field_impls)* + }, + } + } + Fields::Unit => { + quote! { + Self::#variant_ident => { + writer.write(#ind_variant); + }, + } + } + } + }); + + quote! { + match self { + #(#variant_impls)* + } + } + } + syn::Data::Union(_) => panic!("union type not supported"), + }; + + quote! { + #[automatically_derived] + impl #core::codec::Encode for #ident { + fn encode(&self, writer: &mut W) + -> ::core::result::Result<(), #core::codec::Error> { + #impl_block + + Ok(()) + } + } + } + .into() +} + +/// Derives the `Decode` trait. +#[proc_macro_derive(Decode, attributes(starknet))] +pub fn derive_decode(input: TokenStream) -> TokenStream { + let input: DeriveInput = parse_macro_input!(input); + let ident = &input.ident; + + let core = derive_core_path(&input); + + let impl_block = match input.data { + syn::Data::Struct(data) => match &data.fields { + Fields::Named(fields_named) => { + let field_impls = fields_named.named.iter().map(|field| { + let field_ident = &field.ident; + let field_type = &field.ty; + + quote! { + #field_ident: <#field_type as #core::codec::Decode> + ::decode_iter(iter)?, + } + }); + + quote! { + Ok(Self { + #(#field_impls)* + }) + } + } + Fields::Unnamed(fields_unnamed) => { + let field_impls = fields_unnamed.unnamed.iter().map(|field| { + let field_type = &field.ty; + quote! { + <#field_type as #core::codec::Decode>::decode_iter(iter)? + } + }); + + quote! { + Ok(Self ( + #(#field_impls),* + )) + } + } + Fields::Unit => { + quote! { + Ok(Self) + } + } + }, + syn::Data::Enum(data) => { + let variant_impls = data + .variants + .iter() + .enumerate() + .map(|(ind_variant, variant)| { + let variant_ident = &variant.ident; + let ind_variant = int_to_felt(ind_variant, &core); + + let decode_impl = match &variant.fields { + Fields::Named(fields_named) => { + let field_impls = fields_named.named.iter().map(|field| { + let field_ident = field.ident.as_ref().unwrap(); + let field_type = &field.ty; + + quote! { + #field_ident: <#field_type as #core::codec::Decode> + ::decode_iter(iter)?, + } + }); + + quote! { + return Ok(Self::#variant_ident { + #(#field_impls)* + }); + } + } + Fields::Unnamed(fields_unnamed) => { + let field_impls = fields_unnamed.unnamed.iter().map(|field| { + let field_type = &field.ty; + + quote! { + <#field_type as #core::codec::Decode>::decode_iter(iter)? + } + }); + + quote! { + return Ok(Self::#variant_ident( #(#field_impls),* )); + } + } + Fields::Unit => { + quote! { + return Ok(Self::#variant_ident); + } + } + }; + + quote! { + if tag == &#ind_variant { + #decode_impl + } + } + }); + + let ident = ident.to_string(); + + quote! { + let tag = iter.next().ok_or_else(#core::codec::Error::input_exhausted)?; + + #(#variant_impls)* + + Err(#core::codec::Error::unknown_enum_tag(tag, #ident)) + } + } + syn::Data::Union(_) => panic!("union type not supported"), + }; + + quote! { + #[automatically_derived] + impl<'a> #core::codec::Decode<'a> for #ident { + fn decode_iter(iter: &mut T) -> ::core::result::Result + where + T: core::iter::Iterator + { + #impl_block + } + } + } + .into() +} + +/// Determines the path to the `starknet-core` crate root. +fn derive_core_path(input: &DeriveInput) -> proc_macro2::TokenStream { + let mut attr_args = Args::default(); + + for attr in &input.attrs { + if !attr.meta.path().is_ident("starknet") { + continue; + } + + match &attr.meta { + Meta::Path(_) => {} + Meta::List(meta_list) => { + let args: Args = meta_list + .parse_args() + .expect("unable to parse starknet attribute args"); + + attr_args.merge(args); + } + Meta::NameValue(_) => panic!("starknet attribute must not be name-value"), + } + } + + attr_args.core.map_or_else( + || { + #[cfg(not(feature = "import_from_starknet"))] + quote! { + ::starknet_core + } + + // This feature is enabled by the `starknet` crate. When using `starknet` it's assumed + // that users would not have imported `starknet-core` directly. + #[cfg(feature = "import_from_starknet")] + quote! { + ::starknet::core + } + }, + |id| id.parse().expect("unable to parse core crate path"), + ) +} + +/// Turns an integer into an optimal `TokenStream` that constructs a `Felt` with the same value. +fn int_to_felt(int: usize, core: &proc_macro2::TokenStream) -> proc_macro2::TokenStream { + match int { + 0 => quote! { #core::types::Felt::ZERO }, + 1 => quote! { #core::types::Felt::ONE }, + 2 => quote! { #core::types::Felt::TWO }, + 3 => quote! { #core::types::Felt::THREE }, + // TODO: turn the number into Montgomery repr and use const ctor instead. + _ => { + let literal = LitInt::new(&int.to_string(), Span::call_site()); + quote! { #core::types::Felt::from(#literal) } + } + } +} diff --git a/starknet-core/Cargo.toml b/starknet-core/Cargo.toml index 5be48640..f12658f7 100644 --- a/starknet-core/Cargo.toml +++ b/starknet-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-core" -version = "0.11.1" +version = "0.12.0" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -17,19 +17,22 @@ exclude = ["test-data/**"] all-features = true [dependencies] -starknet-crypto = { version = "0.7.0", path = "../starknet-crypto", default-features = false, features = ["alloc"] } +starknet-crypto = { version = "0.7.3", path = "../starknet-crypto", default-features = false, features = ["alloc"] } +starknet-core-derive = { version = "0.1.0", path = "../starknet-core-derive" } base64 = { version = "0.21.0", default-features = false, features = ["alloc"] } crypto-bigint = { version = "0.5.1", default-features = false } flate2 = { version = "1.0.25", optional = true } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +num-traits = { version = "0.2.19", default-features = false } serde = { version = "1.0.160", default-features = false, features = ["derive"] } serde_json = { version = "1.0.96", default-features = false, features = ["alloc", "raw_value"] } serde_json_pythonic = { version = "0.1.2", default-features = false, features = ["alloc", "raw_value"] } -serde_with = { version = "2.3.2", default-features = false, features = ["alloc", "macros"] } +serde_with = { version = "3.9.0", default-features = false, features = ["alloc", "macros"] } sha3 = { version = "0.10.7", default-features = false } -starknet-types-core = { version = "0.1.3", default-features = false, features = ["curve", "serde", "num-traits"] } +starknet-types-core = { version = "0.1.6", default-features = false, features = ["curve", "serde", "num-traits"] } [dev-dependencies] +bincode = "1.3.3" criterion = { version = "0.4.0", default-features = false } hex-literal = "0.4.1" starknet-core = { path = ".", features = ["no_unknown_fields"] } @@ -39,7 +42,7 @@ wasm-bindgen-test = "0.3.34" [features] default = ["std"] -std = ["dep:flate2", "starknet-crypto/std"] +std = ["dep:flate2", "starknet-crypto/std", "starknet-types-core/std"] no_unknown_fields = [] [[bench]] @@ -49,3 +52,6 @@ harness = false [[bench]] name = "sierra_class_hash" harness = false + +[lints] +workspace = true diff --git a/starknet-core/src/chain_id.rs b/starknet-core/src/chain_id.rs index cbb430fa..8ec150bc 100644 --- a/starknet-core/src/chain_id.rs +++ b/starknet-core/src/chain_id.rs @@ -1,5 +1,6 @@ use starknet_types_core::felt::Felt; +/// The chain identifier for Starknet Mainnet. A Cairo short string encoding of `SN_MAIN`. pub const MAINNET: Felt = Felt::from_raw([ 502562008147966918, 18446744073709551615, @@ -7,6 +8,7 @@ pub const MAINNET: Felt = Felt::from_raw([ 17696389056366564951, ]); +/// The chain identifier for Starknet Goerli. A Cairo short string encoding of `SN_GOERLI`. #[deprecated = "The Goerli testnet has been shutdown"] pub const TESTNET: Felt = Felt::from_raw([ 398700013197595345, @@ -15,6 +17,7 @@ pub const TESTNET: Felt = Felt::from_raw([ 3753493103916128178, ]); +/// The chain identifier for Starknet Goerli 2. A Cairo short string encoding of `SN_GOERLI2`. #[deprecated = "The Goerli testnet has been shutdown"] pub const TESTNET2: Felt = Felt::from_raw([ 33650220878420990, @@ -23,6 +26,7 @@ pub const TESTNET2: Felt = Felt::from_raw([ 1663542769632127759, ]); +/// The chain identifier for Starknet Sepolia. A Cairo short string encoding of `SN_SEPOLIA`. pub const SEPOLIA: Felt = Felt::from_raw([ 507980251676163170, 18446744073709551615, @@ -45,9 +49,7 @@ mod test { ("SN_GOERLI", TESTNET), ("SN_GOERLI2", TESTNET2), ("SN_SEPOLIA", SEPOLIA), - ] - .into_iter() - { + ] { assert_eq!(cairo_short_string_to_felt(text).unwrap(), felt); } } diff --git a/starknet-core/src/codec.rs b/starknet-core/src/codec.rs new file mode 100644 index 00000000..6d3b8a11 --- /dev/null +++ b/starknet-core/src/codec.rs @@ -0,0 +1,983 @@ +use alloc::{boxed::Box, fmt::Formatter, format, string::*, vec::*}; +use core::{fmt::Display, mem::MaybeUninit}; + +use num_traits::ToPrimitive; + +use crate::types::{Felt, U256}; + +pub use starknet_core_derive::{Decode, Encode}; + +/// Any type where [`Felt`]s can be written into. This would typically be [`Vec`], but can +/// also be something like a stateful hasher. +/// +/// The trait method is infallible, as the most common use case is to simply write into a `Vec`. +/// Making the method infallible avoids over-engineering. However, if deemed necessary, a future +/// breaking change can make this fallible instead. +pub trait FeltWriter { + /// Adds a single [Felt] element into the writer. + fn write(&mut self, felt: Felt); +} + +/// Any type that can be serialized into a series of [Felt]s. This trait corresponds to the +/// `serialize` function of the Cairo `Serde` trait. +/// +/// This trait can be derived as long as all the fields in type implement [`Encode`]. +/// +/// # Example +/// +/// This example demonstrates deriving the trait and then using it to serialize an instance into +/// [`Vec`]. +/// +/// ```rust +/// use starknet_core::codec::Encode; +/// # use starknet_core::types::Felt; +/// +/// #[derive(Encode)] +/// # #[starknet(core = "starknet_core")] +/// struct CairoType { +/// a: u32, +/// b: Option, +/// } +/// +/// let instance = CairoType { +/// a: 3, +/// b: Some(true), +/// }; +/// let mut serialized = vec![]; +/// instance.encode(&mut serialized); +/// +/// assert_eq!(vec![Felt::THREE, Felt::ZERO, Felt::ONE], serialized); +/// ``` +pub trait Encode { + /// Converts the type into a list of [`Felt`] and append them into the writer. + fn encode(&self, writer: &mut W) -> Result<(), Error>; +} + +/// Any type that can be deserialized from a series of [Felt]s. This trait corresponds to the +/// `deserialize` function of the Cairo `Serde` trait. +/// +/// This trait can be derived as long as all the fields in type implement [`Encode`]. +/// +/// # Example +/// +/// This example demonstrates deriving the trait and then using it to deserialize an instance from +/// [`Vec`]. +/// +/// ```rust +/// use starknet_core::codec::Decode; +/// # use starknet_core::types::Felt; +/// +/// #[derive(Debug, PartialEq, Eq, Decode)] +/// # #[starknet(core = "starknet_core")] +/// struct CairoType { +/// a: u32, +/// b: Option, +/// } +/// +/// assert_eq!( +/// CairoType { +/// a: 3, +/// b: Some(true) +/// }, +/// CairoType::decode(&[Felt::THREE, Felt::ZERO, Felt::ONE]).unwrap() +/// ); +/// ``` +pub trait Decode<'a>: Sized { + /// Converts into the type from a list of [`Felt`]. + fn decode(reader: T) -> Result + where + T: IntoIterator, + { + Self::decode_iter(&mut reader.into_iter()) + } + + /// Converts into the type from an iterator of references to [`Felt`]. + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator; +} + +/// Error type for any encoding/decoding operations. +/// +/// A simple string representation is forced onto all implementations for simplicity. This is +/// because most of the time, a encoding/decoding error indicates a bug that requires human +/// attention to fix anyway; even when handling untrusted data, the program is likely to only be +/// interested in knowing that an error _did_ occur, instead of handling based on cause. +/// +/// There might be cases where allocations must be avoided. A feature could be added in the future +/// that turns the `repr` into `()` to address this. Such a feature would be a non-breaking change +/// so there's no need to add it now. +#[derive(Debug)] +pub struct Error { + repr: Box, +} + +impl FeltWriter for Vec { + fn write(&mut self, felt: Felt) { + self.push(felt); + } +} + +impl Encode for Felt { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write(*self); + Ok(()) + } +} + +impl Encode for bool { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write(if *self { Felt::ONE } else { Felt::ZERO }); + Ok(()) + } +} + +impl Encode for u8 { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write((*self).into()); + Ok(()) + } +} + +impl Encode for u16 { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write((*self).into()); + Ok(()) + } +} + +impl Encode for u32 { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write((*self).into()); + Ok(()) + } +} + +impl Encode for u64 { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write((*self).into()); + Ok(()) + } +} + +impl Encode for u128 { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write((*self).into()); + Ok(()) + } +} + +impl Encode for U256 { + fn encode(&self, writer: &mut W) -> Result<(), Error> { + self.low().encode(writer)?; + self.high().encode(writer)?; + Ok(()) + } +} + +impl Encode for Option +where + T: Encode, +{ + fn encode(&self, writer: &mut W) -> Result<(), Error> { + match self { + Some(inner) => { + writer.write(Felt::ZERO); + inner.encode(writer)?; + } + None => { + writer.write(Felt::ONE); + } + } + + Ok(()) + } +} + +impl Encode for Vec +where + T: Encode, +{ + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write(Felt::from(self.len())); + + for item in self { + item.encode(writer)?; + } + + Ok(()) + } +} + +impl Encode for [T; N] +where + T: Encode, +{ + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write(Felt::from(N)); + + for item in self { + item.encode(writer)?; + } + + Ok(()) + } +} + +impl Encode for [T] +where + T: Encode, +{ + fn encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write(Felt::from(self.len())); + + for item in self { + item.encode(writer)?; + } + + Ok(()) + } +} + +impl<'a> Decode<'a> for Felt { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + iter.next().ok_or_else(Error::input_exhausted).cloned() + } +} + +impl<'a> Decode<'a> for bool { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input = iter.next().ok_or_else(Error::input_exhausted)?; + if input == &Felt::ZERO { + Ok(false) + } else if input == &Felt::ONE { + Ok(true) + } else { + Err(Error::value_out_of_range(input, "bool")) + } + } +} + +impl<'a> Decode<'a> for u8 { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input = iter.next().ok_or_else(Error::input_exhausted)?; + input + .to_u8() + .ok_or_else(|| Error::value_out_of_range(input, "u8")) + } +} + +impl<'a> Decode<'a> for u16 { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input = iter.next().ok_or_else(Error::input_exhausted)?; + input + .to_u16() + .ok_or_else(|| Error::value_out_of_range(input, "u16")) + } +} + +impl<'a> Decode<'a> for u32 { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input = iter.next().ok_or_else(Error::input_exhausted)?; + input + .to_u32() + .ok_or_else(|| Error::value_out_of_range(input, "u32")) + } +} + +impl<'a> Decode<'a> for u64 { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input = iter.into_iter().next().ok_or_else(Error::input_exhausted)?; + input + .to_u64() + .ok_or_else(|| Error::value_out_of_range(input, "u64")) + } +} + +impl<'a> Decode<'a> for u128 { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input = iter.next().ok_or_else(Error::input_exhausted)?; + input + .to_u128() + .ok_or_else(|| Error::value_out_of_range(input, "u128")) + } +} + +impl<'a> Decode<'a> for U256 { + fn decode_iter(iter: &mut T) -> Result + where + T: Iterator, + { + let input_low = iter.next().ok_or_else(Error::input_exhausted)?; + let input_high = iter.next().ok_or_else(Error::input_exhausted)?; + + let input_low = input_low + .to_u128() + .ok_or_else(|| Error::value_out_of_range(input_low, "u128"))?; + let input_high = input_high + .to_u128() + .ok_or_else(|| Error::value_out_of_range(input_high, "u128"))?; + + Ok(Self::from_words(input_low, input_high)) + } +} + +impl<'a, T> Decode<'a> for Option +where + T: Decode<'a>, +{ + fn decode_iter(iter: &mut I) -> Result + where + I: Iterator, + { + let tag = iter.next().ok_or_else(Error::input_exhausted)?; + + if tag == &Felt::ZERO { + Ok(Some(T::decode_iter(iter)?)) + } else if tag == &Felt::ONE { + Ok(None) + } else { + Err(Error::unknown_enum_tag(tag, "Option")) + } + } +} + +impl<'a, T> Decode<'a> for Vec +where + T: Decode<'a>, +{ + fn decode_iter(iter: &mut I) -> Result + where + I: Iterator, + { + let length = iter.next().ok_or_else(Error::input_exhausted)?; + let length = length + .to_usize() + .ok_or_else(|| Error::value_out_of_range(length, "usize"))?; + + let mut result = Self::with_capacity(length); + + for _ in 0..length { + result.push(T::decode_iter(iter)?); + } + + Ok(result) + } +} + +impl<'a, T, const N: usize> Decode<'a> for [T; N] +where + T: Decode<'a> + Sized, +{ + fn decode_iter(iter: &mut I) -> Result + where + I: Iterator, + { + let length = iter.next().ok_or_else(Error::input_exhausted)?; + let length = length + .to_usize() + .ok_or_else(|| Error::value_out_of_range(length, "usize"))?; + + if length != N { + return Err(Error::length_mismatch(N, length)); + } + + let mut result: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for elem in &mut result[..] { + *elem = MaybeUninit::new(T::decode_iter(iter)?); + } + + Ok(unsafe { core::mem::transmute_copy::<_, [T; N]>(&result) }) + } +} + +impl Error { + /// Creates an [`Error`] which indicates that the input stream has ended prematurely. + pub fn input_exhausted() -> Self { + Self { + repr: "unexpected end of input stream" + .to_string() + .into_boxed_str(), + } + } + + /// Creates an [`Error`] which indicates that the length (likely prefix) is different from the + /// expected value. + pub fn length_mismatch(expected: usize, actual: usize) -> Self { + Self { + repr: format!("expecting length `{}` but got `{}`", expected, actual).into_boxed_str(), + } + } + + /// Creates an [`Error`] which indicates that the input value is out of range. + pub fn value_out_of_range(value: V, type_name: &str) -> Self + where + V: Display, + { + Self { + repr: format!("value `{}` is out of range for type `{}`", value, type_name) + .into_boxed_str(), + } + } + + /// Creates an [`Error`] which indicates that the enum tag does not belong to a known variant. + pub fn unknown_enum_tag(tag: V, type_name: &str) -> Self + where + V: Display, + { + Self { + repr: format!("enum tag `{}` is unknown for type `{}`", tag, type_name) + .into_boxed_str(), + } + } + + /// Creates an [`Error`] using a custom error string. + pub fn custom(content: T) -> Self + where + T: Display, + { + Self { + repr: content.to_string().into_boxed_str(), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.repr) + } +} + +#[cfg(test)] +mod tests { + use core::str::FromStr; + + use super::*; + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_felt() { + let mut serialized = Vec::::new(); + Felt::from_str("99999999999999999999999999") + .unwrap() + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![Felt::from_str("99999999999999999999999999").unwrap()] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_bool() { + let mut serialized = Vec::::new(); + true.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("1").unwrap()]); + + let mut serialized = Vec::::new(); + false.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("0").unwrap()]); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_u8() { + let mut serialized = Vec::::new(); + 123u8.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("123").unwrap()]); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_u16() { + let mut serialized = Vec::::new(); + 12345u16.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("12345").unwrap()]); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_u32() { + let mut serialized = Vec::::new(); + 1234567890u32.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("1234567890").unwrap()]); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_u64() { + let mut serialized = Vec::::new(); + 12345678900000000000u64.encode(&mut serialized).unwrap(); + assert_eq!( + serialized, + vec![Felt::from_str("12345678900000000000").unwrap()] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_u128() { + let mut serialized = Vec::::new(); + 123456789000000000000000000000u128 + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![Felt::from_str("123456789000000000000000000000").unwrap()] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_u256() { + let mut serialized = Vec::::new(); + U256::from_words(12345, 67890) + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("12345").unwrap(), + Felt::from_str("67890").unwrap() + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_option() { + let mut serialized = Vec::::new(); + Some(10u32).encode(&mut serialized).unwrap(); + assert_eq!( + serialized, + vec![Felt::from_str("0").unwrap(), Felt::from_str("10").unwrap()] + ); + + serialized.clear(); + Option::::None.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("1").unwrap()]); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_vec() { + let mut serialized = Vec::::new(); + vec![Some(10u32), None].encode(&mut serialized).unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("10").unwrap(), + Felt::from_str("1").unwrap() + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_array() { + let mut serialized = Vec::::new(); + <[Option; 2]>::encode(&[Some(10u32), None], &mut serialized).unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("10").unwrap(), + Felt::from_str("1").unwrap() + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_encode_slice() { + let mut serialized = Vec::::new(); + <[Option]>::encode(&[Some(10u32), None], &mut serialized).unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("10").unwrap(), + Felt::from_str("1").unwrap() + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_derive_encode_struct_named() { + #[derive(Encode)] + #[starknet(core = "crate")] + struct CairoType { + a: Felt, + b: U256, + c: bool, + } + + let mut serialized = Vec::::new(); + CairoType { + a: Felt::from_str("12345").unwrap(), + b: U256::from_words(12, 34), + c: true, + } + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("12345").unwrap(), + Felt::from_str("12").unwrap(), + Felt::from_str("34").unwrap(), + Felt::from_str("1").unwrap(), + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_derive_encode_struct_tuple() { + #[derive(Encode)] + #[starknet(core = "crate")] + struct CairoType(Felt, U256, bool); + + let mut serialized = Vec::::new(); + CairoType( + Felt::from_str("12345").unwrap(), + U256::from_words(12, 34), + true, + ) + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("12345").unwrap(), + Felt::from_str("12").unwrap(), + Felt::from_str("34").unwrap(), + Felt::from_str("1").unwrap(), + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_derive_encode_enum() { + #[derive(Encode)] + #[starknet(core = "crate")] + enum CairoType { + A, + B(bool), + C(Option, u8), + D { a: u64, b: bool }, + } + + let mut serialized = Vec::::new(); + CairoType::A.encode(&mut serialized).unwrap(); + assert_eq!(serialized, vec![Felt::from_str("0").unwrap()]); + + serialized.clear(); + CairoType::B(true).encode(&mut serialized).unwrap(); + assert_eq!( + serialized, + vec![Felt::from_str("1").unwrap(), Felt::from_str("1").unwrap()] + ); + + serialized.clear(); + CairoType::C(Some(U256::from_words(12, 23)), 4) + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("12").unwrap(), + Felt::from_str("23").unwrap(), + Felt::from_str("4").unwrap(), + ] + ); + + serialized.clear(); + CairoType::C(None, 8).encode(&mut serialized).unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("2").unwrap(), + Felt::from_str("1").unwrap(), + Felt::from_str("8").unwrap(), + ] + ); + + serialized.clear(); + CairoType::D { a: 100, b: false } + .encode(&mut serialized) + .unwrap(); + assert_eq!( + serialized, + vec![ + Felt::from_str("3").unwrap(), + Felt::from_str("100").unwrap(), + Felt::from_str("0").unwrap() + ] + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_felt() { + assert_eq!( + Felt::from_str("99999999999999999999999999").unwrap(), + Felt::decode(&[Felt::from_str("99999999999999999999999999").unwrap()]).unwrap() + ); + } + + #[allow(clippy::bool_assert_comparison)] + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_bool() { + assert_eq!(true, bool::decode(&[Felt::from_str("1").unwrap()]).unwrap()); + + assert_eq!( + false, + bool::decode(&[Felt::from_str("0").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_u8() { + assert_eq!( + 123u8, + u8::decode(&[Felt::from_str("123").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_u16() { + assert_eq!( + 12345u16, + u16::decode(&[Felt::from_str("12345").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_u32() { + assert_eq!( + 1234567890u32, + u32::decode(&[Felt::from_str("1234567890").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_u64() { + assert_eq!( + 12345678900000000000u64, + u64::decode(&[Felt::from_str("12345678900000000000").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_u128() { + assert_eq!( + 123456789000000000000000000000u128, + u128::decode(&[Felt::from_str("123456789000000000000000000000").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_u256() { + assert_eq!( + U256::from_words(12345, 67890), + U256::decode(&[ + Felt::from_str("12345").unwrap(), + Felt::from_str("67890").unwrap() + ]) + .unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_option() { + assert_eq!( + Some(10u32), + Option::::decode(&[Felt::from_str("0").unwrap(), Felt::from_str("10").unwrap()]) + .unwrap() + ); + + assert_eq!( + Option::::None, + Option::::decode(&[Felt::from_str("1").unwrap()]).unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_vec() { + assert_eq!( + vec![Some(10u32), None], + Vec::>::decode(&[ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("10").unwrap(), + Felt::from_str("1").unwrap() + ]) + .unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_decode_array() { + assert_eq!( + [Some(10u32), None], + <[Option; 2]>::decode(&[ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("10").unwrap(), + Felt::from_str("1").unwrap() + ]) + .unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_derive_decode_struct_named() { + #[derive(Debug, PartialEq, Eq, Decode)] + #[starknet(core = "crate")] + struct CairoType { + a: Felt, + b: U256, + c: bool, + } + + assert_eq!( + CairoType { + a: Felt::from_str("12345").unwrap(), + b: U256::from_words(12, 34), + c: true, + }, + CairoType::decode(&[ + Felt::from_str("12345").unwrap(), + Felt::from_str("12").unwrap(), + Felt::from_str("34").unwrap(), + Felt::from_str("1").unwrap(), + ]) + .unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_derive_decode_struct_tuple() { + #[derive(Debug, PartialEq, Eq, Decode)] + #[starknet(core = "crate")] + struct CairoType(Felt, U256, bool); + + assert_eq!( + CairoType( + Felt::from_str("12345").unwrap(), + U256::from_words(12, 34), + true, + ), + CairoType::decode(&[ + Felt::from_str("12345").unwrap(), + Felt::from_str("12").unwrap(), + Felt::from_str("34").unwrap(), + Felt::from_str("1").unwrap(), + ]) + .unwrap() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_derive_decode_enum() { + #[derive(Debug, PartialEq, Eq, Decode)] + #[starknet(core = "crate")] + enum CairoType { + A, + B(bool), + C(Option, u8), + D { a: u64, b: bool }, + } + + assert_eq!( + CairoType::A, + CairoType::decode(&[Felt::from_str("0").unwrap()]).unwrap() + ); + + assert_eq!( + CairoType::B(true), + CairoType::decode(&[Felt::from_str("1").unwrap(), Felt::from_str("1").unwrap()]) + .unwrap() + ); + + assert_eq!( + CairoType::C(Some(U256::from_words(12, 23)), 4), + CairoType::decode(&[ + Felt::from_str("2").unwrap(), + Felt::from_str("0").unwrap(), + Felt::from_str("12").unwrap(), + Felt::from_str("23").unwrap(), + Felt::from_str("4").unwrap(), + ]) + .unwrap() + ); + + assert_eq!( + CairoType::C(None, 8), + CairoType::decode(&[ + Felt::from_str("2").unwrap(), + Felt::from_str("1").unwrap(), + Felt::from_str("8").unwrap(), + ]) + .unwrap() + ); + + assert_eq!( + CairoType::D { a: 100, b: false }, + CairoType::decode(&[ + Felt::from_str("3").unwrap(), + Felt::from_str("100").unwrap(), + Felt::from_str("0").unwrap() + ]) + .unwrap() + ); + } +} diff --git a/starknet-core/src/crypto.rs b/starknet-core/src/crypto.rs index 1977f4da..fbaf6973 100644 --- a/starknet-core/src/crypto.rs +++ b/starknet-core/src/crypto.rs @@ -6,16 +6,23 @@ use starknet_crypto::{rfc6979_generate_k, sign, verify, SignError, VerifyError}; mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors when performing ECDSA [`sign`](fn.ecdsa_sign) operations. #[derive(Debug)] pub enum EcdsaSignError { + /// The message hash is not in the range of `[0, 2^251)`. MessageHashOutOfRange, } #[derive(Debug)] + /// Errors when performing ECDSA [`verify`](fn.ecdsa_verify) operations. pub enum EcdsaVerifyError { + /// The message hash is not in the range of `[0, 2^251)`. MessageHashOutOfRange, + /// The public key is not a valid point on the STARK curve. InvalidPublicKey, + /// The `r` value is not in the range of `[0, 2^251)`. SignatureROutOfRange, + /// The `s` value is not in the range of `[0, 2^251)`. SignatureSOutOfRange, } @@ -46,6 +53,16 @@ mod errors { } pub use errors::{EcdsaSignError, EcdsaVerifyError}; +/// Computes the Pedersen hash of a list of [`Felt`]. +/// +/// The hash is computed by starting with `0`, hashing it recursively against all elements in +/// the list, and finally also hashing against the length of the list. +/// +/// For example, calling `compute_hash_on_elements([7, 8])` would return: +/// +/// ```markdown +/// pedersen_hash(pedersen_hash(pedersen_hash(0, 7)), 8), 2) +/// ``` pub fn compute_hash_on_elements<'a, ESI, II>(data: II) -> Felt where ESI: ExactSizeIterator, @@ -62,6 +79,8 @@ where pedersen_hash(¤t_hash, &data_len) } +/// Signs a hash using deterministic ECDSA on the STARK curve. The signature returned can be used +/// to recover the public key. pub fn ecdsa_sign( private_key: &Felt, message_hash: &Felt, @@ -89,6 +108,7 @@ pub fn ecdsa_sign( } } +/// Verified an ECDSA signature on the STARK curve. pub fn ecdsa_verify( public_key: &Felt, message_hash: &Felt, diff --git a/starknet-core/src/lib.rs b/starknet-core/src/lib.rs index f689809b..88eb2e90 100644 --- a/starknet-core/src/lib.rs +++ b/starknet-core/src/lib.rs @@ -1,15 +1,25 @@ +//! Core data types and utilities for Starknet. + +#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::comparison_chain)] -#![doc = include_str!("../README.md")] +/// Module containing custom serialization/deserialization implementations. pub mod serde; +/// Module containing core types for representing objects in Starknet. pub mod types; +/// High-level utilities for cryptographic operations used in Starknet. pub mod crypto; +/// Utilities for performing commonly used algorithms in Starknet. pub mod utils; +/// Chain IDs for commonly used public Starknet networks. pub mod chain_id; +/// Types for serializing high-level Cairo types into field elements and vice versa. +pub mod codec; + extern crate alloc; diff --git a/starknet-core/src/serde/byte_array.rs b/starknet-core/src/serde/byte_array.rs index 9d171396..3fa0fb64 100644 --- a/starknet-core/src/serde/byte_array.rs +++ b/starknet-core/src/serde/byte_array.rs @@ -1,3 +1,4 @@ +/// Serializing and deserializing [`Vec`] with base64 encoding. pub mod base64 { use alloc::{fmt::Formatter, format, vec::*}; @@ -6,6 +7,7 @@ pub mod base64 { struct Base64Visitor; + /// Serializes [`Vec`] as base64 string. pub fn serialize(value: T, serializer: S) -> Result where S: Serializer, @@ -14,6 +16,7 @@ pub mod base64 { serializer.serialize_str(&STANDARD.encode(value.as_ref())) } + /// Deserializes [`Vec`] from base64 string. pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -21,10 +24,10 @@ pub mod base64 { deserializer.deserialize_any(Base64Visitor) } - impl<'de> Visitor<'de> for Base64Visitor { + impl Visitor<'_> for Base64Visitor { type Value = Vec; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { write!(formatter, "string") } diff --git a/starknet-core/src/serde/mod.rs b/starknet-core/src/serde/mod.rs index c414c706..0a7c8022 100644 --- a/starknet-core/src/serde/mod.rs +++ b/starknet-core/src/serde/mod.rs @@ -1,5 +1,7 @@ +/// Custom serialization/deserialization implementations for [`Vec`]. pub mod byte_array; +/// Custom serialization/deserialization implementations for [`Felt`](crate::types::Felt). pub mod unsigned_field_element; pub(crate) mod num_hex; diff --git a/starknet-core/src/serde/num_hex.rs b/starknet-core/src/serde/num_hex.rs index 985b9053..2026be40 100644 --- a/starknet-core/src/serde/num_hex.rs +++ b/starknet-core/src/serde/num_hex.rs @@ -9,21 +9,29 @@ pub mod u64 { where S: Serializer, { - serializer.serialize_str(&format!("{value:#x}")) + if serializer.is_human_readable() { + serializer.serialize_str(&format!("{value:#x}")) + } else { + serializer.serialize_u64(*value) + } } pub fn deserialize<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - deserializer.deserialize_any(NumHexVisitor) + if deserializer.is_human_readable() { + deserializer.deserialize_str(NumHexVisitor) + } else { + deserializer.deserialize_u64(NumHexVisitor) + } } - impl<'de> Visitor<'de> for NumHexVisitor { + impl Visitor<'_> for NumHexVisitor { type Value = u64; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { - write!(formatter, "string") + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { + write!(formatter, "string, or an array of u8") } fn visit_str(self, v: &str) -> Result @@ -33,5 +41,37 @@ pub mod u64 { u64::from_str_radix(v.trim_start_matches("0x"), 16) .map_err(|err| serde::de::Error::custom(format!("invalid u64 hex string: {err}"))) } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + Ok(v) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use hex_literal::hex; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize)] + struct TestStruct(#[serde(with = "u64")] pub u64); + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_ser() { + let r = bincode::serialize(&TestStruct(0x1234)).unwrap(); + assert_eq!(r, hex!("3412000000000000")); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_deser() { + let r = bincode::deserialize::(&hex!("3412000000000000")).unwrap(); + assert_eq!(r.0, 0x1234); } } diff --git a/starknet-core/src/serde/unsigned_field_element.rs b/starknet-core/src/serde/unsigned_field_element.rs index 38317ff9..380b68a2 100644 --- a/starknet-core/src/serde/unsigned_field_element.rs +++ b/starknet-core/src/serde/unsigned_field_element.rs @@ -1,5 +1,6 @@ use alloc::{fmt::Formatter, format}; +use crypto_bigint::U256; use serde::{ de::{Error as DeError, Visitor}, Deserializer, Serializer, @@ -8,10 +9,20 @@ use serde_with::{DeserializeAs, SerializeAs}; use starknet_types_core::felt::Felt; +const PRIME: U256 = + U256::from_be_hex("0800000000000011000000000000000000000000000000000000000000000001"); + +/// Serialize/deserialize [`Felt`] as hex strings. For use with `serde_with`. +#[derive(Debug)] pub struct UfeHex; +/// Serialize/deserialize [`Option`] as hex strings. For use with `serde_with`. +#[derive(Debug)] pub struct UfeHexOption; +/// Serialize/deserialize [`Option`] as hex strings in a pending block hash context. For use +/// with `serde_with`. +#[derive(Debug)] pub struct UfePendingBlockHash; struct UfeHexVisitor; @@ -23,7 +34,11 @@ impl SerializeAs for UfeHex { where S: Serializer, { - serializer.serialize_str(&format!("{value:#x}")) + if serializer.is_human_readable() { + serializer.serialize_str(&format!("{value:#x}")) + } else { + serializer.serialize_bytes(&value.to_bytes_be()) + } } } @@ -32,15 +47,19 @@ impl<'de> DeserializeAs<'de, Felt> for UfeHex { where D: Deserializer<'de>, { - deserializer.deserialize_any(UfeHexVisitor) + if deserializer.is_human_readable() { + deserializer.deserialize_any(UfeHexVisitor) + } else { + deserializer.deserialize_bytes(UfeHexVisitor) + } } } -impl<'de> Visitor<'de> for UfeHexVisitor { +impl Visitor<'_> for UfeHexVisitor { type Value = Felt; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { - write!(formatter, "string") + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { + write!(formatter, "a hex string, or an array of u8") } fn visit_str(self, v: &str) -> Result @@ -49,6 +68,16 @@ impl<'de> Visitor<'de> for UfeHexVisitor { { Felt::from_hex(v).map_err(|err| DeError::custom(format!("invalid hex string: {err}"))) } + + fn visit_bytes(self, v: &[u8]) -> Result { + let buf = <[u8; 32]>::try_from(v).map_err(serde::de::Error::custom)?; + + if U256::from_be_slice(&buf) < PRIME { + Ok(Felt::from_bytes_be(&buf)) + } else { + Err(serde::de::Error::custom("field element value out of range")) + } + } } impl SerializeAs> for UfeHexOption { @@ -56,9 +85,16 @@ impl SerializeAs> for UfeHexOption { where S: Serializer, { - match value { - Some(value) => serializer.serialize_str(&format!("{value:#064x}")), - None => serializer.serialize_none(), + if serializer.is_human_readable() { + match value { + Some(value) => serializer.serialize_str(&format!("{value:#064x}")), + None => serializer.serialize_none(), + } + } else { + match value { + Some(value) => serializer.serialize_bytes(&value.to_bytes_be()), + None => serializer.serialize_bytes(&[]), + } } } } @@ -68,14 +104,18 @@ impl<'de> DeserializeAs<'de, Option> for UfeHexOption { where D: Deserializer<'de>, { - deserializer.deserialize_any(UfeHexOptionVisitor) + if deserializer.is_human_readable() { + deserializer.deserialize_any(UfeHexOptionVisitor) + } else { + deserializer.deserialize_bytes(UfeHexOptionVisitor) + } } } -impl<'de> Visitor<'de> for UfeHexOptionVisitor { +impl Visitor<'_> for UfeHexOptionVisitor { type Value = Option; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { write!(formatter, "string") } @@ -91,6 +131,20 @@ impl<'de> Visitor<'de> for UfeHexOptionVisitor { }, } } + + fn visit_bytes(self, v: &[u8]) -> Result { + if v.is_empty() { + return Ok(None); + } + + let buf = <[u8; 32]>::try_from(v).map_err(serde::de::Error::custom)?; + + if U256::from_be_slice(&buf) < PRIME { + Ok(Some(Felt::from_bytes_be(&buf))) + } else { + Err(serde::de::Error::custom("field element value out of range")) + } + } } impl SerializeAs> for UfePendingBlockHash { @@ -98,10 +152,17 @@ impl SerializeAs> for UfePendingBlockHash { where S: Serializer, { - match value { - Some(value) => serializer.serialize_str(&format!("{value:#064x}")), - // We don't know if it's `null` or `"pending"` - None => serializer.serialize_none(), + if serializer.is_human_readable() { + match value { + Some(value) => serializer.serialize_str(&format!("{value:#064x}")), + // We don't know if it's `null` or `"pending"` + None => serializer.serialize_none(), + } + } else { + match value { + Some(value) => serializer.serialize_bytes(&value.to_bytes_be()), + None => serializer.serialize_bytes(&[]), + } } } } @@ -111,14 +172,18 @@ impl<'de> DeserializeAs<'de, Option> for UfePendingBlockHash { where D: Deserializer<'de>, { - deserializer.deserialize_any(UfePendingBlockHashVisitor) + if deserializer.is_human_readable() { + deserializer.deserialize_any(UfePendingBlockHashVisitor) + } else { + deserializer.deserialize_bytes(UfePendingBlockHashVisitor) + } } } -impl<'de> Visitor<'de> for UfePendingBlockHashVisitor { +impl Visitor<'_> for UfePendingBlockHashVisitor { type Value = Option; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { write!(formatter, "string") } @@ -135,23 +200,115 @@ impl<'de> Visitor<'de> for UfePendingBlockHashVisitor { } } } + + fn visit_bytes(self, v: &[u8]) -> Result { + if v.is_empty() { + return Ok(None); + } + + let buf = <[u8; 32]>::try_from(v).map_err(serde::de::Error::custom)?; + + if U256::from_be_slice(&buf) < PRIME { + Ok(Some(Felt::from_bytes_be(&buf))) + } else { + Err(serde::de::Error::custom("field element value out of range")) + } + } } #[cfg(test)] mod tests { use super::*; - use serde::Deserialize; + use hex_literal::hex; + use serde::{Deserialize, Serialize}; use serde_with::serde_as; #[serde_as] - #[derive(Deserialize)] - struct TestStruct(#[serde_as(as = "UfeHexOption")] pub Option); + #[derive(Serialize, Deserialize)] + struct TestStruct(#[serde_as(as = "UfeHex")] pub Felt); + + #[serde_as] + #[derive(Serialize, Deserialize)] + struct TestOptionStruct(#[serde_as(as = "UfeHexOption")] pub Option); + + #[serde_as] + #[derive(Serialize, Deserialize)] + struct TestBlockHashStruct(#[serde_as(as = "UfePendingBlockHash")] pub Option); + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_ser() { + let r = bincode::serialize(&TestStruct(Felt::ONE)).unwrap(); + assert_eq!( + r, + hex!( + "2000000000000000 0000000000000000000000000000000000000000000000000000000000000001" + ) + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_deser() { + let r = bincode::deserialize::(&hex!( + "2000000000000000 0000000000000000000000000000000000000000000000000000000000000001" + )) + .unwrap(); + assert_eq!(r.0, Felt::ONE); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_deser_out_of_range() { + if bincode::deserialize::(&hex!( + "2000000000000000 0800000000000011000000000000000000000000000000000000000000000001" + )) + .is_ok() + { + panic!("deserialization should fail") + } + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn option_deser_empty_string() { + let r = serde_json::from_str::("\"\"").unwrap(); + assert_eq!(r.0, None); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn option_bin_ser_none() { + let r = bincode::serialize(&TestOptionStruct(None)).unwrap(); + assert_eq!(r, hex!("0000000000000000")); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn option_bin_deser_none() { + let r = bincode::deserialize::(&hex!("0000000000000000")).unwrap(); + assert_eq!(r.0, None); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn pending_block_hash_deser_pending() { + let r = serde_json::from_str::("\"pending\"").unwrap(); + assert_eq!(r.0, None); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn pending_block_hash_bin_ser_none() { + let r = bincode::serialize(&TestBlockHashStruct(None)).unwrap(); + assert_eq!(r, hex!("0000000000000000")); + } #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - fn empty_string_deser() { - let r = serde_json::from_str::("\"\"").unwrap(); + fn pending_block_hash_bin_deser_none() { + let r = bincode::deserialize::(&hex!("0000000000000000")).unwrap(); assert_eq!(r.0, None); } } diff --git a/starknet-core/src/types/call.rs b/starknet-core/src/types/call.rs new file mode 100644 index 00000000..bf9ac864 --- /dev/null +++ b/starknet-core/src/types/call.rs @@ -0,0 +1,14 @@ +use alloc::vec::*; + +use crate::types::Felt; + +/// A contract call as part of a multi-call execution request. +#[derive(Debug, Clone)] +pub struct Call { + /// Address of the contract being invoked. + pub to: Felt, + /// Entrypoint selector of the function being invoked. + pub selector: Felt, + /// List of calldata to be sent for the call. + pub calldata: Vec, +} diff --git a/starknet-core/src/types/codegen.rs b/starknet-core/src/types/codegen.rs index 46e11560..e4890f3f 100644 --- a/starknet-core/src/types/codegen.rs +++ b/starknet-core/src/types/codegen.rs @@ -3,7 +3,7 @@ // https://github.com/xJonathanLEI/starknet-jsonrpc-codegen // Code generated with version: -// https://github.com/xJonathanLEI/starknet-jsonrpc-codegen#4118b48cc450a8ff558c2ac480aa12bf5efdd3bd +// https://github.com/xJonathanLEI/starknet-jsonrpc-codegen#66aa909a760ed0800232a2ca13ece97b9ea32d24 // These types are ignored from code generation. Implement them manually: // - `RECEIPT_BLOCK` @@ -24,6 +24,10 @@ // - `TXN` // - `TXN_RECEIPT` +#![allow(missing_docs)] +#![allow(clippy::doc_markdown)] +#![allow(clippy::missing_const_for_fn)] + use alloc::{format, string::*, vec::*}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -4591,7 +4595,7 @@ impl Serialize for AddDeclareTransactionRequest { } } -impl<'a> Serialize for AddDeclareTransactionRequestRef<'a> { +impl Serialize for AddDeclareTransactionRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -4668,7 +4672,7 @@ impl Serialize for AddDeployAccountTransactionRequest { } } -impl<'a> Serialize for AddDeployAccountTransactionRequestRef<'a> { +impl Serialize for AddDeployAccountTransactionRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -4745,7 +4749,7 @@ impl Serialize for AddInvokeTransactionRequest { } } -impl<'a> Serialize for AddInvokeTransactionRequestRef<'a> { +impl Serialize for AddInvokeTransactionRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -4869,7 +4873,7 @@ impl Serialize for CallRequest { } } -impl<'a> Serialize for CallRequestRef<'a> { +impl Serialize for CallRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5007,7 +5011,7 @@ impl Serialize for EstimateFeeRequest { } } -impl<'a> Serialize for EstimateFeeRequestRef<'a> { +impl Serialize for EstimateFeeRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5141,7 +5145,7 @@ impl Serialize for EstimateMessageFeeRequest { } } -impl<'a> Serialize for EstimateMessageFeeRequestRef<'a> { +impl Serialize for EstimateMessageFeeRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5242,7 +5246,7 @@ impl Serialize for GetBlockTransactionCountRequest { } } -impl<'a> Serialize for GetBlockTransactionCountRequestRef<'a> { +impl Serialize for GetBlockTransactionCountRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5319,7 +5323,7 @@ impl Serialize for GetBlockWithReceiptsRequest { } } -impl<'a> Serialize for GetBlockWithReceiptsRequestRef<'a> { +impl Serialize for GetBlockWithReceiptsRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5396,7 +5400,7 @@ impl Serialize for GetBlockWithTxHashesRequest { } } -impl<'a> Serialize for GetBlockWithTxHashesRequestRef<'a> { +impl Serialize for GetBlockWithTxHashesRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5473,7 +5477,7 @@ impl Serialize for GetBlockWithTxsRequest { } } -impl<'a> Serialize for GetBlockWithTxsRequestRef<'a> { +impl Serialize for GetBlockWithTxsRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5561,7 +5565,7 @@ impl Serialize for GetClassAtRequest { } } -impl<'a> Serialize for GetClassAtRequestRef<'a> { +impl Serialize for GetClassAtRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5678,7 +5682,7 @@ impl Serialize for GetClassHashAtRequest { } } -impl<'a> Serialize for GetClassHashAtRequestRef<'a> { +impl Serialize for GetClassHashAtRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5795,7 +5799,7 @@ impl Serialize for GetClassRequest { } } -impl<'a> Serialize for GetClassRequestRef<'a> { +impl Serialize for GetClassRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5901,7 +5905,7 @@ impl Serialize for GetEventsRequest { } } -impl<'a> Serialize for GetEventsRequestRef<'a> { +impl Serialize for GetEventsRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -5989,7 +5993,7 @@ impl Serialize for GetNonceRequest { } } -impl<'a> Serialize for GetNonceRequestRef<'a> { +impl Serialize for GetNonceRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -6095,7 +6099,7 @@ impl Serialize for GetStateUpdateRequest { } } -impl<'a> Serialize for GetStateUpdateRequestRef<'a> { +impl Serialize for GetStateUpdateRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -6192,7 +6196,7 @@ impl Serialize for GetStorageAtRequest { } } -impl<'a> Serialize for GetStorageAtRequestRef<'a> { +impl Serialize for GetStorageAtRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[serde_as] #[derive(Serialize)] @@ -6332,7 +6336,7 @@ impl Serialize for GetTransactionByBlockIdAndIndexRequest { } } -impl<'a> Serialize for GetTransactionByBlockIdAndIndexRequestRef<'a> { +impl Serialize for GetTransactionByBlockIdAndIndexRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -6433,7 +6437,7 @@ impl Serialize for GetTransactionByHashRequest { } } -impl<'a> Serialize for GetTransactionByHashRequestRef<'a> { +impl Serialize for GetTransactionByHashRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[serde_as] #[derive(Serialize)] @@ -6517,7 +6521,7 @@ impl Serialize for GetTransactionReceiptRequest { } } -impl<'a> Serialize for GetTransactionReceiptRequestRef<'a> { +impl Serialize for GetTransactionReceiptRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[serde_as] #[derive(Serialize)] @@ -6601,7 +6605,7 @@ impl Serialize for GetTransactionStatusRequest { } } -impl<'a> Serialize for GetTransactionStatusRequestRef<'a> { +impl Serialize for GetTransactionStatusRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[serde_as] #[derive(Serialize)] @@ -6701,7 +6705,7 @@ impl Serialize for SimulateTransactionsRequest { } } -impl<'a> Serialize for SimulateTransactionsRequestRef<'a> { +impl Serialize for SimulateTransactionsRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -6864,7 +6868,7 @@ impl Serialize for TraceBlockTransactionsRequest { } } -impl<'a> Serialize for TraceBlockTransactionsRequestRef<'a> { +impl Serialize for TraceBlockTransactionsRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[derive(Serialize)] #[serde(transparent)] @@ -6943,7 +6947,7 @@ impl Serialize for TraceTransactionRequest { } } -impl<'a> Serialize for TraceTransactionRequestRef<'a> { +impl Serialize for TraceTransactionRequestRef<'_> { fn serialize(&self, serializer: S) -> Result { #[serde_as] #[derive(Serialize)] diff --git a/starknet-core/src/types/contract/legacy.rs b/starknet-core/src/types/contract/legacy.rs index 83294a82..a6edda7f 100644 --- a/starknet-core/src/types/contract/legacy.rs +++ b/starknet-core/src/types/contract/legacy.rs @@ -26,150 +26,234 @@ use flate2::{write::GzEncoder, Compression}; const API_VERSION: Felt = Felt::ZERO; +/// A legacy (Cairo 0) contract class in a representation identical to the compiler output. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyContractClass { + /// Contract ABI. pub abi: Vec, + /// Contract entrypoints. pub entry_points_by_type: RawLegacyEntryPoints, + /// The Cairo program of the contract containing the actual bytecode. pub program: LegacyProgram, } +/// Legacy (Cairo 0) contract entrypoints by types. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct RawLegacyEntryPoints { + /// Entrypoints of type `CONSTRUCTOR` used during contract deployment. pub constructor: Vec, + /// Entrypoints of type `EXTERNAL` used for invocations from outside contracts. pub external: Vec, + /// Entrypoints of type `L1_HANDLER` used for handling L1-to-L2 messages. pub l1_handler: Vec, } +/// Legacy (Cairo 0) program containing bytecode and other data necessary for execution. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyProgram { + /// Attributes that provide additional context for certain sections of the bytecode. #[serde(skip_serializing_if = "Option::is_none")] pub attributes: Option>, + /// The list of Cairo builtins this program has access to. pub builtins: Vec, // This field was introduced in Cairo 0.10.0. By making it optional we're keeping compatibility // with older artifacts. This decision should be reviewd in the future. + /// Version of the compiler used to compile this contract. #[serde(skip_serializing_if = "Option::is_none")] pub compiler_version: Option, + /// The Cairo assembly bytecode of the contract. #[serde_as(as = "Vec")] pub data: Vec, + /// Debug information which is optionally emitted by the compiler. This field is not used for + /// class declaration or class hash calculation. pub debug_info: Option, + /// Legacy hints for non-determinism. pub hints: BTreeMap>, + /// A map of identifiers by name. pub identifiers: BTreeMap, + /// The main scope/namespace where all identifiers this program defines live in, usually + /// `__main__`. pub main_scope: String, // Impossible to use [Felt] here as by definition field elements are smaller // than prime + /// The STARK field prime. pub prime: String, + /// Data for tracking + /// [references](https://docs.cairo-lang.org/how_cairo_works/consts.html#references). pub reference_manager: LegacyReferenceManager, } +/// An legacy (Cairo 0) contract entrypoint for translating a selector to a bytecode offset. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct RawLegacyEntryPoint { + /// Offset in the bytecode. pub offset: LegacyEntrypointOffset, + /// Selector of the entrypoint, usually computed as the Starknet Keccak of the function name. #[serde_as(as = "UfeHex")] pub selector: Felt, } +/// Legacy (Cairo 0) program attribute that provide additional context for certain sections of the +/// bytecode. +/// +/// Attributes are usually used for providing error messages when an assertion fails. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyAttribute { + /// The scopes from which the attribute is accessible. #[serde(default)] pub accessible_scopes: Vec, + /// The ending PC of the segment that has access to the attribute. pub end_pc: u64, + /// Data needed for tracking the allocation pointer. #[serde(skip_serializing_if = "Option::is_none")] pub flow_tracking_data: Option, + /// Name of the attribute. pub name: String, + /// The starting PC of the segment that has access to the attribute. pub start_pc: u64, + /// Value of the attribute. pub value: String, } +/// Debug information generated by the legacy (Cairo 0) compiler. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyDebugInfo { /// A partial map from file name to its content. Files that are not in the map, are assumed to /// exist in the file system. pub file_contents: BTreeMap, - /// A map from (relative) PC to the location of the instruction + /// A map from (relative) PC to the location of the instruction. pub instruction_locations: BTreeMap, } +/// Legacy (Cairo 0) hints for introducing non-determinism into a Cairo program. +/// +/// These hints are implemented in Python that execute arbitrary code to fill Cairo VM memory. In +/// a public network like Starknet, a predefined list of hints are whitelisted to prevent deployment +/// of malicious code. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyHint { + /// The scopes from which the hint is accessible. pub accessible_scopes: Vec, + /// The Python code of the hint. pub code: String, + /// Data needed for tracking the allocation pointer. pub flow_tracking_data: LegacyFlowTrackingData, } +/// Legacy (Cairo 0) program identifiers. +/// +/// These are needed mostly to allow Python hints to work, as hints are allowed to reference Cairo +/// identifiers (e.g. variables) by name, which would otherwise be lost during compilation. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyIdentifier { + /// Decorators of the identifier, used for functions. #[serde(skip_serializing_if = "Option::is_none")] pub decorators: Option>, + /// The Cairo type, used for type definitions. #[serde(skip_serializing_if = "Option::is_none")] pub cairo_type: Option, + /// The fully-qualified name, used for struct definitions. #[serde(skip_serializing_if = "Option::is_none")] pub full_name: Option, + /// The list of members, used for struct definitions. #[serde(skip_serializing_if = "Option::is_none")] pub members: Option>, + /// The list of references, used for references. #[serde(skip_serializing_if = "Option::is_none")] pub references: Option>, + /// The size in the number of field elements, used for struct definitions. #[serde(skip_serializing_if = "Option::is_none")] pub size: Option, + /// The program counter, used for functions. #[serde(skip_serializing_if = "Option::is_none")] pub pc: Option, + /// The fully-qualified name of the identifier that this identifier points to, used for + /// aliases. #[serde(skip_serializing_if = "Option::is_none")] pub destination: Option, + /// Type of the identifier. pub r#type: String, + /// Value of the identifier, used for constants. #[serde(skip_serializing_if = "Option::is_none")] pub value: Option>, } +/// Data needed for tracking +/// [references](https://docs.cairo-lang.org/how_cairo_works/consts.html#references). #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyReferenceManager { + /// The list of references. pub references: Vec, } -/// This field changed from hex string to number on 0.11.0. +/// The legacy (Cairo 0) contract entrypoint offset, in either hexadecimal or numeric +/// representation. +/// +/// This type is needed as the entrypoint offset field changed from hex string to number on 0.11.0. +/// The type allows serializing older contracts in their original forms. #[derive(Debug, Copy, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] #[serde(untagged)] pub enum LegacyEntrypointOffset { + /// Offset with hexadecimal representation. U64AsHex(#[serde(with = "u64_hex")] u64), + /// Offset with numeric representation. U64AsInt(u64), } +/// Legacy (Cairo 0) instruction location for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyInstructionLocation { + /// The scopes from which the instruction is accessible. pub accessible_scopes: Vec, // This field is serialized as `null` instead of skipped + /// Data needed for tracking the allocation pointer. pub flow_tracking_data: Option, + /// Physical code locations of hints in the source . pub hints: Vec, + /// Physical code location of the instruction in the source. pub inst: LegacyLocation, } +/// Legacy (Cairo 0) struct member as part of a struct definition identifier. Used in +/// [`LegacyIdentifier`] for enabling hints to access identifiers by name. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyIdentifierMember { + /// The Cairo type of this struct field. pub cairo_type: String, + /// Offset of the field calculated as the total size of all the fields before this member in the + /// number of field elements. pub offset: u64, } +/// Cairo 0 [references](https://docs.cairo-lang.org/how_cairo_works/consts.html#references). #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyReference { + /// Data needed for tracking the allocation pointer. pub ap_tracking_data: LegacyApTrackingData, + /// Program counter value. pub pc: u64, + /// Value of the reference. pub value: String, } +// Missing docs allowed as it's unclear what exactly how type works in the Cairo program. +#[allow(missing_docs)] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyFlowTrackingData { @@ -177,26 +261,37 @@ pub struct LegacyFlowTrackingData { pub reference_ids: BTreeMap, } +/// Physical location of a legacy (Cairo 0) hint in source for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyHintLocation { + /// The physical location of the hint. pub location: LegacyLocation, - /// The number of new lines following the "%{" symbol + /// The number of new lines following the "%{" symbol. pub n_prefix_newlines: u64, } +/// The physical location in source of a certain code segment for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyLocation { + /// The ending column number. pub end_col: u64, + /// The ending line number. pub end_line: u64, + /// The file path or content. pub input_file: LegacyInputFile, + /// Location of the parent instruction, if any. #[serde(skip_serializing_if = "Option::is_none")] pub parent_location: Option, + /// The starting column number. pub start_col: u64, + /// The starting line number. pub start_line: u64, } +// Missing docs allowed as it's unclear what exactly how type works in the Cairo program. +#[allow(missing_docs)] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyApTrackingData { @@ -204,73 +299,118 @@ pub struct LegacyApTrackingData { pub offset: u64, } +/// Input file path or content for use in debug info. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct LegacyInputFile { + /// Path to file in the file system. #[serde(skip_serializing_if = "Option::is_none")] pub filename: Option, + /// Full content of the file, typically for ephemeral files. #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, } +/// Location of the parent instruction for use in debug info. +/// +/// It's used (?) for generating human-readable stack traces. #[derive(Debug, Clone)] pub struct LegacyParentLocation { + /// Location of the parent instruction. pub location: Box, + /// A human-readable remark usually in the form similar to "while trying to xxx". pub remark: String, } +/// Legacy (Cairo 0) contract ABI item. #[derive(Debug, Clone)] pub enum RawLegacyAbiEntry { + /// Constructor ABI entry. Constructor(RawLegacyConstructor), + /// Function ABI entry. Function(RawLegacyFunction), + /// Struct ABI entry. Struct(RawLegacyStruct), + /// L1 handler ABI entry. L1Handler(RawLegacyL1Handler), + /// Event ABI entry. Event(RawLegacyEvent), } +/// Legacy (Cairo 0) contract ABI representation of a constructor. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyConstructor { + /// Inputs to the constructor. pub inputs: Vec, + /// Name of the constructor. pub name: String, + /// Outputs of the constructor. pub outputs: Vec, } +/// Legacy (Cairo 0) contract ABI representation of a function. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RawLegacyFunction { + /// Inputs to the function. pub inputs: Vec, + /// Name of the function. pub name: String, + /// Outputs of the function. pub outputs: Vec, + /// State mutability of the function. + /// + /// Note that this is currently not enforced by the compiler. It's therefore only as accurate as + /// the code author annotating them is. #[serde(skip_serializing_if = "Option::is_none")] pub state_mutability: Option, } +/// Legacy (Cairo 0) contract ABI representation of a struct. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyStruct { + /// Fields of the struct. pub members: Vec, + /// Name of the struct. pub name: String, + /// Size of the struct in the number of field elements. pub size: u64, } +/// Legacy (Cairo 0) contract ABI representation of an L1 handler. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyL1Handler { + /// Inputs to the L1 handler function. pub inputs: Vec, + /// Name of the L1 handler function. pub name: String, + /// Outputs of the L1 handler function. pub outputs: Vec, } +/// Legacy (Cairo 0) contract ABI representation of an event. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RawLegacyEvent { + /// Data of the events for the unindexed event fields. pub data: Vec, + /// Keys of the events. + /// + /// This usually includes at least one element as the Starknet Keccak of the event name. + /// Additional keys are used for indexed event fields, if any. pub keys: Vec, + /// Name of the events. pub name: String, } +/// Legacy (Cairo 0) contract ABI representation of a struct field. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct RawLegacyMember { + /// Name of the struct field. pub name: String, + /// Total size of the fields in the struct before this field. pub offset: u64, + /// Cairo type of the struct field. pub r#type: String, } @@ -361,26 +501,26 @@ impl<'de> Deserialize<'de> for RawLegacyAbiEntry { let temp_value = serde_json::Value::deserialize(deserializer)?; match &temp_value["type"] { serde_json::Value::String(type_str) => match &type_str[..] { - "constructor" => Ok(RawLegacyAbiEntry::Constructor( + "constructor" => Ok(Self::Constructor( RawLegacyConstructor::deserialize(temp_value).map_err(|err| { DeError::custom(format!("invalid constructor variant: {err}")) })?, )), - "function" => Ok(RawLegacyAbiEntry::Function( + "function" => Ok(Self::Function( RawLegacyFunction::deserialize(temp_value).map_err(|err| { DeError::custom(format!("invalid function variant: {err}")) })?, )), - "struct" => Ok(RawLegacyAbiEntry::Struct( + "struct" => Ok(Self::Struct( RawLegacyStruct::deserialize(temp_value) .map_err(|err| DeError::custom(format!("invalid struct variant: {err}")))?, )), - "l1_handler" => Ok(RawLegacyAbiEntry::L1Handler( + "l1_handler" => Ok(Self::L1Handler( RawLegacyL1Handler::deserialize(temp_value).map_err(|err| { DeError::custom(format!("invalid l1_handler variant: {err}")) })?, )), - "event" => Ok(RawLegacyAbiEntry::Event( + "event" => Ok(Self::Event( RawLegacyEvent::deserialize(temp_value) .map_err(|err| DeError::custom(format!("invalid event variant: {err}")))?, )), @@ -394,6 +534,7 @@ impl<'de> Deserialize<'de> for RawLegacyAbiEntry { } impl LegacyContractClass { + /// Computes the class hash of the legacy (Cairo 0) class. pub fn class_hash(&self) -> Result { let mut elements = Vec::new(); @@ -402,7 +543,7 @@ impl LegacyContractClass { // Hashes external entry points elements.push({ let mut buffer = Vec::new(); - for entrypoint in self.entry_points_by_type.external.iter() { + for entrypoint in &self.entry_points_by_type.external { buffer.push(entrypoint.selector); buffer.push(entrypoint.offset.into()); } @@ -412,7 +553,7 @@ impl LegacyContractClass { // Hashes L1 handler entry points elements.push({ let mut buffer = Vec::new(); - for entrypoint in self.entry_points_by_type.l1_handler.iter() { + for entrypoint in &self.entry_points_by_type.l1_handler { buffer.push(entrypoint.selector); buffer.push(entrypoint.offset.into()); } @@ -422,7 +563,7 @@ impl LegacyContractClass { // Hashes constructor entry points elements.push({ let mut buffer = Vec::new(); - for entrypoint in self.entry_points_by_type.constructor.iter() { + for entrypoint in &self.entry_points_by_type.constructor { buffer.push(entrypoint.selector); buffer.push(entrypoint.offset.into()); } @@ -449,6 +590,12 @@ impl LegacyContractClass { Ok(compute_hash_on_elements(&elements)) } + /// Computes the "hinted" class hash of the legacy (Cairo 0) class. + /// + /// This is known as the "hinted" hash as it isn't possible to directly calculate, and thus + /// prove the correctness of, this hash, since it involves JSON serialization. Instead, this + /// hash is always calculated outside of the Cairo VM, and then fed to the Cairo program as a + /// hinted value. pub fn hinted_class_hash(&self) -> Result { #[serde_as] #[derive(Serialize)] @@ -471,6 +618,7 @@ impl LegacyContractClass { Ok(starknet_keccak(serialized.as_bytes())) } + /// Compresses the legacy (Cairo 0) class with gzip, as needed for class declaration. #[cfg(feature = "std")] pub fn compress(&self) -> Result { Ok(CompressedLegacyContractClass { @@ -488,6 +636,7 @@ impl LegacyContractClass { } impl LegacyProgram { + /// Compresses the legacy (Cairo 0) program with gzip. #[cfg(feature = "std")] pub fn compress(&self) -> Result, CompressProgramError> { use std::io::Write; @@ -835,6 +984,7 @@ fn should_skip_attributes_for_hinted_hash(value: &Option>) #[cfg(test)] mod tests { + use super::super::ContractArtifact; use super::*; #[derive(serde::Deserialize)] @@ -853,13 +1003,37 @@ mod tests { include_str!( "../../../test-data/contracts/cairo0/artifacts/pre-0.11.0/event_example.txt" ), - ] - .into_iter() - { + ] { serde_json::from_str::(raw_artifact).unwrap(); } } + #[test] + #[ignore = "https://github.com/xJonathanLEI/starknet-rs/issues/392"] + fn test_legacy_artifact_deser_from_contract_artifact() { + for raw_artifact in [ + include_str!("../../../test-data/contracts/cairo0/artifacts/oz_account.txt"), + include_str!("../../../test-data/contracts/cairo0/artifacts/event_example.txt"), + include_str!("../../../test-data/contracts/cairo0/artifacts/pre-0.11.0/oz_account.txt"), + include_str!( + "../../../test-data/contracts/cairo0/artifacts/pre-0.11.0/event_example.txt" + ), + ] { + let direct_deser = serde_json::from_str::(raw_artifact).unwrap(); + let deser_via_contract_artifact = + match serde_json::from_str::(raw_artifact).unwrap() { + ContractArtifact::LegacyClass(class) => class, + _ => panic!("unexpected artifact type"), + }; + + // Class should be identical however it's deserialized + assert_eq!( + direct_deser.class_hash().unwrap(), + deser_via_contract_artifact.class_hash().unwrap() + ); + } + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_contract_class_hash() { @@ -893,7 +1067,6 @@ mod tests { ), ), ] - .into_iter() { let artifact = serde_json::from_str::(raw_artifact).unwrap(); let computed_hash = artifact.class_hash().unwrap(); @@ -938,7 +1111,6 @@ mod tests { ), ), ] - .into_iter() { let artifact = serde_json::from_str::(raw_artifact).unwrap(); let computed_hash = artifact.hinted_class_hash().unwrap(); diff --git a/starknet-core/src/types/contract/mod.rs b/starknet-core/src/types/contract/mod.rs index 24046f9b..d7ee6d62 100644 --- a/starknet-core/src/types/contract/mod.rs +++ b/starknet-core/src/types/contract/mod.rs @@ -16,7 +16,7 @@ use crate::{ /// Module containing types related to artifacts of contracts compiled with a Cairo 0.x compiler. pub mod legacy; -/// Cairo string for "CONTRACT_CLASS_V0.1.0" +/// Cairo string for `CONTRACT_CLASS_V0.1.0` const PREFIX_CONTRACT_CLASS_V0_1_0: Felt = Felt::from_raw([ 37302452645455172, 18446734822722598327, @@ -24,7 +24,7 @@ const PREFIX_CONTRACT_CLASS_V0_1_0: Felt = Felt::from_raw([ 5800711240972404213, ]); -/// Cairo string for "COMPILED_CLASS_V1" +/// Cairo string for `COMPILED_CLASS_V1` const PREFIX_COMPILED_CLASS_V1: Felt = Felt::from_raw([ 324306817650036332, 18446744073709549462, @@ -32,223 +32,335 @@ const PREFIX_COMPILED_CLASS_V1: Felt = Felt::from_raw([ 2291010424822318237, ]); +/// Cairo contract artifact in a representation identical to the compiler output. #[derive(Debug, Clone, Serialize)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum ContractArtifact { + /// Sierra (Cairo 1) class. SierraClass(SierraClass), + /// Cairo assembly (CASM) class compiled from a Sierra class. CompiledClass(CompiledClass), + /// Legacy (Cairo 0) class LegacyClass(legacy::LegacyContractClass), } +/// A Sierra (Cairo 1) contract class in a representation identical to the compiler output. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct SierraClass { + /// Sierra bytecode. #[serde_as(as = "Vec")] pub sierra_program: Vec, + /// Sierra class debug info. pub sierra_program_debug_info: SierraClassDebugInfo, + /// Sierra version. pub contract_class_version: String, + /// Contract entrypoints. pub entry_points_by_type: EntryPointsByType, + /// Contract ABI. pub abi: Vec, } +/// A Cairo assembly (CASM) class compiled from a Sierra class. +/// +/// The Sierra to CASM process is needed as the Cairo VM can only execute CASM, not Sierra bytecode. +/// However, if direct deployment of CASM were allowed, unsafe (unprovable) programs could be +/// deployed to a public network allowing for DOS attacks. Instead, only Sierra, an intermedia +/// representation that always compiles to safe (provable) CASM, is allowed to be deployed. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct CompiledClass { + /// The STARK field prime. pub prime: String, + /// Version of the compiler used to compile this contract. pub compiler_version: String, + /// Cairo assembly bytecode. #[serde_as(as = "Vec")] pub bytecode: Vec, /// Represents the structure of the bytecode segments, using a nested list of segment lengths. /// For example, [2, [3, 4]] represents a bytecode with 2 segments, the first is a leaf of /// length 2 and the second is a node with 2 children of lengths 3 and 4. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub bytecode_segment_lengths: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bytecode_segment_lengths: Option, + /// Hints for non-determinism. pub hints: Vec, + /// Same as `hints` but represented in Python code, which can be generated by the compiler but + /// is off by default. pub pythonic_hints: Option>, + /// Contract entrypoints. pub entry_points_by_type: CompiledClassEntrypointList, } +/// Debug information optionally generated by the compiler for mapping IDs to human-readable names. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct SierraClassDebugInfo { + /// Mapping from type IDs to names. pub type_names: Vec<(u64, String)>, + /// Mapping from libfunc IDs to names. pub libfunc_names: Vec<(u64, String)>, + /// Mapping from user function IDs to names. pub user_func_names: Vec<(u64, String)>, } +/// Cairo assembly (CASM) contract entrypoints by types. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct CompiledClassEntrypointList { + /// Entrypoints of type `EXTERNAL` used for invocations from outside contracts. pub external: Vec, + /// Entrypoints of type `L1_HANDLER` used for handling L1-to-L2 messages. pub l1_handler: Vec, + /// Entrypoints of type `CONSTRUCTOR` used during contract deployment. pub constructor: Vec, } +/// Sierra (Cairo 1) contract ABI item. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub enum AbiEntry { + /// Function ABI entry. Function(AbiFunction), + /// Event ABI entry. Event(AbiEvent), + /// Struct ABI entry. Struct(AbiStruct), + /// Enum ABI entry. Enum(AbiEnum), + /// Constructor ABI entry. Constructor(AbiConstructor), + /// Impl ABI entry. Impl(AbiImpl), + /// Interface ABI entry. Interface(AbiInterface), + /// L1 handler ABI entry. L1Handler(AbiFunction), } +/// Cairo assembly (CASM) hints for introducing non-determinism into a Cairo program. +/// +/// Unlike legacy (Cairo 0) hints which are arbitrary Python code, hints compiled from Sierra +/// bytecode come from a predefined list of hint specifications (e.g. `TestLessThanOrEqual`). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Hint { + /// ID of the hint. pub id: u64, // For convenience we just treat it as an opaque JSON value here, unless a use case justifies // implementing the structure. (We no longer need the hints for the class hash anyways.) + /// Declarative specification of the hint. pub code: Vec, } +/// Same as [`Hint`] but is represented as Python code instead of a declarative specification. #[derive(Debug, Clone)] pub struct PythonicHint { + /// ID of the hint. pub id: u64, + /// Python code representation of the hint. pub code: Vec, } +/// An Cairo assembly (CASM) contract entrypoint for translating a selector to a bytecode offset. #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct CompiledClassEntrypoint { + /// Selector of the entrypoint, usually computed as the Starknet Keccak of the function name. #[serde_as(as = "UfeHex")] pub selector: Felt, + /// Offset in the bytecode. pub offset: u64, + /// The list of Cairo builtins used with this entrypoint. pub builtins: Vec, } +/// Sierra (Cairo 1) contract ABI representation of a function. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiFunction { + /// Name of the function. pub name: String, + /// Inputs to the function. pub inputs: Vec, + /// Outputs of the function. pub outputs: Vec, + /// State mutability of the function. + /// + /// Note that this is currently not enforced by the compiler. It's therefore only as accurate as + /// the code author annotating them is. pub state_mutability: StateMutability, } +/// Sierra (Cairo 1) contract ABI representation of an event. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] #[serde(untagged)] pub enum AbiEvent { - /// Cairo 2.x ABI event entry + /// Cairo 2.x ABI event entry. Typed(TypedAbiEvent), - /// Cairo 1.x ABI event entry + /// Cairo 1.x ABI event entry. Untyped(UntypedAbiEvent), } +/// Cairo 2.x ABI event entry. #[derive(Debug, Clone, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub enum TypedAbiEvent { + /// An event definition that's a struct. Struct(AbiEventStruct), + /// An event definition that's an enum. Enum(AbiEventEnum), } +/// Cairo 1.x ABI event entry. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct UntypedAbiEvent { + /// Name of the event. pub name: String, + /// Fields of the event. pub inputs: Vec, } +/// An event definition that's a struct. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiEventStruct { + /// Name of the event struct. pub name: String, + /// Fields of the event struct. pub members: Vec, } +/// An event definition that's an enum. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiEventEnum { + /// Name of the event enum. pub name: String, + /// Variants of the event enum. pub variants: Vec, } +/// Sierra (Cairo 1) contract ABI representation of a struct. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiStruct { + /// Name of the struct. pub name: String, + /// Fields of the struct. pub members: Vec, } +/// Sierra (Cairo 1) contract ABI representation of a constructor. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiConstructor { + /// Name of the constructor. pub name: String, + /// Inputs to the constructor. pub inputs: Vec, } +/// Sierra (Cairo 1) contract ABI representation of an interface implementation. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiImpl { + /// Name of the interface implementation. pub name: String, + /// Name of the interface being implemented. pub interface_name: String, } +/// Sierra (Cairo 1) contract ABI representation of an interface. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiInterface { + /// Name of the interface. pub name: String, + /// The shape of the interface. pub items: Vec, } +/// Sierra (Cairo 1) contract ABI representation of an enum. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiEnum { + /// Name of the enum. pub name: String, + /// Variants of the enum. pub variants: Vec, } +/// A name and type pair for describing a struct field. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiNamedMember { + /// Name of the field. pub name: String, + /// Type of the field. pub r#type: String, } +/// An output from a contract function. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct AbiOutput { + /// Type of the output. pub r#type: String, } +/// Struct field or enum variant of an event type. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] pub struct EventField { + /// Name of the field or variant. pub name: String, + /// Type of the field or variant. pub r#type: String, + /// The role this field or variant plays as part of an event emission. pub kind: EventFieldKind, } +/// State mutability of a function. +/// +/// Note that this is currently not enforced by the compiler. It's therefore only as accurate as the +/// code author annotating them is. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum StateMutability { + /// The function is *annotated* as potentially state-changing. External, + /// The function is *annotated* as view-only, without changing the state. View, } +/// The role an event struct field or enum variant plays during an event emission. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum EventFieldKind { + /// Emitted as part of the event keys. Annotated with `#[key]`. Key, + /// Emitted as part of the event data. Annotated with `#[data]`. Data, + /// Serialized as a nested event. Nested, + /// Serialize as a flat event. Annotated with `#[flat]`. Flat, } +/// A node in a bytecode segment length tree. #[derive(Debug, Clone)] pub enum IntOrList { + /// A leave node of the tree representing a segment. Int(u64), + /// A branch node of the tree. List(Vec), } @@ -278,7 +390,7 @@ struct BytecodeSegmentedNode { /// Internal structure used for post-Sierra-1.5.0 CASM hash calculation. /// -/// Represents a child of [BytecodeSegmentedNode]. +/// Represents a child of [`BytecodeSegmentedNode`]. struct BytecodeSegment { segment_length: u64, #[allow(unused)] @@ -290,41 +402,68 @@ mod errors { use alloc::string::*; use core::fmt::{Display, Formatter, Result}; + /// Errors computing a class hash. #[derive(Debug)] pub enum ComputeClassHashError { + /// An invalid Cairo builtin name is found. This can happen when computing hashes for legacy + /// (Cairo 0) or Cairo assembly (CASM) classes. InvalidBuiltinName, + /// The total bytecode length is different than the implied length from + /// `bytecode_segment_lengths`. This can happen when computing Cairo assembly (CASM) class + /// hashes. BytecodeSegmentLengthMismatch(BytecodeSegmentLengthMismatchError), + /// A bytecode segment specified in `bytecode_segment_lengths` is invalid. This can happen + /// when computing Cairo assembly (CASM) class hashes. InvalidBytecodeSegment(InvalidBytecodeSegmentError), + /// The bytecode segments specified in `bytecode_segment_lengths` do not cover the full + /// range of bytecode. This can happen when computing Cairo assembly (CASM) class hashes. PcOutOfRange(PcOutOfRangeError), + /// Json serialization error. This can happen when serializing the contract ABI, as required + /// for computing hashes for legacy (Cairo 0) and Sierra (Cairo 1) classes. Json(JsonError), } + /// Errors compressing a legacy (Cairo 0) class. #[cfg(feature = "std")] #[derive(Debug)] pub enum CompressProgramError { + /// Json serialization error when serializing the contract ABI. Json(JsonError), + /// Gzip encoding error. Io(std::io::Error), } + /// Json serialization error represented as a string message. This is done instead of exposing + /// `serde_json` error types to avoid leaking implementation details and ease error handling. #[derive(Debug)] pub struct JsonError { pub(crate) message: String, } + /// The total bytecode length is different than the implied length from + /// `bytecode_segment_lengths`. #[derive(Debug)] pub struct BytecodeSegmentLengthMismatchError { + /// The total length implied by `bytecode_segment_lengths`. pub segment_length: usize, + /// The actual bytecode length. pub bytecode_length: usize, } + /// The bytecode segment is invalid as it's not consecutive with the prior segment. #[derive(Debug)] pub struct InvalidBytecodeSegmentError { + /// The expected program counter implied from a previous processed segment. pub visited_pc: u64, + /// The actual starting program counter as specified by the segment bytecode offset. pub segment_start: u64, } + /// The bytecode segments specified in `bytecode_segment_lengths` do not cover the full range of + /// bytecode. #[derive(Debug)] pub struct PcOutOfRangeError { + /// The first out-of-range program counter. pub pc: u64, } @@ -410,6 +549,7 @@ pub use errors::{ pub use errors::CompressProgramError; impl SierraClass { + /// Computes the class hash of the Sierra (Cairo 1) class. pub fn class_hash(&self) -> Result { // Technically we don't have to use the Pythonic JSON style here. Doing this just to align // with the official `cairo-lang` CLI. @@ -442,6 +582,22 @@ impl SierraClass { Ok(normalize_address(hasher.finalize())) } + /// Flattens the Sierra (Cairo 1) class by serializing the ABI using a Pythonic JSON + /// representation. The Pythoic JSON format is used for achieving the exact same output as that + /// of `cairo-lang`. + /// + /// The flattening is necessary for class declaration as the network does not deal with + /// structured contract ABI. Instead, any ABI is treated as an opaque string. + /// + /// Therefore, a process is needed for transforming a structured ABI into a string. This process + /// is called "flattening" in this library. The word is chosen instead of "serializing" as the + /// latter implies that there must be a way to "deserialize" the resulting string back to its + /// original, structured form. However, that would not be guaranteed. + /// + /// While it's true that the in *this* particular implementation, the string can *indeed* be + /// deserialized back to the original form, it's easy to imagine other flattening strategies + /// that are more destructive. In the future, this library will also allow custom flattening + /// implementations where the Pythoic style one presented here would merely be one of them. pub fn flatten(self) -> Result { let abi = to_string_pythonic(&self.abi).map_err(|err| JsonError { message: format!("{}", err), @@ -457,6 +613,7 @@ impl SierraClass { } impl FlattenedSierraClass { + /// Computes the class hash of the flattened Sierra (Cairo 1) class. pub fn class_hash(&self) -> Felt { let mut hasher = PoseidonHasher::new(); hasher.update(PREFIX_CONTRACT_CLASS_V0_1_0); @@ -481,6 +638,7 @@ impl FlattenedSierraClass { } impl CompiledClass { + /// Computes the class hash of the Cairo assembly (CASM) class. pub fn class_hash(&self) -> Result { let mut hasher = PoseidonHasher::new(); hasher.update(PREFIX_COMPILED_CLASS_V1); @@ -500,42 +658,45 @@ impl CompiledClass { ); // Hashes bytecode - hasher.update(if self.bytecode_segment_lengths.is_empty() { - // Pre-Sierra-1.5.0 compiled classes - poseidon_hash_many(&self.bytecode) - } else { - // `bytecode_segment_lengths` was added since Sierra 1.5.0 and changed hash calculation. - // This implementation here is basically a direct translation of the Python code from - // `cairo-lang` v0.13.1. The goal was simply to have a working implementation as quickly - // as possible. There should be some optimizations to be made here. - // TODO: review how this can be optimized - - // NOTE: this looks extremely inefficient. Maybe just use a number for tracking instead? - let mut rev_visited_pcs: Vec = (0..(self.bytecode.len() as u64)).rev().collect(); - - let (res, total_len) = Self::create_bytecode_segment_structure_inner( - &self.bytecode, - &IntOrList::List(self.bytecode_segment_lengths.clone()), - &mut rev_visited_pcs, - &mut 0, - )?; - - if total_len != self.bytecode.len() as u64 { - return Err(ComputeClassHashError::BytecodeSegmentLengthMismatch( - BytecodeSegmentLengthMismatchError { - segment_length: total_len as usize, - bytecode_length: self.bytecode.len(), - }, - )); - } - if !rev_visited_pcs.is_empty() { - return Err(ComputeClassHashError::PcOutOfRange(PcOutOfRangeError { - pc: rev_visited_pcs[rev_visited_pcs.len() - 1], - })); - } + hasher.update( + if let Some(bytecode_segment_lengths) = self.bytecode_segment_lengths.clone() { + // `bytecode_segment_lengths` was added since Sierra 1.5.0 and changed hash calculation. + // This implementation here is basically a direct translation of the Python code from + // `cairo-lang` v0.13.1. The goal was simply to have a working implementation as quickly + // as possible. There should be some optimizations to be made here. + // TODO: review how this can be optimized + + // NOTE: this looks extremely inefficient. Maybe just use a number for tracking instead? + let mut rev_visited_pcs: Vec = + (0..(self.bytecode.len() as u64)).rev().collect(); + + let (res, total_len) = Self::create_bytecode_segment_structure_inner( + &self.bytecode, + &bytecode_segment_lengths, + &mut rev_visited_pcs, + &mut 0, + )?; + + if total_len != self.bytecode.len() as u64 { + return Err(ComputeClassHashError::BytecodeSegmentLengthMismatch( + BytecodeSegmentLengthMismatchError { + segment_length: total_len as usize, + bytecode_length: self.bytecode.len(), + }, + )); + } + if !rev_visited_pcs.is_empty() { + return Err(ComputeClassHashError::PcOutOfRange(PcOutOfRangeError { + pc: rev_visited_pcs[rev_visited_pcs.len() - 1], + })); + } - res.hash() - }); + res.hash() + } else { + // Pre-Sierra-1.5.0 compiled classes + poseidon_hash_many(&self.bytecode) + }, + ); Ok(hasher.finalize()) } @@ -545,12 +706,12 @@ impl CompiledClass { ) -> Result { let mut hasher = PoseidonHasher::new(); - for entry in entrypoints.iter() { + for entry in entrypoints { hasher.update(entry.selector); hasher.update(entry.offset.into()); let mut builtin_hasher = PoseidonHasher::new(); - for builtin in entry.builtins.iter() { + for builtin in &entry.builtins { builtin_hasher.update(cairo_short_string_to_felt(builtin)?) } @@ -594,7 +755,7 @@ impl CompiledClass { let mut res = Vec::new(); let mut total_len = 0; - for item in bytecode_segment_lengths.iter() { + for item in bytecode_segment_lengths { let visited_pc_before = if !visited_pcs.is_empty() { Some(visited_pcs[visited_pcs.len() - 1]) } else { @@ -666,7 +827,7 @@ impl BytecodeLeaf { impl BytecodeSegmentedNode { fn hash(&self) -> Felt { let mut hasher = PoseidonHasher::new(); - for node in self.segments.iter() { + for node in &self.segments { hasher.update(node.segment_length.into()); hasher.update(node.inner_structure.hash()); } @@ -761,7 +922,7 @@ impl Serialize for TypedAbiEvent { } match self { - TypedAbiEvent::Struct(inner) => StructRef::serialize( + Self::Struct(inner) => StructRef::serialize( &StructRef { name: &inner.name, kind: "struct", @@ -769,7 +930,7 @@ impl Serialize for TypedAbiEvent { }, serializer, ), - TypedAbiEvent::Enum(inner) => EnumRef::serialize( + Self::Enum(inner) => EnumRef::serialize( &EnumRef { name: &inner.name, kind: "enum", @@ -790,7 +951,7 @@ impl Serialize for IntOrList { Self::Int(int) => serializer.serialize_u64(*int), Self::List(list) => { let mut seq = serializer.serialize_seq(Some(list.len()))?; - for item in list.iter() { + for item in list { seq.serialize_element(item)?; } seq.end() @@ -802,7 +963,7 @@ impl Serialize for IntOrList { impl<'de> Visitor<'de> for IntOrListVisitor { type Value = IntOrList; - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(formatter, "number or list") } @@ -837,7 +998,7 @@ impl<'de> Deserialize<'de> for IntOrList { fn hash_sierra_entrypoints(entrypoints: &[SierraEntryPoint]) -> Felt { let mut hasher = PoseidonHasher::new(); - for entry in entrypoints.iter() { + for entry in entrypoints { hasher.update(entry.selector); hasher.update(entry.function_idx.into()); } @@ -864,13 +1025,20 @@ mod tests { include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types_sierra.txt"), include_str!("../../../test-data/contracts/cairo2/artifacts/erc20_sierra.txt"), include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20_sierra.txt"), - ] - .into_iter() - { - match serde_json::from_str::(raw_artifact) { - Ok(ContractArtifact::SierraClass(_)) => {} + include_str!("../../../test-data/contracts/cairo2.6/artifacts/trivial_sierra.txt"), + ] { + let direct_deser = serde_json::from_str::(raw_artifact).unwrap(); + let via_contract_artifact = match serde_json::from_str::(raw_artifact) + { + Ok(ContractArtifact::SierraClass(class)) => class, _ => panic!("Unexpected result"), - } + }; + + // Class should be identical however it's deserialized + assert_eq!( + direct_deser.class_hash().unwrap(), + via_contract_artifact.class_hash().unwrap() + ); } } @@ -883,13 +1051,20 @@ mod tests { include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types_compiled.txt"), include_str!("../../../test-data/contracts/cairo2/artifacts/erc20_compiled.txt"), include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20_compiled.txt"), - ] - .into_iter() - { - match serde_json::from_str::(raw_artifact) { - Ok(ContractArtifact::CompiledClass(_)) => {} + include_str!("../../../test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt"), + ] { + let direct_deser = serde_json::from_str::(raw_artifact).unwrap(); + let via_contract_artifact = match serde_json::from_str::(raw_artifact) + { + Ok(ContractArtifact::CompiledClass(class)) => class, _ => panic!("Unexpected result"), - } + }; + + // Class should be identical however it's deserialized + assert_eq!( + direct_deser.class_hash().unwrap(), + via_contract_artifact.class_hash().unwrap() + ); } } @@ -924,9 +1099,7 @@ mod tests { include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types_sierra.txt"), include_str!("../../../test-data/contracts/cairo2/artifacts/abi_types.hashes.json"), ), - ] - .into_iter() - { + ] { let sierra_class = serde_json::from_str::(raw_artifact).unwrap(); let computed_hash = sierra_class.class_hash().unwrap(); @@ -965,9 +1138,13 @@ mod tests { include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20_compiled.txt"), include_str!("../../../test-data/contracts/cairo2.6/artifacts/erc20.hashes.json"), ), - ] - .into_iter() - { + ( + include_str!( + "../../../test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt" + ), + include_str!("../../../test-data/contracts/cairo2.6/artifacts/trivial.hashes.json"), + ), + ] { let compiled_class = serde_json::from_str::(raw_artifact).unwrap(); let computed_hash = compiled_class.class_hash().unwrap(); diff --git a/starknet-core/src/types/conversions.rs b/starknet-core/src/types/conversions.rs index 38e7aa5f..a4a9ba1d 100644 --- a/starknet-core/src/types/conversions.rs +++ b/starknet-core/src/types/conversions.rs @@ -10,33 +10,29 @@ impl From for RawLegacyAbiEntry { fn from(value: LegacyContractAbiEntry) -> Self { match value { LegacyContractAbiEntry::Function(inner) => match inner.r#type { - LegacyFunctionAbiType::Function => RawLegacyAbiEntry::Function(RawLegacyFunction { + LegacyFunctionAbiType::Function => Self::Function(RawLegacyFunction { inputs: inner.inputs, name: inner.name, outputs: inner.outputs, state_mutability: inner.state_mutability, }), - LegacyFunctionAbiType::L1Handler => { - RawLegacyAbiEntry::L1Handler(RawLegacyL1Handler { - inputs: inner.inputs, - name: inner.name, - outputs: inner.outputs, - }) - } - LegacyFunctionAbiType::Constructor => { - RawLegacyAbiEntry::Constructor(RawLegacyConstructor { - inputs: inner.inputs, - name: inner.name, - outputs: inner.outputs, - }) - } + LegacyFunctionAbiType::L1Handler => Self::L1Handler(RawLegacyL1Handler { + inputs: inner.inputs, + name: inner.name, + outputs: inner.outputs, + }), + LegacyFunctionAbiType::Constructor => Self::Constructor(RawLegacyConstructor { + inputs: inner.inputs, + name: inner.name, + outputs: inner.outputs, + }), }, - LegacyContractAbiEntry::Event(inner) => RawLegacyAbiEntry::Event(RawLegacyEvent { + LegacyContractAbiEntry::Event(inner) => Self::Event(RawLegacyEvent { data: inner.data, keys: inner.keys, name: inner.name, }), - LegacyContractAbiEntry::Struct(inner) => RawLegacyAbiEntry::Struct(RawLegacyStruct { + LegacyContractAbiEntry::Struct(inner) => Self::Struct(RawLegacyStruct { members: inner .members .into_iter() diff --git a/starknet-core/src/types/eth_address.rs b/starknet-core/src/types/eth_address.rs index dfc3028e..1d0409ae 100644 --- a/starknet-core/src/types/eth_address.rs +++ b/starknet-core/src/types/eth_address.rs @@ -12,6 +12,7 @@ const MAX_L1_ADDRESS: Felt = Felt::from_raw([ 18406070939574861858, ]); +/// Ethereum address represented with a 20-byte array. #[derive(Debug, Clone, PartialEq, Eq)] pub struct EthAddress { inner: [u8; 20], @@ -22,15 +23,21 @@ struct EthAddressVisitor; mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors parsing [`EthAddress`](super::EthAddress) from a hex string. #[derive(Debug)] pub enum FromHexError { + /// The hex string is not 40 hexadecimal characters in length without the `0x` prefix. UnexpectedLength, + /// The string contains non-hexadecimal characters. InvalidHexString, } + /// The [`Felt`](super::Felt) value is out of range for converting into + /// [`EthAddress`](super::EthAddress). #[derive(Debug)] pub struct FromFieldElementError; + /// The byte slice is out of range for converting into [`EthAddress`](super::EthAddress). #[derive(Debug)] pub struct FromBytesSliceError; @@ -71,19 +78,23 @@ mod errors { pub use errors::{FromBytesSliceError, FromFieldElementError, FromHexError}; impl EthAddress { + /// Constructs [`EthAddress`] from a byte array. pub const fn from_bytes(bytes: [u8; 20]) -> Self { Self { inner: bytes } } + /// Parses [`EthAddress`] from a hex string. pub fn from_hex(hex: &str) -> Result { hex.parse() } + /// Constructs [`EthAddress`] from a [`Felt`]. pub fn from_felt(felt: &Felt) -> Result { felt.try_into() } - pub fn as_bytes(&self) -> &[u8; 20] { + /// Gets a reference to the underlying byte array. + pub const fn as_bytes(&self) -> &[u8; 20] { &self.inner } } @@ -106,10 +117,10 @@ impl<'de> Deserialize<'de> for EthAddress { } } -impl<'de> Visitor<'de> for EthAddressVisitor { +impl Visitor<'_> for EthAddressVisitor { type Value = EthAddress; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { write!(formatter, "string") } @@ -169,7 +180,7 @@ impl TryFrom<&Felt> for EthAddress { impl From for Felt { fn from(value: EthAddress) -> Self { // Safe to unwrap here as the value is never out of range - Felt::from_bytes_be_slice(&value.inner) + Self::from_bytes_be_slice(&value.inner) } } @@ -196,7 +207,7 @@ impl From<[u8; 20]> for EthAddress { #[cfg(test)] mod tests { - use super::{EthAddress, Felt, FromBytesSliceError, FromFieldElementError}; + use super::{EthAddress, Felt}; use alloc::vec::*; @@ -280,11 +291,12 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_eth_address_from_felt_error() { - match EthAddress::from_felt( + if EthAddress::from_felt( &Felt::from_hex("0x10000000000000000000000000000000000000000").unwrap(), - ) { - Ok(_) => panic!("Expected error, but got Ok"), - Err(FromFieldElementError) => {} + ) + .is_ok() + { + panic!("Expected error, but got Ok"); } } @@ -293,9 +305,8 @@ mod tests { fn test_eth_address_from_slice_invalid_slice() { let buffer: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7]; - match EthAddress::try_from(&buffer[0..4]) { - Ok(_) => panic!("Expected error, but got Ok"), - Err(FromBytesSliceError) => {} + if EthAddress::try_from(&buffer[0..4]).is_ok() { + panic!("Expected error, but got Ok"); } } } diff --git a/starknet-core/src/types/execution_result.rs b/starknet-core/src/types/execution_result.rs index 2a757531..fe2f83c1 100644 --- a/starknet-core/src/types/execution_result.rs +++ b/starknet-core/src/types/execution_result.rs @@ -4,18 +4,28 @@ use serde::{Deserialize, Serialize}; use super::TransactionExecutionStatus; -/// A more idiomatic way to access `execution_status` and `revert_reason`. +/// Execution result of a transaction. +/// +/// This struct ccorresponds to the `execution_status` and `revert_reason` fields of a transaction +/// receipt, capturing the fact that the presence of `revert_reason` depends on `execution_status`, +/// allowing more idiomatic access to `revert_reason`. #[derive(Debug, Clone, PartialEq, Eq)] pub enum ExecutionResult { + /// The execution succeeded. Succeeded, - Reverted { reason: String }, + /// The execution reverted. + Reverted { + /// The reason that the execution was reverted. + reason: String, + }, } impl ExecutionResult { - pub fn status(&self) -> TransactionExecutionStatus { + /// Gets the [`TransactionExecutionStatus`]. + pub const fn status(&self) -> TransactionExecutionStatus { match self { - ExecutionResult::Succeeded => TransactionExecutionStatus::Succeeded, - ExecutionResult::Reverted { .. } => TransactionExecutionStatus::Reverted, + Self::Succeeded => TransactionExecutionStatus::Succeeded, + Self::Reverted { .. } => TransactionExecutionStatus::Reverted, } } @@ -25,8 +35,8 @@ impl ExecutionResult { /// variant. pub fn revert_reason(&self) -> Option<&str> { match self { - ExecutionResult::Succeeded => None, - ExecutionResult::Reverted { reason } => Some(reason), + Self::Succeeded => None, + Self::Reverted { reason } => Some(reason), } } } diff --git a/starknet-core/src/types/hash_256.rs b/starknet-core/src/types/hash_256.rs index dd3e31d0..4513d8f6 100644 --- a/starknet-core/src/types/hash_256.rs +++ b/starknet-core/src/types/hash_256.rs @@ -9,6 +9,7 @@ use starknet_types_core::felt::Felt; const HASH_256_BYTE_COUNT: usize = 32; +/// A 256-bit cryptographic hash. #[derive(Clone, Copy, PartialEq, Eq)] pub struct Hash256 { inner: [u8; HASH_256_BYTE_COUNT], @@ -19,12 +20,16 @@ struct Hash256Visitor; mod errors { use core::fmt::{Display, Formatter, Result}; + /// Errors parsing [`Hash256`](super::Hash256) from a hex string. #[derive(Debug)] pub enum FromHexError { + /// The hex string is not 64 hexadecimal characters in length without the `0x` prefix. UnexpectedLength, + /// The string contains non-hexadecimal characters. InvalidHexString, } + /// The hash value is out of range for converting into [`Felt`](super::Felt). #[derive(Debug)] pub struct ToFieldElementError; @@ -56,19 +61,23 @@ mod errors { pub use errors::{FromHexError, ToFieldElementError}; impl Hash256 { + /// Constructs [`Hash256`] from a byte array. pub const fn from_bytes(bytes: [u8; HASH_256_BYTE_COUNT]) -> Self { Self { inner: bytes } } + /// Parses [`Hash256`] from a hex string. pub fn from_hex(hex: &str) -> Result { hex.parse() } + /// Constructs [`Hash256`] from a [`Felt`]. pub fn from_felt(felt: &Felt) -> Self { felt.into() } - pub fn as_bytes(&self) -> &[u8; HASH_256_BYTE_COUNT] { + /// Gets a reference to the underlying byte array. + pub const fn as_bytes(&self) -> &[u8; HASH_256_BYTE_COUNT] { &self.inner } } @@ -78,7 +87,11 @@ impl Serialize for Hash256 { where S: serde::Serializer, { - serializer.serialize_str(&format!("0x{}", hex::encode(self.inner))) + if serializer.is_human_readable() { + serializer.serialize_str(&format!("0x{}", hex::encode(self.inner))) + } else { + serializer.serialize_bytes(self.as_bytes()) + } } } @@ -87,15 +100,19 @@ impl<'de> Deserialize<'de> for Hash256 { where D: serde::Deserializer<'de>, { - deserializer.deserialize_any(Hash256Visitor) + if deserializer.is_human_readable() { + deserializer.deserialize_any(Hash256Visitor) + } else { + deserializer.deserialize_bytes(Hash256Visitor) + } } } -impl<'de> Visitor<'de> for Hash256Visitor { +impl Visitor<'_> for Hash256Visitor { type Value = Hash256; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { - write!(formatter, "string") + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { + write!(formatter, "string, or an array of u8") } fn visit_str(self, v: &str) -> Result @@ -105,6 +122,12 @@ impl<'de> Visitor<'de> for Hash256Visitor { v.parse() .map_err(|err| serde::de::Error::custom(format!("{}", err))) } + + fn visit_bytes(self, v: &[u8]) -> Result { + <[u8; HASH_256_BYTE_COUNT]>::try_from(v) + .map(Hash256::from_bytes) + .map_err(serde::de::Error::custom) + } } impl FromStr for Hash256 { @@ -172,7 +195,7 @@ impl TryFrom<&Hash256> for Felt { type Error = ToFieldElementError; fn try_from(value: &Hash256) -> Result { - Ok(Felt::from_bytes_be(&value.inner)) + Ok(Self::from_bytes_be(&value.inner)) } } @@ -186,6 +209,8 @@ impl From<[u8; HASH_256_BYTE_COUNT]> for Hash256 { mod tests { use super::{Felt, FromHexError, Hash256, HASH_256_BYTE_COUNT}; + use hex_literal::hex; + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] fn test_hash_256_from_hex_error_unexpected_length() { @@ -196,7 +221,7 @@ mod tests { "25c5b1592b1743b62d7fabd4373d98219c2ff3750f49ec0608a8355fa3bb060f5", ]; - for item in test_data.into_iter() { + for item in test_data { match Hash256::from_hex(item) { Err(FromHexError::UnexpectedLength) => {} _ => panic!("Unexpected test result"), @@ -214,7 +239,7 @@ mod tests { "0x?5c5b1592b1743b62d7fabd4373d98219c2f63750f49ec0608a8355fa3bb060", ]; - for item in test_data.into_iter() { + for item in test_data { match Hash256::from_hex(item) { Err(FromHexError::InvalidHexString) => {} _ => panic!("Unexpected test result"), @@ -243,4 +268,32 @@ mod tests { // Assert that the conversion from the `Felt` to `Hash256` is successful assert_eq!(Hash256::from_felt(&felt), hash_256); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_ser() { + let r = bincode::serialize(&Hash256::from_bytes(hex!( + "1111111111111111111111111111111111111111111111111111111111111111" + ))) + .unwrap(); + assert_eq!( + r, + hex!( + "2000000000000000 1111111111111111111111111111111111111111111111111111111111111111" + ) + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn bin_deser() { + let r = bincode::deserialize::(&hex!( + "2000000000000000 1111111111111111111111111111111111111111111111111111111111111111" + )) + .unwrap(); + assert_eq!( + r.inner, + hex!("1111111111111111111111111111111111111111111111111111111111111111") + ); + } } diff --git a/starknet-core/src/types/mod.rs b/starknet-core/src/types/mod.rs index fa51cada..163d5e8e 100644 --- a/starknet-core/src/types/mod.rs +++ b/starknet-core/src/types/mod.rs @@ -5,7 +5,7 @@ use serde_with::serde_as; use crate::serde::unsigned_field_element::UfeHex; -pub use starknet_types_core::felt::{Felt, NonZeroFelt}; +pub use starknet_types_core::felt::*; mod conversions; @@ -41,12 +41,15 @@ pub use codegen::{ TransactionReceiptWithBlockInfo, TransactionTraceWithHash, TransactionWithReceipt, }; +/// Module containing the [`U256`] type. pub mod u256; pub use u256::U256; +/// Module containing the [`EthAddress`] type. pub mod eth_address; pub use eth_address::EthAddress; +/// Module containing the [`Hash256`] type. pub mod hash_256; pub use hash_256::Hash256; @@ -65,54 +68,89 @@ pub use byte_array::ByteArray; mod msg; pub use msg::MsgToL2; +mod call; +pub use call::Call; + // TODO: move generated request code to `starknet-providers` +/// Module containing JSON-RPC request types. pub mod requests; +/// Module containing types related to Starknet contracts/classes. pub mod contract; pub use contract::ContractArtifact; +/// A block with transaction hashes that may or may not be pending. +/// +/// A pending block lacks certain information on the block header compared to a non-pending block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithTxHashes { + /// A confirmed, non-pending block. Block(BlockWithTxHashes), + /// A pending block. PendingBlock(PendingBlockWithTxHashes), } +/// A block with full transactions that may or may not be pending. +/// +/// A pending block lacks certain information on the block header compared to a non-pending block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithTxs { + /// A confirmed, non-pending block. Block(BlockWithTxs), + /// A pending block. PendingBlock(PendingBlockWithTxs), } +/// A block with full transactions and receipts that may or may not be pending. +/// +/// A pending block lacks certain information on the block header compared to a non-pending block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingBlockWithReceipts { + /// A confirmed, non-pending block. Block(BlockWithReceipts), + /// A pending block. PendingBlock(PendingBlockWithReceipts), } +/// State update of a block that may or may not be pending. +/// +/// State update for a pending block lacks certain information compared to that of a non-pending +/// block. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum MaybePendingStateUpdate { + /// The state update is for a confirmed, non-pending block. Update(StateUpdate), + /// The state update is for a pending block. PendingUpdate(PendingStateUpdate), } +/// The hash and number (height) for a block. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BlockHashAndNumber { + /// The block's hash. #[serde_as(as = "UfeHex")] pub block_hash: Felt, + /// The block's number (height). pub block_number: u64, } +/// A Starknet client node's synchronization status. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyncStatusType { + /// The node is synchronizing. Syncing(SyncStatus), + /// The node is not synchronizing. NotSyncing, } +/// A "page" of events in a cursor-based pagniation system. +/// +/// This type is usually returned from the `starknet_getEvents` RPC method. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EventsPage { /// Matching events @@ -124,6 +162,7 @@ pub struct EventsPage { pub continuation_token: Option, } +/// Response for broadcasting an `INVOKE` transaction. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct InvokeTransactionResult { @@ -132,6 +171,7 @@ pub struct InvokeTransactionResult { pub transaction_hash: Felt, } +/// Response for broadcasting a `DECLARE` transaction. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeclareTransactionResult { @@ -143,6 +183,10 @@ pub struct DeclareTransactionResult { pub class_hash: Felt, } +/// Response for broadcasting a `DEPLOY` transaction. +/// +/// Note that `DEPLOY` transactions have been deprecated and disabled on all public Starknet +/// networks. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeployTransactionResult { @@ -154,6 +198,7 @@ pub struct DeployTransactionResult { pub contract_address: Felt, } +/// Response for broadcasting a `DEPLOY_ACCOUNT` transaction. #[serde_as] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct DeployAccountTransactionResult { @@ -165,18 +210,25 @@ pub struct DeployAccountTransactionResult { pub contract_address: Felt, } -/// Block hash, number or tag +/// Block identifier in the form of hash, number or tag. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BlockId { + /// Block hash. Hash(Felt), + /// Block number (height). Number(u64), + /// Block tag. Tag(BlockTag), } +/// A "processed" contract class representation that's circulated in the network. This is different +/// from the class representation of compiler output. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum ContractClass { + /// A "processed" Sierra (Cairo 1) class. Sierra(FlattenedSierraClass), + /// A "processed" legacy (Cairo 0) class. Legacy(CompressedLegacyContractClass), } @@ -215,136 +267,190 @@ impl TransactionStatus { } } +/// A Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum Transaction { + /// An `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(InvokeTransaction), + /// An `L1_HANDLER` transaction. #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransaction), + /// A `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(DeclareTransaction), + /// A `DEPLOY` transaction. #[serde(rename = "DEPLOY")] Deploy(DeployTransaction), + /// A `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransaction), } +/// A Starknet transaction in its "mempool" representation that's broadcast by a client. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum BroadcastedTransaction { + /// An `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(BroadcastedInvokeTransaction), + /// A `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(BroadcastedDeclareTransaction), + /// A `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(BroadcastedDeployAccountTransaction), } +/// An `INVOKE` Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] pub enum InvokeTransaction { + /// Version 0 `INVOKE` transaction. #[serde(rename = "0x0")] V0(InvokeTransactionV0), + /// Version 1 `INVOKE` transaction. #[serde(rename = "0x1")] V1(InvokeTransactionV1), + /// Version 3 `INVOKE` transaction. #[serde(rename = "0x3")] V3(InvokeTransactionV3), } +/// A `DECLARE` Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] pub enum DeclareTransaction { + /// Version 0 `DECLARE` transaction. #[serde(rename = "0x0")] V0(DeclareTransactionV0), + /// Version 1 `DECLARE` transaction. #[serde(rename = "0x1")] V1(DeclareTransactionV1), + /// Version 2 `DECLARE` transaction. #[serde(rename = "0x2")] V2(DeclareTransactionV2), + /// Version 3 `DECLARE` transaction. #[serde(rename = "0x3")] V3(DeclareTransactionV3), } +/// A `DEPLOY_ACCOUNT` Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "version")] pub enum DeployAccountTransaction { + /// Version 1 `DEPLOY_ACCOUNT` transaction. #[serde(rename = "0x1")] V1(DeployAccountTransactionV1), + /// Version 3 `DEPLOY_ACCOUNT` transaction. #[serde(rename = "0x3")] V3(DeployAccountTransactionV3), } +/// An `INVOKE` Starknet transaction in its "mempool" representation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum BroadcastedInvokeTransaction { + /// Version 1 `INVOKE` transaction. V1(BroadcastedInvokeTransactionV1), + /// Version 3 `INVOKE` transaction. V3(BroadcastedInvokeTransactionV3), } +/// A `DECLARE` Starknet transaction in its "mempool" representation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum BroadcastedDeclareTransaction { + /// Version 1 `DECLARE` transaction. V1(BroadcastedDeclareTransactionV1), + /// Version 2 `DECLARE` transaction. V2(BroadcastedDeclareTransactionV2), + /// Version 3 `DECLARE` transaction. V3(BroadcastedDeclareTransactionV3), } +/// A `DEPLOY_ACCOUNT` Starknet transaction in its "mempool" representation. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum BroadcastedDeployAccountTransaction { + /// Version 1 `DEPLOY_ACCOUNT` transaction. V1(BroadcastedDeployAccountTransactionV1), + /// Version 3 `DEPLOY_ACCOUNT` transaction. V3(BroadcastedDeployAccountTransactionV3), } +/// Starknet transaction receipt containing execution results. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum TransactionReceipt { + /// Receipt for an `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(InvokeTransactionReceipt), + /// Receipt for an `L1_HANDLER` transaction. #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransactionReceipt), + /// Receipt for a `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(DeclareTransactionReceipt), + /// Receipt for a `DEPLOY` transaction. #[serde(rename = "DEPLOY")] Deploy(DeployTransactionReceipt), + /// Receipt for a `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransactionReceipt), } +/// ABI entry item for legacy (Cairo 0) contract classes. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum LegacyContractAbiEntry { + /// ABI entry representing a Cairo function. Function(LegacyFunctionAbiEntry), + /// ABI entry representing a Starknet event. Event(LegacyEventAbiEntry), + /// ABI entry representing a Cairo struct. Struct(LegacyStructAbiEntry), } +/// Execution trace of a Starknet transaction. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(tag = "type")] pub enum TransactionTrace { + /// Trace for an `INVOKE` transaction. #[serde(rename = "INVOKE")] Invoke(InvokeTransactionTrace), + /// Trace for a `DEPLOY_ACCOUNT` transaction. #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransactionTrace), + /// Trace for an `L1_HANDLER` transaction. #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransactionTrace), + /// Trace for a `DECLARE` transaction. #[serde(rename = "DECLARE")] Declare(DeclareTransactionTrace), } +/// The execution result of a function invocation. +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(untagged)] pub enum ExecuteInvocation { + /// Successful invocation. Success(FunctionInvocation), + /// Failed and reverted invocation. Reverted(RevertedInvocation), } mod errors { use core::fmt::{Display, Formatter, Result}; - #[derive(Debug, PartialEq)] + /// Errors parsing an L1-to-L2 message from transaction calldata. + #[derive(Debug, PartialEq, Eq)] pub enum ParseMsgToL2Error { + /// The transaction calldata is empty. EmptyCalldata, + /// The L1 sender address is longer than 20 bytes. FromAddressOutOfRange, } @@ -370,139 +476,166 @@ mod errors { pub use errors::ParseMsgToL2Error; impl MaybePendingBlockWithTxHashes { + /// Gets a reference to the list of transaction hashes. pub fn transactions(&self) -> &[Felt] { match self { - MaybePendingBlockWithTxHashes::Block(block) => &block.transactions, - MaybePendingBlockWithTxHashes::PendingBlock(block) => &block.transactions, + Self::Block(block) => &block.transactions, + Self::PendingBlock(block) => &block.transactions, } } - pub fn l1_gas_price(&self) -> &ResourcePrice { + /// Gets a reference to the L1 gas price. + pub const fn l1_gas_price(&self) -> &ResourcePrice { match self { - MaybePendingBlockWithTxHashes::Block(block) => &block.l1_gas_price, - MaybePendingBlockWithTxHashes::PendingBlock(block) => &block.l1_gas_price, + Self::Block(block) => &block.l1_gas_price, + Self::PendingBlock(block) => &block.l1_gas_price, } } } impl MaybePendingBlockWithTxs { + /// Gets a reference to the list of transactions. pub fn transactions(&self) -> &[Transaction] { match self { - MaybePendingBlockWithTxs::Block(block) => &block.transactions, - MaybePendingBlockWithTxs::PendingBlock(block) => &block.transactions, + Self::Block(block) => &block.transactions, + Self::PendingBlock(block) => &block.transactions, } } - pub fn l1_gas_price(&self) -> &ResourcePrice { + /// Gets a reference to the L1 gas price. + pub const fn l1_gas_price(&self) -> &ResourcePrice { match self { - MaybePendingBlockWithTxs::Block(block) => &block.l1_gas_price, - MaybePendingBlockWithTxs::PendingBlock(block) => &block.l1_gas_price, + Self::Block(block) => &block.l1_gas_price, + Self::PendingBlock(block) => &block.l1_gas_price, } } } impl MaybePendingBlockWithReceipts { + /// Gets a reference to the list of transactions with receipts. pub fn transactions(&self) -> &[TransactionWithReceipt] { match self { - MaybePendingBlockWithReceipts::Block(block) => &block.transactions, - MaybePendingBlockWithReceipts::PendingBlock(block) => &block.transactions, + Self::Block(block) => &block.transactions, + Self::PendingBlock(block) => &block.transactions, } } - pub fn l1_gas_price(&self) -> &ResourcePrice { + /// Gets a reference to the L1 gas price. + pub const fn l1_gas_price(&self) -> &ResourcePrice { match self { - MaybePendingBlockWithReceipts::Block(block) => &block.l1_gas_price, - MaybePendingBlockWithReceipts::PendingBlock(block) => &block.l1_gas_price, + Self::Block(block) => &block.l1_gas_price, + Self::PendingBlock(block) => &block.l1_gas_price, } } } impl TransactionStatus { - pub fn finality_status(&self) -> SequencerTransactionStatus { + /// Gets a reference to the transaction's finality status. + pub const fn finality_status(&self) -> SequencerTransactionStatus { match self { - TransactionStatus::Received => SequencerTransactionStatus::Received, - TransactionStatus::Rejected => SequencerTransactionStatus::Rejected, - TransactionStatus::AcceptedOnL2(_) => SequencerTransactionStatus::AcceptedOnL2, - TransactionStatus::AcceptedOnL1(_) => SequencerTransactionStatus::AcceptedOnL1, + Self::Received => SequencerTransactionStatus::Received, + Self::Rejected => SequencerTransactionStatus::Rejected, + Self::AcceptedOnL2(_) => SequencerTransactionStatus::AcceptedOnL2, + Self::AcceptedOnL1(_) => SequencerTransactionStatus::AcceptedOnL1, } } } impl Transaction { - pub fn transaction_hash(&self) -> &Felt { + /// Gets a reference to the transaction's hash. + pub const fn transaction_hash(&self) -> &Felt { match self { - Transaction::Invoke(tx) => tx.transaction_hash(), - Transaction::L1Handler(tx) => &tx.transaction_hash, - Transaction::Declare(tx) => tx.transaction_hash(), - Transaction::Deploy(tx) => &tx.transaction_hash, - Transaction::DeployAccount(tx) => tx.transaction_hash(), + Self::Invoke(tx) => tx.transaction_hash(), + Self::L1Handler(tx) => &tx.transaction_hash, + Self::Declare(tx) => tx.transaction_hash(), + Self::Deploy(tx) => &tx.transaction_hash, + Self::DeployAccount(tx) => tx.transaction_hash(), } } } impl InvokeTransaction { - pub fn transaction_hash(&self) -> &Felt { + /// Gets a reference to the transaction's hash. + pub const fn transaction_hash(&self) -> &Felt { match self { - InvokeTransaction::V0(tx) => &tx.transaction_hash, - InvokeTransaction::V1(tx) => &tx.transaction_hash, - InvokeTransaction::V3(tx) => &tx.transaction_hash, + Self::V0(tx) => &tx.transaction_hash, + Self::V1(tx) => &tx.transaction_hash, + Self::V3(tx) => &tx.transaction_hash, } } } impl DeclareTransaction { - pub fn transaction_hash(&self) -> &Felt { + /// Gets a reference to the transaction's hash. + pub const fn transaction_hash(&self) -> &Felt { match self { - DeclareTransaction::V0(tx) => &tx.transaction_hash, - DeclareTransaction::V1(tx) => &tx.transaction_hash, - DeclareTransaction::V2(tx) => &tx.transaction_hash, - DeclareTransaction::V3(tx) => &tx.transaction_hash, + Self::V0(tx) => &tx.transaction_hash, + Self::V1(tx) => &tx.transaction_hash, + Self::V2(tx) => &tx.transaction_hash, + Self::V3(tx) => &tx.transaction_hash, } } } impl DeployAccountTransaction { - pub fn transaction_hash(&self) -> &Felt { + /// Gets a reference to the transaction's hash. + pub const fn transaction_hash(&self) -> &Felt { match self { - DeployAccountTransaction::V1(tx) => &tx.transaction_hash, - DeployAccountTransaction::V3(tx) => &tx.transaction_hash, + Self::V1(tx) => &tx.transaction_hash, + Self::V3(tx) => &tx.transaction_hash, } } } impl TransactionReceipt { - pub fn transaction_hash(&self) -> &Felt { + /// Gets a reference to the transaction's hash. + pub const fn transaction_hash(&self) -> &Felt { + match self { + Self::Invoke(receipt) => &receipt.transaction_hash, + Self::L1Handler(receipt) => &receipt.transaction_hash, + Self::Declare(receipt) => &receipt.transaction_hash, + Self::Deploy(receipt) => &receipt.transaction_hash, + Self::DeployAccount(receipt) => &receipt.transaction_hash, + } + } + + /// Gets a reference to the transaction's finality status. + pub const fn finality_status(&self) -> &TransactionFinalityStatus { match self { - TransactionReceipt::Invoke(receipt) => &receipt.transaction_hash, - TransactionReceipt::L1Handler(receipt) => &receipt.transaction_hash, - TransactionReceipt::Declare(receipt) => &receipt.transaction_hash, - TransactionReceipt::Deploy(receipt) => &receipt.transaction_hash, - TransactionReceipt::DeployAccount(receipt) => &receipt.transaction_hash, + Self::Invoke(receipt) => &receipt.finality_status, + Self::L1Handler(receipt) => &receipt.finality_status, + Self::Declare(receipt) => &receipt.finality_status, + Self::Deploy(receipt) => &receipt.finality_status, + Self::DeployAccount(receipt) => &receipt.finality_status, } } - pub fn finality_status(&self) -> &TransactionFinalityStatus { + /// Gets a reference to the transaction's execution result. + pub const fn execution_result(&self) -> &ExecutionResult { match self { - TransactionReceipt::Invoke(receipt) => &receipt.finality_status, - TransactionReceipt::L1Handler(receipt) => &receipt.finality_status, - TransactionReceipt::Declare(receipt) => &receipt.finality_status, - TransactionReceipt::Deploy(receipt) => &receipt.finality_status, - TransactionReceipt::DeployAccount(receipt) => &receipt.finality_status, + Self::Invoke(receipt) => &receipt.execution_result, + Self::L1Handler(receipt) => &receipt.execution_result, + Self::Declare(receipt) => &receipt.execution_result, + Self::Deploy(receipt) => &receipt.execution_result, + Self::DeployAccount(receipt) => &receipt.execution_result, } } - pub fn execution_result(&self) -> &ExecutionResult { + /// Gets a reference to the transaction's emitted events. + pub fn events(&self) -> &[Event] { match self { - TransactionReceipt::Invoke(receipt) => &receipt.execution_result, - TransactionReceipt::L1Handler(receipt) => &receipt.execution_result, - TransactionReceipt::Declare(receipt) => &receipt.execution_result, - TransactionReceipt::Deploy(receipt) => &receipt.execution_result, - TransactionReceipt::DeployAccount(receipt) => &receipt.execution_result, + Self::Invoke(receipt) => &receipt.events, + Self::L1Handler(receipt) => &receipt.events, + Self::Declare(receipt) => &receipt.events, + Self::Deploy(receipt) => &receipt.events, + Self::DeployAccount(receipt) => &receipt.events, } } } impl L1HandlerTransaction { + /// Parses [`MsgToL2`] from the transaction's calldata. This should not never fail on a genuine + /// `L1_HANDLER` transaction. pub fn parse_msg_to_l2(&self) -> Result { self.calldata.split_first().map_or( Err(ParseMsgToL2Error::EmptyCalldata), @@ -521,44 +654,44 @@ impl L1HandlerTransaction { } } -impl AsRef for BlockId { - fn as_ref(&self) -> &BlockId { +impl AsRef for BlockId { + fn as_ref(&self) -> &Self { self } } -impl AsRef for FunctionCall { - fn as_ref(&self) -> &FunctionCall { +impl AsRef for FunctionCall { + fn as_ref(&self) -> &Self { self } } -impl AsRef for MsgFromL1 { - fn as_ref(&self) -> &MsgFromL1 { +impl AsRef for MsgFromL1 { + fn as_ref(&self) -> &Self { self } } -impl AsRef for BroadcastedTransaction { - fn as_ref(&self) -> &BroadcastedTransaction { +impl AsRef for BroadcastedTransaction { + fn as_ref(&self) -> &Self { self } } -impl AsRef for BroadcastedInvokeTransaction { - fn as_ref(&self) -> &BroadcastedInvokeTransaction { +impl AsRef for BroadcastedInvokeTransaction { + fn as_ref(&self) -> &Self { self } } -impl AsRef for BroadcastedDeclareTransaction { - fn as_ref(&self) -> &BroadcastedDeclareTransaction { +impl AsRef for BroadcastedDeclareTransaction { + fn as_ref(&self) -> &Self { self } } -impl AsRef for BroadcastedDeployAccountTransaction { - fn as_ref(&self) -> &BroadcastedDeployAccountTransaction { +impl AsRef for BroadcastedDeployAccountTransaction { + fn as_ref(&self) -> &Self { self } } diff --git a/starknet-core/src/types/msg.rs b/starknet-core/src/types/msg.rs index e1f1ce04..beec076a 100644 --- a/starknet-core/src/types/msg.rs +++ b/starknet-core/src/types/msg.rs @@ -5,19 +5,25 @@ use starknet_types_core::felt::Felt; use super::{EthAddress, Hash256, MsgToL1}; +/// An L1-to-L2 message sent from Ethereum to Starknet. #[derive(Debug, Clone)] pub struct MsgToL2 { + /// The Ethereum address sending the message. pub from_address: EthAddress, + /// The Starknet contract address that handles the message. pub to_address: Felt, + /// The entrypoint selector on the handler contract. pub selector: Felt, + /// The calldata to be used for the handler contract invocation. pub payload: Vec, + /// The nonce on the message for duduplication. pub nonce: u64, } impl MsgToL2 { /// Calculates the message hash based on the algorithm documented here: /// - /// https://docs.starknet.io/documentation/architecture_and_concepts/L1-L2_Communication/messaging-mechanism/ + /// pub fn hash(&self) -> Hash256 { let mut hasher = Keccak256::new(); @@ -40,7 +46,7 @@ impl MsgToL2 { hasher.update((self.payload.len() as u64).to_be_bytes()); // Payload - for item in self.payload.iter() { + for item in &self.payload { hasher.update(item.to_bytes_be()); } @@ -54,7 +60,7 @@ impl MsgToL2 { impl MsgToL1 { /// Calculates the message hash based on the algorithm documented here: /// - /// https://docs.starknet.io/documentation/architecture_and_concepts/Network_Architecture/messaging-mechanism/#structure_and_hashing_l2-l1 + /// pub fn hash(&self) -> Hash256 { let mut hasher = Keccak256::new(); @@ -69,7 +75,7 @@ impl MsgToL1 { hasher.update((self.payload.len() as u64).to_be_bytes()); // Payload - for item in self.payload.iter() { + for item in &self.payload { hasher.update(item.to_bytes_be()); } diff --git a/starknet-core/src/types/receipt_block.rs b/starknet-core/src/types/receipt_block.rs index 5c73d03d..d9bc02bc 100644 --- a/starknet-core/src/types/receipt_block.rs +++ b/starknet-core/src/types/receipt_block.rs @@ -4,47 +4,59 @@ use serde_with::serde_as; use crate::serde::unsigned_field_element::UfeHex; use starknet_types_core::felt::Felt; -/// A more idiomatic way to access `execution_status` and `revert_reason`. +/// Block identifier used in +/// [`TransactionReceiptWithBlockInfo`](super::TransactionReceiptWithBlockInfo). +/// +/// Instead of directly exposing the `block_hash` and `block_number` fields as [`Option`], +/// this struct captures the fact that these fields are always [`Some`](Option::Some) or +/// [`None`](Option::None) toggether, allowing idiomatic access without unnecessary unwraps. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ReceiptBlock { + /// The receipt is attached to a pending block. Pending, - Block { block_hash: Felt, block_number: u64 }, + /// The receipt is attached to a confirmed, non-pending block. + Block { + /// Block hash. + block_hash: Felt, + /// Block number (height). + block_number: u64, + }, } impl ReceiptBlock { /// Returns `true` if and only if it's the `Pending` variant. - pub fn is_pending(&self) -> bool { + pub const fn is_pending(&self) -> bool { match self { - ReceiptBlock::Pending => true, - ReceiptBlock::Block { .. } => false, + Self::Pending => true, + Self::Block { .. } => false, } } /// Returns `true` if and only if it's the `Block` variant. - pub fn is_block(&self) -> bool { + pub const fn is_block(&self) -> bool { match self { - ReceiptBlock::Pending => false, - ReceiptBlock::Block { .. } => true, + Self::Pending => false, + Self::Block { .. } => true, } } /// Returns `None` if block is not `Block`. /// /// A more idiomatic way of accessing the block hash is to match the `Block` enum variant. - pub fn block_hash(&self) -> Option { + pub const fn block_hash(&self) -> Option { match self { - ReceiptBlock::Pending => None, - ReceiptBlock::Block { block_hash, .. } => Some(*block_hash), + Self::Pending => None, + Self::Block { block_hash, .. } => Some(*block_hash), } } /// Returns `None` if block is not `Block`. /// /// A more idiomatic way of accessing the block number is to match the `Block` enum variant. - pub fn block_number(&self) -> Option { + pub const fn block_number(&self) -> Option { match self { - ReceiptBlock::Pending => None, - ReceiptBlock::Block { block_number, .. } => Some(*block_number), + Self::Pending => None, + Self::Block { block_number, .. } => Some(*block_number), } } } diff --git a/starknet-core/src/types/serde_impls.rs b/starknet-core/src/types/serde_impls.rs index 95a5221f..fc65b64b 100644 --- a/starknet-core/src/types/serde_impls.rs +++ b/starknet-core/src/types/serde_impls.rs @@ -55,10 +55,10 @@ impl<'de> DeserializeAs<'de, u128> for NumAsHex { } } -impl<'de> Visitor<'de> for NumAsHexVisitorU64 { +impl Visitor<'_> for NumAsHexVisitorU64 { type Value = u64; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { write!(formatter, "string or number") } @@ -95,10 +95,10 @@ impl<'de> Visitor<'de> for NumAsHexVisitorU64 { } } -impl<'de> Visitor<'de> for NumAsHexVisitorU128 { +impl Visitor<'_> for NumAsHexVisitorU128 { type Value = u128; - fn expecting(&self, formatter: &mut Formatter) -> alloc::fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> alloc::fmt::Result { write!(formatter, "string or number") } @@ -128,8 +128,8 @@ impl Serialize for SyncStatusType { S: serde::Serializer, { match self { - SyncStatusType::NotSyncing => serializer.serialize_bool(false), - SyncStatusType::Syncing(sync_status) => SyncStatus::serialize(sync_status, serializer), + Self::NotSyncing => serializer.serialize_bool(false), + Self::Syncing(sync_status) => SyncStatus::serialize(sync_status, serializer), } } } @@ -145,7 +145,7 @@ impl<'de> Deserialize<'de> for SyncStatusType { false => Ok(Self::NotSyncing), }, - SyncStatusTypeDe::SyncStatus(value) => Ok(SyncStatusType::Syncing(value)), + SyncStatusTypeDe::SyncStatus(value) => Ok(Self::Syncing(value)), } } } @@ -233,19 +233,19 @@ mod transaction_status { S: Serializer, { let raw = match self { - TransactionStatus::Received => Raw { + Self::Received => Raw { finality_status: SequencerTransactionStatus::Received, execution_status: None, }, - TransactionStatus::Rejected => Raw { + Self::Rejected => Raw { finality_status: SequencerTransactionStatus::Rejected, execution_status: None, }, - TransactionStatus::AcceptedOnL2(exe) => Raw { + Self::AcceptedOnL2(exe) => Raw { finality_status: SequencerTransactionStatus::AcceptedOnL2, execution_status: Some(*exe), }, - TransactionStatus::AcceptedOnL1(exe) => Raw { + Self::AcceptedOnL1(exe) => Raw { finality_status: SequencerTransactionStatus::AcceptedOnL1, execution_status: Some(*exe), }, @@ -419,9 +419,7 @@ mod tests { (BlockId::Number(1234), "{\"block_number\":1234}"), (BlockId::Tag(BlockTag::Latest), "\"latest\""), (BlockId::Tag(BlockTag::Pending), "\"pending\""), - ] - .into_iter() - { + ] { assert_eq!(serde_json::to_string(&block_id).unwrap(), json); assert_eq!(serde_json::from_str::(json).unwrap(), block_id); } @@ -434,7 +432,7 @@ mod tests { #[derive(Debug, PartialEq, Eq, Deserialize)] struct Value(#[serde_as(as = "NumAsHex")] u64); - for (num, json) in [(Value(100), "\"0x64\""), (Value(100), "100")].into_iter() { + for (num, json) in [(Value(100), "\"0x64\""), (Value(100), "100")] { assert_eq!(serde_json::from_str::(json).unwrap(), num); } } diff --git a/starknet-core/src/types/u256.rs b/starknet-core/src/types/u256.rs index 7f731e3e..fe57e728 100644 --- a/starknet-core/src/types/u256.rs +++ b/starknet-core/src/types/u256.rs @@ -11,107 +11,113 @@ use crate::types::Felt; pub struct U256(crypto_bigint::U256); impl U256 { - #[cfg(target_pointer_width = "64")] - pub fn from_words(low: u128, high: u128) -> Self { - Self(crypto_bigint::U256::from_words([ - low as u64, - (low >> 64) as u64, - high as u64, - (high >> 64) as u64, - ])) - } - - #[cfg(target_pointer_width = "32")] - pub fn from_words(low: u128, high: u128) -> Self { - Self(crypto_bigint::U256::from_words([ - low as u32, - (low >> 32) as u32, - (low >> 64) as u32, - (low >> 96) as u32, - high as u32, - (high >> 32) as u32, - (high >> 64) as u32, - (high >> 96) as u32, - ])) - } - - pub fn low(&self) -> u128 { + /// Constructs a [U256] from the low 128 bits and the high 128 bits, similar to how they're + /// represented in Cairo. + pub const fn from_words(low: u128, high: u128) -> Self { + #[cfg(target_pointer_width = "64")] + { + Self(crypto_bigint::U256::from_words([ + low as u64, + (low >> 64) as u64, + high as u64, + (high >> 64) as u64, + ])) + } + + #[cfg(target_pointer_width = "32")] + { + Self(crypto_bigint::U256::from_words([ + low as u32, + (low >> 32) as u32, + (low >> 64) as u32, + (low >> 96) as u32, + high as u32, + (high >> 32) as u32, + (high >> 64) as u32, + (high >> 96) as u32, + ])) + } + } + + /// Gets the lower (least significant) 128 bits as [u128]. + pub const fn low(&self) -> u128 { let words = u256_to_u64_array(&self.0); words[0] as u128 + ((words[1] as u128) << 64) } - pub fn high(&self) -> u128 { + /// Gets the higher (most significant) 128 bits as [u128]. + pub const fn high(&self) -> u128 { let words = u256_to_u64_array(&self.0); words[2] as u128 + ((words[3] as u128) << 64) } } -impl core::ops::Add for U256 { - type Output = U256; +impl core::ops::Add for U256 { + type Output = Self; - fn add(self, rhs: U256) -> Self::Output { + fn add(self, rhs: Self) -> Self::Output { Self(self.0.checked_add(&rhs.0).unwrap()) } } -impl core::ops::AddAssign for U256 { - fn add_assign(&mut self, rhs: U256) { +impl core::ops::AddAssign for U256 { + fn add_assign(&mut self, rhs: Self) { self.0 = self.0.checked_add(&rhs.0).unwrap() } } -impl core::ops::Sub for U256 { - type Output = U256; +impl core::ops::Sub for U256 { + type Output = Self; - fn sub(self, rhs: U256) -> Self::Output { + fn sub(self, rhs: Self) -> Self::Output { Self(self.0.checked_sub(&rhs.0).unwrap()) } } -impl core::ops::SubAssign for U256 { - fn sub_assign(&mut self, rhs: U256) { +impl core::ops::SubAssign for U256 { + fn sub_assign(&mut self, rhs: Self) { self.0 = self.0.checked_sub(&rhs.0).unwrap() } } -impl core::ops::Mul for U256 { - type Output = U256; +impl core::ops::Mul for U256 { + type Output = Self; - fn mul(self, rhs: U256) -> Self::Output { + fn mul(self, rhs: Self) -> Self::Output { Self(self.0.checked_mul(&rhs.0).unwrap()) } } -impl core::ops::MulAssign for U256 { - fn mul_assign(&mut self, rhs: U256) { +impl core::ops::MulAssign for U256 { + fn mul_assign(&mut self, rhs: Self) { self.0 = self.0.checked_mul(&rhs.0).unwrap() } } -impl core::ops::Div for U256 { - type Output = U256; +impl core::ops::Div for U256 { + type Output = Self; - fn div(self, rhs: U256) -> Self::Output { + fn div(self, rhs: Self) -> Self::Output { Self(self.0.checked_div(&rhs.0).unwrap()) } } -impl core::ops::DivAssign for U256 { - fn div_assign(&mut self, rhs: U256) { +impl core::ops::DivAssign for U256 { + fn div_assign(&mut self, rhs: Self) { self.0 = self.0.checked_div(&rhs.0).unwrap() } } -impl core::ops::Rem for U256 { - type Output = U256; +impl core::ops::Rem for U256 { + type Output = Self; - fn rem(self, rhs: U256) -> Self::Output { + fn rem(self, rhs: Self) -> Self::Output { Self(self.0.checked_rem(&rhs.0).unwrap()) } } -impl core::ops::RemAssign for U256 { - fn rem_assign(&mut self, rhs: U256) { +impl core::ops::RemAssign for U256 { + fn rem_assign(&mut self, rhs: Self) { self.0 = self.0.checked_rem(&rhs.0).unwrap() } } @@ -253,13 +259,13 @@ impl From for U256 { #[cfg(target_pointer_width = "64")] #[inline] -fn u256_to_u64_array(num: &crypto_bigint::U256) -> [u64; 4] { +const fn u256_to_u64_array(num: &crypto_bigint::U256) -> [u64; 4] { num.to_words() } #[cfg(target_pointer_width = "32")] #[inline] -fn u256_to_u64_array(num: &crypto_bigint::U256) -> [u64; 4] { +const fn u256_to_u64_array(num: &crypto_bigint::U256) -> [u64; 4] { unsafe { core::mem::transmute::<[u32; 8], [u64; 4]>(num.to_words()) } } diff --git a/starknet-core/src/utils.rs b/starknet-core/src/utils.rs index c1fa8273..36ec3d43 100644 --- a/starknet-core/src/utils.rs +++ b/starknet-core/src/utils.rs @@ -29,31 +29,46 @@ const CONTRACT_ADDRESS_PREFIX: Felt = Felt::from_raw([ /// The uniqueness settings for UDC deployments. #[derive(Debug, Clone)] pub enum UdcUniqueness { + /// Contract deployment is not unique to the deployer, as in any deployer account can deploy to + /// this same deployed address given the same settings. NotUnique, + /// Contract deployment is unique to the deployer, as in the deployer address is used to form + /// part of the deployment salt, making it impossible to another deployer account to deploy to + /// the same deployed address, even using the same UDC inputs. Unique(UdcUniqueSettings), } +/// The uniqueness settings when using [`UdcUniqueness::Unique`] for contract deployment. #[derive(Debug, Clone)] pub struct UdcUniqueSettings { + /// Contract address of the deployer account, which is the caller of the UDC. pub deployer_address: Felt, + /// The UDC address. pub udc_contract_address: Felt, } mod errors { use core::fmt::{Display, Formatter, Result}; + /// The string provided contains non-ASCII characters. #[derive(Debug)] pub struct NonAsciiNameError; + /// Possible errors for encoding a Cairo short string. #[derive(Debug)] pub enum CairoShortStringToFeltError { + /// The string provided contains non-ASCII characters. NonAsciiCharacter, + /// The string provided is longer than 31 characters. StringTooLong, } + /// Possible errors for decoding a Cairo short string. #[derive(Debug)] pub enum ParseCairoShortStringError { + /// The encoded [`Felt`](super::Felt) value is out of range. ValueOutOfRange, + /// A null terminator (`0x00`) is encountered. UnexpectedNullTerminator, } @@ -96,7 +111,8 @@ mod errors { } pub use errors::{CairoShortStringToFeltError, NonAsciiNameError, ParseCairoShortStringError}; -/// A variant of eth-keccak that computes a value that fits in a Starknet field element. +/// A variant of eth-keccak that computes a value that fits in a Starknet field element. It performs +/// a standard Keccak-256 but with the 6 most significant bits removed. pub fn starknet_keccak(data: &[u8]) -> Felt { let mut hasher = Keccak256::new(); hasher.update(data); @@ -109,6 +125,11 @@ pub fn starknet_keccak(data: &[u8]) -> Felt { Felt::from_bytes_be(unsafe { &*(hash[..].as_ptr() as *const [u8; 32]) }) } +/// Calculates the entrypoint selector from a human-readable function name. +/// +/// Returns the [Starknet Keccak](fn.starknet_keccak) of the function name in most cases, except for +/// 2 special built-in default entrypoints of `__default__` and `__l1_default__` for which `0` is +/// returned instead. pub fn get_selector_from_name(func_name: &str) -> Result { if func_name == DEFAULT_ENTRY_POINT_NAME || func_name == DEFAULT_L1_ENTRY_POINT_NAME { Ok(Felt::ZERO) @@ -122,11 +143,16 @@ pub fn get_selector_from_name(func_name: &str) -> Result Result { let var_name_bytes = var_name.as_bytes(); if var_name_bytes.is_ascii() { let mut res = starknet_keccak(var_name_bytes); - for arg in args.iter() { + for arg in args { res = pedersen_hash(&res, arg); } Ok(normalize_address(res)) @@ -135,7 +161,7 @@ pub fn get_storage_var_address(var_name: &str, args: &[Felt]) -> Result Result { if !str.is_ascii() { return Err(CairoShortStringToFeltError::NonAsciiCharacter); @@ -153,7 +179,7 @@ pub fn cairo_short_string_to_felt(str: &str) -> Result Result { if felt == &Felt::ZERO { return Ok(String::new()); @@ -165,7 +191,7 @@ pub fn parse_cairo_short_string(felt: &Felt) -> Result Result Felt { address.mod_floor(&ADDR_BOUND) } @@ -324,7 +356,7 @@ mod tests { ), ]; - for (str, felt_dec) in data.into_iter() { + for (str, felt_dec) in data { assert_eq!( cairo_short_string_to_felt(str).unwrap(), Felt::from_dec_str(felt_dec).unwrap() @@ -364,7 +396,7 @@ mod tests { ), ]; - for (str, felt_dec) in data.into_iter() { + for (str, felt_dec) in data { assert_eq!( parse_cairo_short_string(&Felt::from_dec_str(felt_dec).unwrap()).unwrap(), str diff --git a/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial.hashes.json b/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial.hashes.json new file mode 100644 index 00000000..8fe9ca8c --- /dev/null +++ b/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial.hashes.json @@ -0,0 +1,4 @@ +{ + "sierra_class_hash": "0x7585639b4e793743860f2761d81e070157ae8d0fc8e518a8cd9069eb2a40010", + "compiled_class_hash": "0x317d3ac2cf840e487b6d0014a75f0cf507dff0bc143c710388e323487089bfa" +} diff --git a/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt b/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt new file mode 100644 index 00000000..09fb9b21 --- /dev/null +++ b/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial_compiled.txt @@ -0,0 +1,12 @@ +{ + "prime": "0x800000000000011000000000000000000000000000000000000000000000001", + "compiler_version": "2.6.2", + "bytecode": [], + "bytecode_segment_lengths": 0, + "hints": [], + "entry_points_by_type": { + "EXTERNAL": [], + "L1_HANDLER": [], + "CONSTRUCTOR": [] + } +} \ No newline at end of file diff --git a/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial_sierra.txt b/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial_sierra.txt new file mode 100644 index 00000000..068b9022 --- /dev/null +++ b/starknet-core/test-data/contracts/cairo2.6/artifacts/trivial_sierra.txt @@ -0,0 +1,34 @@ +{ + "sierra_program": [ + "0x1", + "0x5", + "0x0", + "0x2", + "0x6", + "0x2", + "0x1", + "0xff", + "0x0", + "0x4", + "0x0" + ], + "sierra_program_debug_info": { + "type_names": [], + "libfunc_names": [], + "user_func_names": [] + }, + "contract_class_version": "0.1.0", + "entry_points_by_type": { + "EXTERNAL": [], + "L1_HANDLER": [], + "CONSTRUCTOR": [] + }, + "abi": [ + { + "type": "event", + "name": "trivial::trivial::Trivial::Event", + "kind": "enum", + "variants": [] + } + ] +} \ No newline at end of file diff --git a/starknet-core/test-data/contracts/cairo2.6/contracts/trivial.cairo b/starknet-core/test-data/contracts/cairo2.6/contracts/trivial.cairo new file mode 100644 index 00000000..1e978c3d --- /dev/null +++ b/starknet-core/test-data/contracts/cairo2.6/contracts/trivial.cairo @@ -0,0 +1,10 @@ +#[starknet::contract] +mod Trivial { + #[storage] + struct Storage {} + + #[abi(embed_v0)] + fn something(ref self: ContractState) -> felt252 { + 1 + } +} diff --git a/starknet-core/test-data/contracts/cairo2.6/docker_entry_compile.sh b/starknet-core/test-data/contracts/cairo2.6/docker_entry_compile.sh index da07484a..51ba4d14 100755 --- a/starknet-core/test-data/contracts/cairo2.6/docker_entry_compile.sh +++ b/starknet-core/test-data/contracts/cairo2.6/docker_entry_compile.sh @@ -11,3 +11,4 @@ compile () { } compile "/contracts/erc20.cairo" "/artifacts/erc20" +compile "/contracts/trivial.cairo" "/artifacts/trivial" diff --git a/starknet-core/test-data/contracts/cairo2.6/docker_entry_hashes.sh b/starknet-core/test-data/contracts/cairo2.6/docker_entry_hashes.sh index c42c4cb5..ba0c8d2c 100755 --- a/starknet-core/test-data/contracts/cairo2.6/docker_entry_hashes.sh +++ b/starknet-core/test-data/contracts/cairo2.6/docker_entry_hashes.sh @@ -9,3 +9,4 @@ hash () { } hash "/artifacts/erc20" +hash "/artifacts/trivial" diff --git a/starknet-crypto-codegen/Cargo.toml b/starknet-crypto-codegen/Cargo.toml deleted file mode 100644 index e8959610..00000000 --- a/starknet-crypto-codegen/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "starknet-crypto-codegen" -version = "0.4.0" -authors = ["Jonathan LEI "] -license = "MIT OR Apache-2.0" -edition = "2021" -readme = "README.md" -repository = "https://github.com/xJonathanLEI/starknet-rs" -homepage = "https://starknet.rs/" -description = """ -Codegen macros for `starknet-crypto` -""" -keywords = ["ethereum", "starknet", "web3", "no_std"] - -[lib] -proc-macro = true - -[dependencies] -starknet-curve = { version = "0.5.0", path = "../starknet-curve" } -syn = "2.0.55" -starknet-types-core = { version = "0.1.3", default-features = false, features = ["curve"] } diff --git a/starknet-crypto-codegen/README.md b/starknet-crypto-codegen/README.md deleted file mode 100644 index 05fab7eb..00000000 --- a/starknet-crypto-codegen/README.md +++ /dev/null @@ -1 +0,0 @@ -# Codegen macros for `starknet-crypto` diff --git a/starknet-crypto-codegen/src/lib.rs b/starknet-crypto-codegen/src/lib.rs deleted file mode 100644 index 00f4f4ac..00000000 --- a/starknet-crypto-codegen/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -use proc_macro::TokenStream; - -mod pedersen; -mod poseidon; - -/// Generates the lookup table for Pedersen hash. -#[proc_macro] -pub fn lookup_table(input: TokenStream) -> TokenStream { - pedersen::lookup_table(input) -} - -/// Generates the constants from Poseidon params. -#[proc_macro] -pub fn poseidon_consts(_input: TokenStream) -> TokenStream { - poseidon::poseidon_consts() -} diff --git a/starknet-crypto-codegen/src/pedersen.rs b/starknet-crypto-codegen/src/pedersen.rs deleted file mode 100644 index ab0cb007..00000000 --- a/starknet-crypto-codegen/src/pedersen.rs +++ /dev/null @@ -1,89 +0,0 @@ -// Code ported from the build.rs script here: -// https://github.com/eqlabs/pathfinder/blob/7f9a6bb0264943f93a633f61fc4e0bc9237f68a0/crates/stark_hash/build.rs - -use std::fmt::Write; - -use proc_macro::TokenStream; -use starknet_curve::curve_params::{PEDERSEN_P0, PEDERSEN_P1, PEDERSEN_P2, PEDERSEN_P3}; -use starknet_types_core::curve::{AffinePoint, ProjectivePoint}; -use syn::{parse_macro_input, LitInt}; - -pub fn lookup_table(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as LitInt); - let bits: u32 = input.base10_parse().expect("invalid bits"); - - let mut output = String::new(); - writeln!(output, "pub const CURVE_CONSTS_BITS: usize = {bits};").unwrap(); - - push_points(&mut output, "P0", PEDERSEN_P0, 248, bits).expect("push_points failed"); - push_points(&mut output, "P1", PEDERSEN_P1, 4, bits).expect("push_points failed"); - push_points(&mut output, "P2", PEDERSEN_P2, 248, bits).expect("push_points failed"); - push_points(&mut output, "P3", PEDERSEN_P3, 4, bits).expect("push_points failed"); - - output.parse().unwrap() -} - -fn push_points( - buf: &mut String, - name: &str, - base: AffinePoint, - max_bits: u32, - bits: u32, -) -> std::fmt::Result { - let full_chunks = max_bits / bits; - let leftover_bits = max_bits % bits; - let table_size_full = (1 << bits) - 1; - let table_size_leftover = (1 << leftover_bits) - 1; - let len = full_chunks * table_size_full + table_size_leftover; - - writeln!( - buf, - "pub const CURVE_CONSTS_{name}: [starknet_types_core::curve::AffinePoint; {len}] = [" - )?; - - let mut bits_left = max_bits; - let mut outer_point = ProjectivePoint::from_affine(base.x(), base.y()).unwrap(); - while bits_left > 0 { - let eat_bits = std::cmp::min(bits_left, bits); - let table_size = (1 << eat_bits) - 1; - - // Loop through each possible bit combination except zero - let mut inner_point = outer_point.clone(); - for _ in 1..(table_size + 1) { - push_point(buf, &inner_point.to_affine().unwrap())?; - inner_point += &outer_point; - } - - // Shift outer point #bits times - bits_left -= eat_bits; - for _i in 0..bits { - outer_point += outer_point.clone(); - } - } - - writeln!(buf, "];")?; - Ok(()) -} - -fn push_point(buf: &mut String, p: &AffinePoint) -> std::fmt::Result { - let x = p.x().to_raw(); - let y = p.y().to_raw(); - writeln!( - buf, - "starknet_types_core::curve::AffinePoint::new_unchecked(" - )?; - writeln!(buf, "starknet_types_core::felt::Felt::from_raw([")?; - writeln!(buf, "{},", x[0])?; - writeln!(buf, "{},", x[1])?; - writeln!(buf, "{},", x[2])?; - writeln!(buf, "{},", x[3])?; - writeln!(buf, "]),")?; - writeln!(buf, "starknet_types_core::felt::Felt::from_raw([")?; - writeln!(buf, "{},", y[0])?; - writeln!(buf, "{},", y[1])?; - writeln!(buf, "{},", y[2])?; - writeln!(buf, "{},", y[3])?; - writeln!(buf, "]),")?; - writeln!(buf, "),")?; - Ok(()) -} diff --git a/starknet-crypto-codegen/src/poseidon/mod.rs b/starknet-crypto-codegen/src/poseidon/mod.rs deleted file mode 100644 index 2a11c2c5..00000000 --- a/starknet-crypto-codegen/src/poseidon/mod.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Code ported from the build.rs script here: -// https://github.com/eqlabs/pathfinder/blob/00a1a74a90a7b8a7f1d07ac3e616be1cb39cf8f1/crates/stark_poseidon/build.rs - -use std::fmt::Write; - -use proc_macro::TokenStream; -use starknet_types_core::felt::Felt; - -mod params; - -const FULL_ROUNDS: usize = 8; -const PARTIAL_ROUNDS: usize = 83; - -pub fn poseidon_consts() -> TokenStream { - let round_keys = params::RAW_ROUND_KEYS - .iter() - .map(|key| key.map(|num| Felt::from_dec_str(num).expect("Invalid round key"))) - .collect::>(); - - let flat = round_keys.iter().flatten().cloned().collect::>(); - let comp = compress_roundkeys(&round_keys); - - let mut buffer = String::new(); - - writeln!(buffer, "const FULL_ROUNDS: usize = {FULL_ROUNDS};").unwrap(); - writeln!(buffer, "const PARTIAL_ROUNDS: usize = {PARTIAL_ROUNDS};").unwrap(); - - writeln!(buffer).unwrap(); - - writeln!(buffer, "{}", generate_code("POSEIDON_CONSTS", &flat)).unwrap(); - writeln!(buffer).unwrap(); - - writeln!(buffer, "{}", generate_code("POSEIDON_COMP_CONSTS", &comp)).unwrap(); - - buffer.parse().expect("Invalid code generated") -} - -pub fn compress_roundkeys(rcs: &[[Felt; 3]]) -> Vec { - let mut result = Vec::new(); - - // Add first full rounds - result.extend(rcs[..FULL_ROUNDS / 2].iter().flatten()); - - // Add compressed partial rounds and first of the last full rounds - result.extend(compress_roundkeys_partial(rcs)); - - // Add last full rounds except the first of them - result.extend( - rcs[(FULL_ROUNDS / 2 + PARTIAL_ROUNDS + 1)..] - .iter() - .flatten(), - ); - - result -} - -pub fn compress_roundkeys_partial(rcs: &[[Felt; 3]]) -> Vec { - let mut result = Vec::new(); - - let mut idx = FULL_ROUNDS / 2; - let mut state: [Felt; 3] = [Felt::ZERO; 3]; - - // Add keys for partial rounds - for _ in 0..PARTIAL_ROUNDS { - // AddRoundKey - state[0] += rcs[idx][0]; - state[1] += rcs[idx][1]; - state[2] += rcs[idx][2]; - - // Add last state - result.push(state[2]); - - // Reset last state - state[2] = Felt::ZERO; - - // MixLayer - let t = state[0] + state[1] + state[2]; - state[0] = t + state[0].double(); - state[1] = t - state[1].double(); - state[2] = t - Felt::THREE * state[2]; - - idx += 1; - } - - // Add keys for first of the last full rounds - state[0] += rcs[idx][0]; - state[1] += rcs[idx][1]; - state[2] += rcs[idx][2]; - result.push(state[0]); - result.push(state[1]); - result.push(state[2]); - - result -} - -pub fn generate_code(name: &str, rcs: &[Felt]) -> String { - let mut buf = String::with_capacity(1024 * 1024); - - writeln!(buf, "pub const {}: [Felt; {}] = [", name, rcs.len()).unwrap(); - - rcs.iter().for_each(|num| { - writeln!( - buf, - "Felt::from_raw([{}]),", - num.to_raw().map(|ele| format!("{ele}")).join(",") - ) - .unwrap(); - }); - - writeln!(buf, "];").unwrap(); - buf -} diff --git a/starknet-crypto-codegen/src/poseidon/params.rs b/starknet-crypto-codegen/src/poseidon/params.rs deleted file mode 100644 index 13d9ec08..00000000 --- a/starknet-crypto-codegen/src/poseidon/params.rs +++ /dev/null @@ -1,460 +0,0 @@ -// Extracted from: -// https://github.com/starkware-industries/poseidon/blob/5403dff9ff4eadb07deb5c0a43e88bedb011deb8/poseidon3.txt - -pub const RAW_ROUND_KEYS: [[&str; 3]; 91] = [ - [ - "2950795762459345168613727575620414179244544320470208355568817838579231751791", - "1587446564224215276866294500450702039420286416111469274423465069420553242820", - "1645965921169490687904413452218868659025437693527479459426157555728339600137", - ], - [ - "2782373324549879794752287702905278018819686065818504085638398966973694145741", - "3409172630025222641379726933524480516420204828329395644967085131392375707302", - "2379053116496905638239090788901387719228422033660130943198035907032739387135", - ], - [ - "2570819397480941104144008784293466051718826502582588529995520356691856497111", - "3546220846133880637977653625763703334841539452343273304410918449202580719746", - "2720682389492889709700489490056111332164748138023159726590726667539759963454", - ], - [ - "1899653471897224903834726250400246354200311275092866725547887381599836519005", - "2369443697923857319844855392163763375394720104106200469525915896159690979559", - "2354174693689535854311272135513626412848402744119855553970180659094265527996", - ], - [ - "2404084503073127963385083467393598147276436640877011103379112521338973185443", - "950320777137731763811524327595514151340412860090489448295239456547370725376", - "2121140748740143694053732746913428481442990369183417228688865837805149503386", - ], - [ - "2372065044800422557577242066480215868569521938346032514014152523102053709709", - "2618497439310693947058545060953893433487994458443568169824149550389484489896", - "3518297267402065742048564133910509847197496119850246255805075095266319996916", - ], - [ - "340529752683340505065238931581518232901634742162506851191464448040657139775", - "1954876811294863748406056845662382214841467408616109501720437541211031966538", - "813813157354633930267029888722341725864333883175521358739311868164460385261", - ], - [ - "71901595776070443337150458310956362034911936706490730914901986556638720031", - "2789761472166115462625363403490399263810962093264318361008954888847594113421", - "2628791615374802560074754031104384456692791616314774034906110098358135152410", - ], - [ - "3617032588734559635167557152518265808024917503198278888820567553943986939719", - "2624012360209966117322788103333497793082705816015202046036057821340914061980", - "149101987103211771991327927827692640556911620408176100290586418839323044234", - ], - [ - "1039927963829140138166373450440320262590862908847727961488297105916489431045", - "2213946951050724449162431068646025833746639391992751674082854766704900195669", - "2792724903541814965769131737117981991997031078369482697195201969174353468597", - ], - [ - "3212031629728871219804596347439383805499808476303618848198208101593976279441", - "3343514080098703935339621028041191631325798327656683100151836206557453199613", - "614054702436541219556958850933730254992710988573177298270089989048553060199", - ], - [ - "148148081026449726283933484730968827750202042869875329032965774667206931170", - "1158283532103191908366672518396366136968613180867652172211392033571980848414", - "1032400527342371389481069504520755916075559110755235773196747439146396688513", - ], - [ - "806900704622005851310078578853499250941978435851598088619290797134710613736", - "462498083559902778091095573017508352472262817904991134671058825705968404510", - "1003580119810278869589347418043095667699674425582646347949349245557449452503", - ], - [ - "619074932220101074089137133998298830285661916867732916607601635248249357793", - "2635090520059500019661864086615522409798872905401305311748231832709078452746", - "978252636251682252755279071140187792306115352460774007308726210405257135181", - ], - [ - "1766912167973123409669091967764158892111310474906691336473559256218048677083", - "1663265127259512472182980890707014969235283233442916350121860684522654120381", - "3532407621206959585000336211742670185380751515636605428496206887841428074250", - ], - [ - "2507023127157093845256722098502856938353143387711652912931112668310034975446", - "3321152907858462102434883844787153373036767230808678981306827073335525034593", - "3039253036806065280643845548147711477270022154459620569428286684179698125661", - ], - [ - "103480338868480851881924519768416587261556021758163719199282794248762465380", - "2394049781357087698434751577708655768465803975478348134669006211289636928495", - "2660531560345476340796109810821127229446538730404600368347902087220064379579", - ], - [ - "3603166934034556203649050570865466556260359798872408576857928196141785055563", - "1553799760191949768532188139643704561532896296986025007089826672890485412324", - "2744284717053657689091306578463476341218866418732695211367062598446038965164", - ], - [ - "320745764922149897598257794663594419839885234101078803811049904310835548856", - "979382242100682161589753881721708883681034024104145498709287731138044566302", - "1860426855810549882740147175136418997351054138609396651615467358416651354991", - ], - [ - "336173081054369235994909356892506146234495707857220254489443629387613956145", - "1632470326779699229772327605759783482411227247311431865655466227711078175883", - "921958250077481394074960433988881176409497663777043304881055317463712938502", - ], - [ - "3034358982193370602048539901033542101022185309652879937418114324899281842797", - "25626282149517463867572353922222474817434101087272320606729439087234878607", - "3002662261401575565838149305485737102400501329139562227180277188790091853682", - ], - [ - "2939684373453383817196521641512509179310654199629514917426341354023324109367", - "1076484609897998179434851570277297233169621096172424141759873688902355505136", - "2575095284833160494841112025725243274091830284746697961080467506739203605049", - ], - [ - "3565075264617591783581665711620369529657840830498005563542124551465195621851", - "2197016502533303822395077038351174326125210255869204501838837289716363437993", - "331415322883530754594261416546036195982886300052707474899691116664327869405", - ], - [ - "1935011233711290003793244296594669823169522055520303479680359990463281661839", - "3495901467168087413996941216661589517270845976538454329511167073314577412322", - "954195417117133246453562983448451025087661597543338750600301835944144520375", - ], - [ - "1271840477709992894995746871435810599280944810893784031132923384456797925777", - "2565310762274337662754531859505158700827688964841878141121196528015826671847", - "3365022288251637014588279139038152521653896670895105540140002607272936852513", - ], - [ - "1660592021628965529963974299647026602622092163312666588591285654477111176051", - "970104372286014048279296575474974982288801187216974504035759997141059513421", - "2617024574317953753849168721871770134225690844968986289121504184985993971227", - ], - [ - "999899815343607746071464113462778273556695659506865124478430189024755832262", - "2228536129413411161615629030408828764980855956560026807518714080003644769896", - "2701953891198001564547196795777701119629537795442025393867364730330476403227", - ], - [ - "837078355588159388741598313782044128527494922918203556465116291436461597853", - "2121749601840466143704862369657561429793951309962582099604848281796392359214", - "771812260179247428733132708063116523892339056677915387749121983038690154755", - ], - [ - "3317336423132806446086732225036532603224267214833263122557471741829060578219", - "481570067997721834712647566896657604857788523050900222145547508314620762046", - "242195042559343964206291740270858862066153636168162642380846129622127460192", - ], - [ - "2855462178889999218204481481614105202770810647859867354506557827319138379686", - "3525521107148375040131784770413887305850308357895464453970651672160034885202", - "1320839531502392535964065058804908871811967681250362364246430459003920305799", - ], - [ - "2514191518588387125173345107242226637171897291221681115249521904869763202419", - "2798335750958827619666318316247381695117827718387653874070218127140615157902", - "2808467767967035643407948058486565877867906577474361783201337540214875566395", - ], - [ - "3551834385992706206273955480294669176699286104229279436819137165202231595747", - "1219439673853113792340300173186247996249367102884530407862469123523013083971", - "761519904537984520554247997444508040636526566551719396202550009393012691157", - ], - [ - "3355402549169351700500518865338783382387571349497391475317206324155237401353", - "199541098009731541347317515995192175813554789571447733944970283654592727138", - "192100490643078165121235261796864975568292640203635147901612231594408079071", - ], - [ - "1187019357602953326192019968809486933768550466167033084944727938441427050581", - "189525349641911362389041124808934468936759383310282010671081989585219065700", - "2831653363992091308880573627558515686245403755586311978724025292003353336665", - ], - [ - "2052859812632218952608271535089179639890275494426396974475479657192657094698", - "1670756178709659908159049531058853320846231785448204274277900022176591811072", - "3538757242013734574731807289786598937548399719866320954894004830207085723125", - ], - [ - "710549042741321081781917034337800036872214466705318638023070812391485261299", - "2345013122330545298606028187653996682275206910242635100920038943391319595180", - "3528369671971445493932880023233332035122954362711876290904323783426765912206", - ], - [ - "1167120829038120978297497195837406760848728897181138760506162680655977700764", - "3073243357129146594530765548901087443775563058893907738967898816092270628884", - "378514724418106317738164464176041649567501099164061863402473942795977719726", - ], - [ - "333391138410406330127594722511180398159664250722328578952158227406762627796", - "1727570175639917398410201375510924114487348765559913502662122372848626931905", - "968312190621809249603425066974405725769739606059422769908547372904403793174", - ], - [ - "360659316299446405855194688051178331671817370423873014757323462844775818348", - "1386580151907705298970465943238806620109618995410132218037375811184684929291", - "3604888328937389309031638299660239238400230206645344173700074923133890528967", - ], - [ - "2496185632263372962152518155651824899299616724241852816983268163379540137546", - "486538168871046887467737983064272608432052269868418721234810979756540672990", - "1558415498960552213241704009433360128041672577274390114589014204605400783336", - ], - [ - "3512058327686147326577190314835092911156317204978509183234511559551181053926", - "2235429387083113882635494090887463486491842634403047716936833563914243946191", - "1290896777143878193192832813769470418518651727840187056683408155503813799882", - ], - [ - "1143310336918357319571079551779316654556781203013096026972411429993634080835", - "3235435208525081966062419599803346573407862428113723170955762956243193422118", - "1293239921425673430660897025143433077974838969258268884994339615096356996604", - ], - [ - "236252269127612784685426260840574970698541177557674806964960352572864382971", - "1733907592497266237374827232200506798207318263912423249709509725341212026275", - "302004309771755665128395814807589350526779835595021835389022325987048089868", - ], - [ - "3018926838139221755384801385583867283206879023218491758435446265703006270945", - "39701437664873825906031098349904330565195980985885489447836580931425171297", - "908381723021746969965674308809436059628307487140174335882627549095646509778", - ], - [ - "219062858908229855064136253265968615354041842047384625689776811853821594358", - "1283129863776453589317845316917890202859466483456216900835390291449830275503", - "418512623547417594896140369190919231877873410935689672661226540908900544012", - ], - [ - "1792181590047131972851015200157890246436013346535432437041535789841136268632", - "370546432987510607338044736824316856592558876687225326692366316978098770516", - "3323437805230586112013581113386626899534419826098235300155664022709435756946", - ], - [ - "910076621742039763058481476739499965761942516177975130656340375573185415877", - "1762188042455633427137702520675816545396284185254002959309669405982213803405", - "2186362253913140345102191078329764107619534641234549431429008219905315900520", - ], - [ - "2230647725927681765419218738218528849146504088716182944327179019215826045083", - "1069243907556644434301190076451112491469636357133398376850435321160857761825", - "2695241469149243992683268025359863087303400907336026926662328156934068747593", - ], - [ - "1361519681544413849831669554199151294308350560528931040264950307931824877035", - "1339116632207878730171031743761550901312154740800549632983325427035029084904", - "790593524918851401449292693473498591068920069246127392274811084156907468875", - ], - [ - "2723400368331924254840192318398326090089058735091724263333980290765736363637", - "3457180265095920471443772463283225391927927225993685928066766687141729456030", - "1483675376954327086153452545475557749815683871577400883707749788555424847954", - ], - [ - "2926303836265506736227240325795090239680154099205721426928300056982414025239", - "543969119775473768170832347411484329362572550684421616624136244239799475526", - "237401230683847084256617415614300816373730178313253487575312839074042461932", - ], - [ - "844568412840391587862072008674263874021460074878949862892685736454654414423", - "151922054871708336050647150237534498235916969120198637893731715254687336644", - "1299332034710622815055321547569101119597030148120309411086203580212105652312", - ], - [ - "487046922649899823989594814663418784068895385009696501386459462815688122993", - "1104883249092599185744249485896585912845784382683240114120846423960548576851", - "1458388705536282069567179348797334876446380557083422364875248475157495514484", - ], - [ - "850248109622750774031817200193861444623975329881731864752464222442574976566", - "2885843173858536690032695698009109793537724845140477446409245651176355435722", - "3027068551635372249579348422266406787688980506275086097330568993357835463816", - ], - [ - "3231892723647447539926175383213338123506134054432701323145045438168976970994", - "1719080830641935421242626784132692936776388194122314954558418655725251172826", - "1172253756541066126131022537343350498482225068791630219494878195815226839450", - ], - [ - "1619232269633026603732619978083169293258272967781186544174521481891163985093", - "3495680684841853175973173610562400042003100419811771341346135531754869014567", - "1576161515913099892951745452471618612307857113799539794680346855318958552758", - ], - [ - "2618326122974253423403350731396350223238201817594761152626832144510903048529", - "2696245132758436974032479782852265185094623165224532063951287925001108567649", - "930116505665110070247395429730201844026054810856263733273443066419816003444", - ], - [ - "2786389174502246248523918824488629229455088716707062764363111940462137404076", - "1555260846425735320214671887347115247546042526197895180675436886484523605116", - "2306241912153325247392671742757902161446877415586158295423293240351799505917", - ], - [ - "411529621724849932999694270803131456243889635467661223241617477462914950626", - "1542495485262286701469125140275904136434075186064076910329015697714211835205", - "1853045663799041100600825096887578544265580718909350942241802897995488264551", - ], - [ - "2963055259497271220202739837493041799968576111953080503132045092194513937286", - "2303806870349915764285872605046527036748108533406243381676768310692344456050", - "2622104986201990620910286730213140904984256464479840856728424375142929278875", - ], - [ - "2369987021925266811581727383184031736927816625797282287927222602539037105864", - "285070227712021899602056480426671736057274017903028992288878116056674401781", - "3034087076179360957800568733595959058628497428787907887933697691951454610691", - ], - [ - "469095854351700119980323115747590868855368701825706298740201488006320881056", - "360001976264385426746283365024817520563236378289230404095383746911725100012", - "3438709327109021347267562000879503009590697221730578667498351600602230296178", - ], - [ - "63573904800572228121671659287593650438456772568903228287754075619928214969", - "3470881855042989871434874691030920672110111605547839662680968354703074556970", - "724559311507950497340993415408274803001166693839947519425501269424891465492", - ], - [ - "880409284677518997550768549487344416321062350742831373397603704465823658986", - "6876255662475867703077362872097208259197756317287339941435193538565586230", - "2701916445133770775447884812906226786217969545216086200932273680400909154638", - ], - [ - "425152119158711585559310064242720816611629181537672850898056934507216982586", - "1475552998258917706756737045704649573088377604240716286977690565239187213744", - "2413772448122400684309006716414417978370152271397082147158000439863002593561", - ], - [ - "392160855822256520519339260245328807036619920858503984710539815951012864164", - "1075036996503791536261050742318169965707018400307026402939804424927087093987", - "2176439430328703902070742432016450246365760303014562857296722712989275658921", - ], - [ - "1413865976587623331051814207977382826721471106513581745229680113383908569693", - "4879283427490523253696177116563427032332223531862961281430108575019551814", - "3392583297537374046875199552977614390492290683707960975137418536812266544902", - ], - [ - "3600854486849487646325182927019642276644093512133907046667282144129939150983", - "2779924664161372134024229593301361846129279572186444474616319283535189797834", - "2722699960903170449291146429799738181514821447014433304730310678334403972040", - ], - [ - "819109815049226540285781191874507704729062681836086010078910930707209464699", - "3046121243742768013822760785918001632929744274211027071381357122228091333823", - "1339019590803056172509793134119156250729668216522001157582155155947567682278", - ], - [ - "1933279639657506214789316403763326578443023901555983256955812717638093967201", - "2138221547112520744699126051903811860205771600821672121643894708182292213541", - "2694713515543641924097704224170357995809887124438248292930846280951601597065", - ], - [ - "2471734202930133750093618989223585244499567111661178960753938272334153710615", - "504903761112092757611047718215309856203214372330635774577409639907729993533", - "1943979703748281357156510253941035712048221353507135074336243405478613241290", - ], - [ - "684525210957572142559049112233609445802004614280157992196913315652663518936", - "1705585400798782397786453706717059483604368413512485532079242223503960814508", - "192429517716023021556170942988476050278432319516032402725586427701913624665", - ], - [ - "1586493702243128040549584165333371192888583026298039652930372758731750166765", - "686072673323546915014972146032384917012218151266600268450347114036285993377", - "3464340397998075738891129996710075228740496767934137465519455338004332839215", - ], - [ - "2805249176617071054530589390406083958753103601524808155663551392362371834663", - "667746464250968521164727418691487653339733392025160477655836902744186489526", - "1131527712905109997177270289411406385352032457456054589588342450404257139778", - ], - [ - "1908969485750011212309284349900149072003218505891252313183123635318886241171", - "1025257076985551890132050019084873267454083056307650830147063480409707787695", - "2153175291918371429502545470578981828372846236838301412119329786849737957977", - ], - [ - "3410257749736714576487217882785226905621212230027780855361670645857085424384", - "3442969106887588154491488961893254739289120695377621434680934888062399029952", - "3029953900235731770255937704976720759948880815387104275525268727341390470237", - ], - [ - "85453456084781138713939104192561924536933417707871501802199311333127894466", - "2730629666577257820220329078741301754580009106438115341296453318350676425129", - "178242450661072967256438102630920745430303027840919213764087927763335940415", - ], - [ - "2844589222514708695700541363167856718216388819406388706818431442998498677557", - "3547876269219141094308889387292091231377253967587961309624916269569559952944", - "2525005406762984211707203144785482908331876505006839217175334833739957826850", - ], - [ - "3096397013555211396701910432830904669391580557191845136003938801598654871345", - "574424067119200181933992948252007230348512600107123873197603373898923821490", - "1714030696055067278349157346067719307863507310709155690164546226450579547098", - ], - [ - "2339895272202694698739231405357972261413383527237194045718815176814132612501", - "3562501318971895161271663840954705079797767042115717360959659475564651685069", - "69069358687197963617161747606993436483967992689488259107924379545671193749", - ], - [ - "2614502738369008850475068874731531583863538486212691941619835266611116051561", - "655247349763023251625727726218660142895322325659927266813592114640858573566", - "2305235672527595714255517865498269719545193172975330668070873705108690670678", - ], - [ - "926416070297755413261159098243058134401665060349723804040714357642180531931", - "866523735635840246543516964237513287099659681479228450791071595433217821460", - "2284334068466681424919271582037156124891004191915573957556691163266198707693", - ], - [ - "1812588309302477291425732810913354633465435706480768615104211305579383928792", - "2836899808619013605432050476764608707770404125005720004551836441247917488507", - "2989087789022865112405242078196235025698647423649950459911546051695688370523", - ], - [ - "68056284404189102136488263779598243992465747932368669388126367131855404486", - "505425339250887519581119854377342241317528319745596963584548343662758204398", - "2118963546856545068961709089296976921067035227488975882615462246481055679215", - ], - [ - "2253872596319969096156004495313034590996995209785432485705134570745135149681", - "1625090409149943603241183848936692198923183279116014478406452426158572703264", - "179139838844452470348634657368199622305888473747024389514258107503778442495", - ], - [ - "1567067018147735642071130442904093290030432522257811793540290101391210410341", - "2737301854006865242314806979738760349397411136469975337509958305470398783585", - "3002738216460904473515791428798860225499078134627026021350799206894618186256", - ], - [ - "374029488099466837453096950537275565120689146401077127482884887409712315162", - "973403256517481077805460710540468856199855789930951602150773500862180885363", - "2691967457038172130555117632010860984519926022632800605713473799739632878867", - ], - [ - "3515906794910381201365530594248181418811879320679684239326734893975752012109", - "148057579455448384062325089530558091463206199724854022070244924642222283388", - "1541588700238272710315890873051237741033408846596322948443180470429851502842", - ], - [ - "147013865879011936545137344076637170977925826031496203944786839068852795297", - "2630278389304735265620281704608245039972003761509102213752997636382302839857", - "1359048670759642844930007747955701205155822111403150159614453244477853867621", - ], - [ - "2438984569205812336319229336885480537793786558293523767186829418969842616677", - "2137792255841525507649318539501906353254503076308308692873313199435029594138", - "2262318076430740712267739371170174514379142884859595360065535117601097652755", - ], - [ - "2792703718581084537295613508201818489836796608902614779596544185252826291584", - "2294173715793292812015960640392421991604150133581218254866878921346561546149", - "2770011224727997178743274791849308200493823127651418989170761007078565678171", - ], -]; diff --git a/starknet-crypto/Cargo.toml b/starknet-crypto/Cargo.toml index c81cd10b..44592e7c 100644 --- a/starknet-crypto/Cargo.toml +++ b/starknet-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-crypto" -version = "0.7.0" +version = "0.7.3" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -14,8 +14,7 @@ keywords = ["ethereum", "starknet", "web3", "no_std"] exclude = ["test-data/**"] [dependencies] -starknet-crypto-codegen = { version = "0.4.0", path = "../starknet-crypto-codegen" } -starknet-curve = { version = "0.5.0", path = "../starknet-curve" } +starknet-curve = { version = "0.5.1", path = "../starknet-curve" } crypto-bigint = { version = "0.5.1", default-features = false, features = ["generic-array", "zeroize"] } hmac = { version = "0.12.1", default-features = false } num-bigint = { version = "0.4.3", default-features = false } @@ -25,13 +24,14 @@ rfc6979 = { version = "0.4.0", default-features = false } sha2 = { version = "0.10.6", default-features = false } zeroize = { version = "1.6.0", default-features = false } hex = { version = "0.4.3", default-features = false, optional = true } -starknet-types-core = { version = "0.1.3", default-features = false, features = ["curve"] } +starknet-types-core = { version = "0.1.6", default-features = false, features = ["curve", "hash"] } [features] default = ["std", "signature-display"] -std = [] +std = ["starknet-types-core/std"] alloc = ["hex?/alloc", "starknet-types-core/alloc"] signature-display = ["dep:hex", "alloc"] +pedersen_no_lookup = [] [dev-dependencies] criterion = { version = "0.4.0", default-features = false } @@ -39,7 +39,7 @@ hex = "0.4.3" hex-literal = "0.4.1" serde = { version = "1.0.160", features = ["derive"] } serde_json = "1.0.96" -starknet-types-core = { version = "0.1.3", default-features = false, features = ["alloc"] } +starknet-types-core = { version = "0.1.6", default-features = false, features = ["alloc"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.34" @@ -71,3 +71,6 @@ harness = false [[bench]] name = "rfc6979_generate_k" harness = false + +[lints] +workspace = true diff --git a/starknet-crypto/README.md b/starknet-crypto/README.md index c4790d62..32e92fca 100644 --- a/starknet-crypto/README.md +++ b/starknet-crypto/README.md @@ -1,6 +1,14 @@ # Low-level cryptography utilities for Starknet -`starknet-crypto` contains utilities for performing **low-level** cryptographic operations in Starknet. +`starknet-crypto` contains utilities for performing **low-level** cryptographic operations in Starknet: + +- ECDSA operations + - Signing hashes + - Verifying signatures + - Recovering public keys from signatures +- Pedersen hash +- Poseidon hash +- RFC-6979 > _You're advised to use high-level crypto utilities implemented by the `starknet-core` crate (or use it through the `starknet::core` re-export) if you're not familiar with cryptographic primitives. Using these low-level functions incorrectly could result in leaking your private key, for example._ @@ -57,6 +65,14 @@ poseidon_hash_many time: [41.878 µs 41.911 µs 41.945 µs] rfc6979_generate_k time: [11.564 µs 11.566 µs 11.569 µs] ``` +## Binary size optimization + +By default, `starknet-crypto` ships with a Pedersen hash implementation utilizing a lookup table for better performance. To optimize for binary size over performance, the crate offers a `pedersen_no_lookup` feature, which uses a vanilla unoptimized implementation instead. + +> [!WARNING] +> +> Enabling the `pedersen_no_lookup` feature significantly slows down hashing performance by approximately a factor of `10`. Make sure you understand the impact on your use case before turning it on. + ## Credits Most of the code in this crate for the Pedersen hash implementation was inspired and modified from the awesome [`pathfinder` from Equilibrium](https://github.com/eqlabs/pathfinder/blob/b091cb889e624897dbb0cbec3c1df9a9e411eb1e/crates/pedersen/src/lib.rs). diff --git a/starknet-crypto/src/ecdsa.rs b/starknet-crypto/src/ecdsa.rs index ea5c4f7c..066add30 100644 --- a/starknet-crypto/src/ecdsa.rs +++ b/starknet-crypto/src/ecdsa.rs @@ -7,6 +7,14 @@ use crate::{ use starknet_types_core::curve::{AffinePoint, ProjectivePoint}; use starknet_types_core::felt::Felt; +/// The (exclusive) upper bound on many ECDSA-related elements based on the original C++ +/// implementation from [`crypto-cpp`](https://github.com/starkware-libs/crypto-cpp). +/// +/// The C++ implementation [imposes](https://github.com/starkware-libs/crypto-cpp/blob/78e3ed8dc7a0901fe6d62f4e99becc6e7936adfd/src/starkware/crypto/ecdsa.cc#L23) +/// an upper bound of `0x0800000000000000000000000000000000000000000000000000000000000000`. +/// +/// When a compuated value is greater than or equal to this bound, the modulus is taken to ensure +/// the resulting value falls under the bound. const ELEMENT_UPPER_BOUND: Felt = Felt::from_raw([ 576459263475450960, 18446744073709255680, @@ -14,7 +22,7 @@ const ELEMENT_UPPER_BOUND: Felt = Felt::from_raw([ 18446743986131435553, ]); -/// Stark ECDSA signature +/// Stark ECDSA signature. #[derive(Debug)] pub struct Signature { /// The `r` value of a signature @@ -23,7 +31,7 @@ pub struct Signature { pub s: Felt, } -/// Stark ECDSA signature with `v` +/// Stark ECDSA signature with `v`, useful for recovering the public key. #[derive(Debug)] pub struct ExtendedSignature { /// The `r` value of a signature @@ -45,7 +53,7 @@ impl From for Signature { #[cfg(feature = "signature-display")] impl core::fmt::Display for Signature { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "{}{}", @@ -57,7 +65,7 @@ impl core::fmt::Display for Signature { #[cfg(feature = "signature-display")] impl core::fmt::Display for ExtendedSignature { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( f, "{}{}{:02x}", @@ -70,9 +78,9 @@ impl core::fmt::Display for ExtendedSignature { /// Computes the public key given a Stark private key. /// -/// ### Arguments +/// ### Parameters /// -/// * `private_key`: The private key +/// - `private_key`: The private key. pub fn get_public_key(private_key: &Felt) -> Felt { mul_by_bits(&GENERATOR, private_key) .to_affine() @@ -82,11 +90,11 @@ pub fn get_public_key(private_key: &Felt) -> Felt { /// Computes ECDSA signature given a Stark private key and message hash. /// -/// ### Arguments +/// ### Parameters /// -/// * `private_key`: The private key -/// * `message`: The message hash -/// * `k`: A random `k` value. You **MUST NOT** use the same `k` on different signatures +/// - `private_key`: The private key. +/// - `message`: The message hash. +/// - `k`: A random `k` value. You **MUST NOT** use the same `k` on different signatures. pub fn sign(private_key: &Felt, message: &Felt, k: &Felt) -> Result { if message >= &ELEMENT_UPPER_BOUND { return Err(SignError::InvalidMessageHash); @@ -120,12 +128,12 @@ pub fn sign(private_key: &Felt, message: &Felt, k: &Felt) -> Result Result { if message >= &ELEMENT_UPPER_BOUND { return Err(VerifyError::InvalidMessageHash); @@ -162,12 +170,12 @@ pub fn verify(public_key: &Felt, message: &Felt, r: &Felt, s: &Felt) -> Result Result { if message >= &ELEMENT_UPPER_BOUND { return Err(RecoverError::InvalidMessageHash); @@ -278,7 +286,7 @@ mod tests { serde_json::from_str(json_data).expect("Unable to parse the JSON"); // Iterating over each element in the JSON - for (private_key, expected_public_key) in key_map.into_iter() { + for (private_key, expected_public_key) in key_map { let private_key = if private_key.len() % 2 != 0 { format!("0{}", private_key.trim_start_matches("0x")) } else { diff --git a/starknet-crypto/src/error.rs b/starknet-crypto/src/error.rs index 6f42fe15..b21e7f2d 100644 --- a/starknet-crypto/src/error.rs +++ b/starknet-crypto/src/error.rs @@ -1,8 +1,11 @@ mod sign_error { - /// Errors when performing ECDSA [`sign`](fn.sign) operations + /// Errors when performing ECDSA [`sign`](fn.sign) operations. #[derive(Debug)] pub enum SignError { + /// The message hash is not in the range of `[0, 2^251)`. InvalidMessageHash, + /// The random `k` value results in an invalid signature. A different `k` value should be + /// used instead, typically by using a new seed per RFC-6979. InvalidK, } @@ -21,12 +24,16 @@ mod sign_error { pub use sign_error::SignError; mod verify_error { - /// Errors when performing ECDSA [`verify`](fn.verify) operations + /// Errors when performing ECDSA [`verify`](fn.verify) operations. #[derive(Debug)] pub enum VerifyError { + /// The public key is not a valid point on the STARK curve. InvalidPublicKey, + /// The message hash is not in the range of `[0, 2^251)`. InvalidMessageHash, + /// The `r` value is not in the range of `[0, 2^251)`. InvalidR, + /// The `s` value is not in the range of `[0, 2^251)`. InvalidS, } @@ -47,12 +54,17 @@ mod verify_error { pub use verify_error::VerifyError; mod recover_error { - /// Errors when performing ECDSA [`recover`](fn.recover) operations + /// Errors when performing ECDSA [`recover`](fn.recover) operations. #[derive(Debug)] pub enum RecoverError { + /// The message hash is not in the range of `[0, 2^251)`. InvalidMessageHash, + /// The `r` value is not in the range of `[0, 2^251)`. InvalidR, + /// The `s` value is not in the range of `[0, + /// 0x0800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f)`. InvalidS, + /// The `v` value is neither `0` nor `1`. InvalidV, } diff --git a/starknet-crypto/src/lib.rs b/starknet-crypto/src/lib.rs index 16470b64..ce10c1f3 100644 --- a/starknet-crypto/src/lib.rs +++ b/starknet-crypto/src/lib.rs @@ -1,6 +1,23 @@ +//! Low-level cryptography utilities for Starknet. Features include: +//! +//! - ECDSA operations +//! - [Signing hashes](fn.sign) +//! - [Verifying signatures](fn.verify) +//! - [Recovering public keys from signatures](fn.recover) +//! - [Pedersen hash](fn.pedersen_hash) +//! - Poseidon hash +//! - [RFC-6979](fn.rfc6979_generate_k) +//! +//! # Warning +//! +//! You're advised to use high-level crypto utilities implemented by the `starknet-core` crate if +//! you're not familiar with cryptographic primitives. Using these low-level functions incorrectly +//! could result in catastrophic consequences like leaking your private key. + +#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -#![doc = include_str!("../README.md")] +#[allow(unused_extern_crates)] #[cfg(all(not(feature = "std"), any(test, feature = "alloc")))] extern crate alloc; @@ -8,7 +25,6 @@ mod ecdsa; mod error; mod fe_utils; mod pedersen_hash; -mod pedersen_points; mod poseidon_hash; mod rfc6979; diff --git a/starknet-crypto/src/pedersen_hash.rs b/starknet-crypto/src/pedersen_hash.rs deleted file mode 100644 index a5026750..00000000 --- a/starknet-crypto/src/pedersen_hash.rs +++ /dev/null @@ -1,83 +0,0 @@ -use starknet_curve::curve_params; -use starknet_types_core::curve::{AffinePoint, ProjectivePoint}; -use starknet_types_core::felt::Felt; - -use crate::pedersen_points::*; - -/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian. -/// -/// ### Arguments -/// -/// * `x`: The x coordinate -/// * `y`: The y coordinate -pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt { - let x = x.to_bits_le(); - let y = y.to_bits_le(); - - // Preprocessed material is lookup-tables for each chunk of bits - let table_size = (1 << CURVE_CONSTS_BITS) - 1; - let add_points = |acc: &mut ProjectivePoint, bits: &[bool], prep: &[AffinePoint]| { - bits.chunks(CURVE_CONSTS_BITS) - .enumerate() - .for_each(|(i, v)| { - let offset = v - .iter() - .rev() - .fold(0, |acc, &bit| (acc << 1) + bit as usize); - if offset > 0 { - // Table lookup at 'offset-1' in table for chunk 'i' - *acc += &prep[i * table_size + offset - 1]; - } - }); - }; - - // Compute hash - let mut acc = - ProjectivePoint::from_affine(curve_params::SHIFT_POINT.x(), curve_params::SHIFT_POINT.y()) - .unwrap(); - - add_points(&mut acc, &x[..248], &CURVE_CONSTS_P0); // Add a_low * P1 - add_points(&mut acc, &x[248..252], &CURVE_CONSTS_P1); // Add a_high * P2 - add_points(&mut acc, &y[..248], &CURVE_CONSTS_P2); // Add b_low * P3 - add_points(&mut acc, &y[248..252], &CURVE_CONSTS_P3); // Add b_high * P4 - - // Convert to affine - let result = acc.to_affine().unwrap(); - - // Return x-coordinate - result.x() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::field_element_from_be_hex; - - // Test case ported from: - // https://github.com/starkware-libs/starkex-for-spot-trading/blob/607f0b4ce507e1d95cd018d206a2797f6ba4aab4/src/starkware/crypto/starkware/crypto/signature/test/config/signature_test_data.json - - #[test] - #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] - fn test_pedersen_hash() { - let test_data = [ - ( - "03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb", - "0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a", - "030e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662", - ), - ( - "058f580910a6ca59b28927c08fe6c43e2e303ca384badc365795fc645d479d45", - "078734f65a067be9bdb39de18434d71e79f7b6466a4b66bbd979ab9e7515fe0b", - "068cc0b76cddd1dd4ed2301ada9b7c872b23875d5ff837b3a87993e0d9996b87", - ), - ]; - - for (in1, in2, expected_hash) in test_data.into_iter() { - let in1 = field_element_from_be_hex(in1); - let in2 = field_element_from_be_hex(in2); - let expected_hash = field_element_from_be_hex(expected_hash); - - assert_eq!(pedersen_hash(&in1, &in2), expected_hash); - } - } -} diff --git a/starknet-crypto/src/pedersen_hash/default.rs b/starknet-crypto/src/pedersen_hash/default.rs new file mode 100644 index 00000000..21279a76 --- /dev/null +++ b/starknet-crypto/src/pedersen_hash/default.rs @@ -0,0 +1,14 @@ +use starknet_types_core::{ + felt::Felt, + hash::{Pedersen, StarkHash}, +}; + +/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian. +/// +/// ### Parameters +/// +/// - `x`: The x coordinate. +/// - `y`: The y coordinate. +pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt { + Pedersen::hash(x, y) +} diff --git a/starknet-crypto/src/pedersen_hash/mod.rs b/starknet-crypto/src/pedersen_hash/mod.rs new file mode 100644 index 00000000..c6d01a59 --- /dev/null +++ b/starknet-crypto/src/pedersen_hash/mod.rs @@ -0,0 +1,43 @@ +#[cfg(not(feature = "pedersen_no_lookup"))] +mod default; +#[cfg(not(feature = "pedersen_no_lookup"))] +pub use default::pedersen_hash; + +#[cfg(feature = "pedersen_no_lookup")] +mod no_lookup; +#[cfg(feature = "pedersen_no_lookup")] +pub use no_lookup::pedersen_hash; + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::field_element_from_be_hex; + + // Test case ported from: + // https://github.com/starkware-libs/starkex-for-spot-trading/blob/607f0b4ce507e1d95cd018d206a2797f6ba4aab4/src/starkware/crypto/starkware/crypto/signature/test/config/signature_test_data.json + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + fn test_pedersen_hash() { + let test_data = [ + ( + "03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb", + "0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a", + "030e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662", + ), + ( + "058f580910a6ca59b28927c08fe6c43e2e303ca384badc365795fc645d479d45", + "078734f65a067be9bdb39de18434d71e79f7b6466a4b66bbd979ab9e7515fe0b", + "068cc0b76cddd1dd4ed2301ada9b7c872b23875d5ff837b3a87993e0d9996b87", + ), + ]; + + for (in1, in2, expected_hash) in test_data { + let in1 = field_element_from_be_hex(in1); + let in2 = field_element_from_be_hex(in2); + let expected_hash = field_element_from_be_hex(expected_hash); + + assert_eq!(pedersen_hash(&in1, &in2), expected_hash); + } + } +} diff --git a/starknet-crypto/src/pedersen_hash/no_lookup.rs b/starknet-crypto/src/pedersen_hash/no_lookup.rs new file mode 100644 index 00000000..b999a451 --- /dev/null +++ b/starknet-crypto/src/pedersen_hash/no_lookup.rs @@ -0,0 +1,97 @@ +// Size-optimized implementation ported from: +// https://github.com/andrewmilson/sandstorm/blob/9e256c4933aa2d89f794b3ed7c293b32984fe1ce/builtins/src/pedersen/mod.rs#L24-L50 + +use starknet_curve::curve_params::SHIFT_POINT; +use starknet_types_core::{curve::ProjectivePoint, felt::Felt}; + +/// Computes the Starkware version of the Pedersen hash of x and y. All inputs are little-endian. +/// +/// ### Parameters +/// +/// - `x`: The x coordinate. +/// - `y`: The y coordinate. +pub fn pedersen_hash(x: &Felt, y: &Felt) -> Felt { + // Temporarily defining the projective points inline, as `ProjectivePoint::new()` is incorrectly + // not `const`. + // TODO: turn these into consts once upstream is fixed. + let p0_projective: ProjectivePoint = ProjectivePoint::new( + Felt::from_raw([ + 241691544791834578, + 518715844721862878, + 13758484295849329960, + 3602345268353203007, + ]), + Felt::from_raw([ + 368891789801938570, + 433857700841878496, + 13001553326386915570, + 13441546676070136227, + ]), + Felt::ONE, + ); + let p1_projective: ProjectivePoint = ProjectivePoint::new( + Felt::from_raw([ + 253000153565733272, + 10043949394709899044, + 12382025591154462459, + 16491878934996302286, + ]), + Felt::from_raw([ + 285630633187035523, + 5191292837124484988, + 2545498000137298346, + 13950428914333633429, + ]), + Felt::ONE, + ); + let p2_projective: ProjectivePoint = ProjectivePoint::new( + Felt::from_raw([ + 338510149841406402, + 12916675983929588442, + 18195981508842736832, + 1203723169299412240, + ]), + Felt::from_raw([ + 161068411212710156, + 11088962269971685343, + 11743524503750604092, + 12352616181161700245, + ]), + Felt::ONE, + ); + let p3_projective: ProjectivePoint = ProjectivePoint::new( + Felt::from_raw([ + 425493972656615276, + 299781701614706065, + 10664803185694787051, + 1145636535101238356, + ]), + Felt::from_raw([ + 345457391846365716, + 6033691581221864148, + 4428713245976508844, + 8187986478389849302, + ]), + Felt::ONE, + ); + + let processed_x = process_element(x, &p0_projective, &p1_projective); + let processed_y = process_element(y, &p2_projective, &p3_projective); + + // Unwrapping is safe as this never fails + (processed_x + processed_y + SHIFT_POINT) + .to_affine() + .unwrap() + .x() +} + +#[inline(always)] +fn process_element(x: &Felt, p1: &ProjectivePoint, p2: &ProjectivePoint) -> ProjectivePoint { + let x = x.to_biguint(); + let shift = 252 - 4; + let high_part = &x >> shift; + let low_part = x - (&high_part << shift); + let x_high = Felt::from(high_part); + let x_low = Felt::from(low_part); + p1 * x_low + p2 * x_high +} diff --git a/starknet-crypto/src/pedersen_points.rs b/starknet-crypto/src/pedersen_points.rs deleted file mode 100644 index 817b1092..00000000 --- a/starknet-crypto/src/pedersen_points.rs +++ /dev/null @@ -1,3 +0,0 @@ -use starknet_crypto_codegen::lookup_table; - -lookup_table!(4); diff --git a/starknet-crypto/src/poseidon_hash.rs b/starknet-crypto/src/poseidon_hash.rs index 4f3db345..ff557ded 100644 --- a/starknet-crypto/src/poseidon_hash.rs +++ b/starknet-crypto/src/poseidon_hash.rs @@ -1,14 +1,11 @@ // Code ported from the the implementation from pathfinder here: // https://github.com/eqlabs/pathfinder/blob/00a1a74a90a7b8a7f1d07ac3e616be1cb39cf8f1/crates/stark_poseidon/src/lib.rs -use starknet_crypto_codegen::poseidon_consts; -use starknet_types_core::felt::Felt; +use starknet_types_core::{felt::Felt, hash::Poseidon}; -poseidon_consts!(); - -/// A hasher for Starknet Poseidon hash. +/// A stateful hasher for Starknet Poseidon hash. /// -/// Using this hasher is the same as calling [poseidon_hash_many]. +/// Using this hasher is the same as calling [`poseidon_hash_many`]. #[derive(Debug, Default)] pub struct PoseidonHasher { state: [Felt; 3], @@ -16,7 +13,7 @@ pub struct PoseidonHasher { } impl PoseidonHasher { - /// Creates a new [PoseidonHasher]. + /// Creates a new [`PoseidonHasher`]. pub fn new() -> Self { Self::default() } @@ -27,7 +24,7 @@ impl PoseidonHasher { Some(previous_message) => { self.state[0] += previous_message; self.state[1] += msg; - poseidon_permute_comp(&mut self.state); + Poseidon::hades_permutation(&mut self.state); } None => { self.buffer = Some(msg); @@ -47,7 +44,7 @@ impl PoseidonHasher { self.state[0] += Felt::ONE; } } - poseidon_permute_comp(&mut self.state); + Poseidon::hades_permutation(&mut self.state); self.state[0] } @@ -56,22 +53,22 @@ impl PoseidonHasher { /// Computes the Starknet Poseidon hash of x and y. pub fn poseidon_hash(x: Felt, y: Felt) -> Felt { let mut state = [x, y, Felt::TWO]; - poseidon_permute_comp(&mut state); + Poseidon::hades_permutation(&mut state); state[0] } -/// Computes the Starknet Poseidon hash of a single [Felt]. +/// Computes the Starknet Poseidon hash of a single [`Felt`]. pub fn poseidon_hash_single(x: Felt) -> Felt { let mut state = [x, Felt::ZERO, Felt::ONE]; - poseidon_permute_comp(&mut state); + Poseidon::hades_permutation(&mut state); state[0] } -/// Computes the Starknet Poseidon hash of an arbitrary number of [Felt]s. +/// Computes the Starknet Poseidon hash of an arbitrary number of [`Felt`]s. /// -/// Using this function is the same as using [PoseidonHasher]. +/// Using this function is the same as using [`PoseidonHasher`]. pub fn poseidon_hash_many<'a, I: IntoIterator>(msgs: I) -> Felt { let mut state = [Felt::ZERO, Felt::ZERO, Felt::ZERO]; let mut iter = msgs.into_iter(); @@ -93,64 +90,22 @@ pub fn poseidon_hash_many<'a, I: IntoIterator>(msgs: I) -> Felt } } - poseidon_permute_comp(&mut state); + Poseidon::hades_permutation(&mut state); } - poseidon_permute_comp(&mut state); + Poseidon::hades_permutation(&mut state); state[0] } -/// Poseidon permutation function. +/// Poseidon permutation function. pub fn poseidon_permute_comp(state: &mut [Felt; 3]) { - let mut idx = 0; - - // Full rounds - for _ in 0..(FULL_ROUNDS / 2) { - round_comp(state, idx, true); - idx += 3; - } - - // Partial rounds - for _ in 0..PARTIAL_ROUNDS { - round_comp(state, idx, false); - idx += 1; - } - - // Full rounds - for _ in 0..(FULL_ROUNDS / 2) { - round_comp(state, idx, true); - idx += 3; - } -} - -/// Linear layer for MDS matrix M = ((3,1,1), (1,-1,1), (1,1,2)) -/// Given state vector x, it returns Mx, optimized by precomputing t. -#[inline(always)] -fn mix(state: &mut [Felt; 3]) { - let t = state[0] + state[1] + state[2]; - state[0] = t + state[0].double(); - state[1] = t - state[1].double(); - state[2] = t - Felt::THREE * state[2]; -} - -#[inline] -fn round_comp(state: &mut [Felt; 3], idx: usize, full: bool) { - if full { - state[0] += POSEIDON_COMP_CONSTS[idx]; - state[1] += POSEIDON_COMP_CONSTS[idx + 1]; - state[2] += POSEIDON_COMP_CONSTS[idx + 2]; - state[0] = state[0] * state[0] * state[0]; - state[1] = state[1] * state[1] * state[1]; - state[2] = state[2] * state[2] * state[2]; - } else { - state[2] += POSEIDON_COMP_CONSTS[idx]; - state[2] = state[2] * state[2] * state[2]; - } - mix(state); + Poseidon::hades_permutation(state) } #[cfg(test)] mod tests { + use starknet_types_core::hash::StarkHash; + use super::*; #[test] @@ -176,8 +131,8 @@ mod tests { ), ]; - for (x, y, hash) in test_data.into_iter() { - assert_eq!(poseidon_hash(x, y), hash); + for (x, y, hash) in test_data { + assert_eq!(Poseidon::hash(&x, &y), hash); } } @@ -200,8 +155,10 @@ mod tests { ), ]; - for (x, hash) in test_data.into_iter() { - assert_eq!(poseidon_hash_single(x), hash); + for (x, hash) in test_data { + let mut state = [x, Felt::ZERO, Felt::ONE]; + Poseidon::hades_permutation(&mut state); + assert_eq!(state[0], hash); } } @@ -252,9 +209,9 @@ mod tests { ), ]; - for (input, hash) in test_data.into_iter() { + for (input, hash) in test_data { // Direct function call - assert_eq!(poseidon_hash_many(&input), hash); + assert_eq!(Poseidon::hash_array(&input), hash); // With hasher let mut hasher = PoseidonHasher::new(); diff --git a/starknet-crypto/src/rfc6979.rs b/starknet-crypto/src/rfc6979.rs index af2897a9..550040df 100644 --- a/starknet-crypto/src/rfc6979.rs +++ b/starknet-crypto/src/rfc6979.rs @@ -9,11 +9,11 @@ const EC_ORDER: U256 = /// Deterministically generate ephemeral scalar `k` based on RFC 6979. /// -/// ### Arguments +/// ### Parameters /// -/// * `message_hash`: message hash -/// * `private_key`: private key -/// * `seed`: extra seed for additional entropy +/// - `message_hash`: Message hash. +/// - `private_key`: Private key. +/// - `seed`: Extra seed for additional entropy. pub fn generate_k(message_hash: &Felt, private_key: &Felt, seed: Option<&Felt>) -> Felt { // The message hash padding as implemented in `cairo-lang` is not needed here. The hash is // padded in `cairo-lang` only to make sure the lowest 4 bits won't get truncated, but here it's @@ -104,9 +104,9 @@ mod tests { } fn test_generate_k_from_json_str(json_str: &'static str) { - let test_vectors: Vec = serde_json::from_str(json_str).unwrap(); + let test_vectors: Vec> = serde_json::from_str(json_str).unwrap(); - for test_vector in test_vectors.iter() { + for test_vector in &test_vectors { let msg_hash = field_element_from_be_hex(test_vector.msg_hash); let priv_key = field_element_from_be_hex(test_vector.priv_key); let seed = field_element_from_be_hex(test_vector.seed); diff --git a/starknet-curve/Cargo.toml b/starknet-curve/Cargo.toml index 83c291ba..0f3da9a6 100644 --- a/starknet-curve/Cargo.toml +++ b/starknet-curve/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-curve" -version = "0.5.0" +version = "0.5.1" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -13,4 +13,7 @@ Stark curve keywords = ["ethereum", "starknet", "web3", "no_std"] [dependencies] -starknet-types-core = { version = "0.1.3", default-features = false, features = ["curve"] } +starknet-types-core = { version = "0.1.6", default-features = false, features = ["curve"] } + +[lints] +workspace = true diff --git a/starknet-curve/README.md b/starknet-curve/README.md index 09e089dd..27300e0c 100644 --- a/starknet-curve/README.md +++ b/starknet-curve/README.md @@ -1 +1,12 @@ -# Starknet Curve +# Starknet Curve Parameters + +The `starknet-curve` crate contains a few useful parameters for the STARK curve defined as: + +``` +y^2 = x^3 + alpha * x + beta +``` + +where: + +- `alpha` = `0x0000000000000000000000000000000000000000000000000000000000000001` +- `beta` = `0x06f21413efbe40de150e596d72f7a8c5609ad26c15c915c1f4cdfcb99cee9e89` diff --git a/starknet-curve/src/curve_params.rs b/starknet-curve/src/curve_params.rs index 5368cbc4..5e7cd3e8 100644 --- a/starknet-curve/src/curve_params.rs +++ b/starknet-curve/src/curve_params.rs @@ -1,6 +1,8 @@ use starknet_types_core::curve::AffinePoint; use starknet_types_core::felt::Felt; +/// EC order of the STARK curve for ECDSA. Equals to +/// `0x0800000000000010ffffffffffffffffb781126dcae7b2321e66a241adc64d2f`. pub const EC_ORDER: Felt = Felt::from_raw([ 369010039416812937, 9, @@ -8,6 +10,14 @@ pub const EC_ORDER: Felt = Felt::from_raw([ 8939893405601011193, ]); +/// The alpha parameter of the STARK curve. Equals to +/// `0x0000000000000000000000000000000000000000000000000000000000000001`. +/// +/// The alpha parameter is used in the curve definition as: +/// +/// ```markdown +/// y^2 = x^3 + alpha * x + beta +/// ``` pub const ALPHA: Felt = Felt::from_raw([ 576460752303422960, 18446744073709551615, @@ -15,6 +25,14 @@ pub const ALPHA: Felt = Felt::from_raw([ 18446744073709551585, ]); +/// The beta parameter of the STARK curve. Equals to +/// `0x06f21413efbe40de150e596d72f7a8c5609ad26c15c915c1f4cdfcb99cee9e89`. +/// +/// The beta parameter is used in the curve definition as: +/// +/// ```markdown +/// y^2 = x^3 + alpha * x + beta +/// ``` pub const BETA: Felt = Felt::from_raw([ 88155977965380735, 12360725113329547591, @@ -22,6 +40,12 @@ pub const BETA: Felt = Felt::from_raw([ 3863487492851900874, ]); +/// Generator point of the STARK curve. +/// +/// Coordinates: +/// +/// - x: `0x01ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca` +/// - y: `0x005668060aa49730b7be4801df46ec62de53ecd11abe43a32873000c36e8dc1f` pub const GENERATOR: AffinePoint = AffinePoint::new_unchecked( Felt::from_raw([ 232005955912912577, @@ -37,6 +61,12 @@ pub const GENERATOR: AffinePoint = AffinePoint::new_unchecked( ]), ); +/// Shift point of the STARK curve. +/// +/// Coordinates: +/// +/// - x: `0x049ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804` +/// - y: `0x03ca0cfe4b3bc6ddf346d49d06ea0ed34e621062c0e056c1d0405d266e10268a` pub const SHIFT_POINT: AffinePoint = AffinePoint::new_unchecked( Felt::from_raw([ 316327189671755572, @@ -52,6 +82,12 @@ pub const SHIFT_POINT: AffinePoint = AffinePoint::new_unchecked( ]), ); +/// The P0 constant of the STARK curve. +/// +/// Coordinates: +/// +/// - x: `0x0234287dcbaffe7f969c748655fca9e58fa8120b6d56eb0c1080d17957ebe47b` +/// - y: `0x03b056f100f96fb21e889527d41f4e39940135dd7a6c94cc6ed0268ee89e5615` pub const PEDERSEN_P0: AffinePoint = AffinePoint::new_unchecked( Felt::from_raw([ 241691544791834578, @@ -67,6 +103,12 @@ pub const PEDERSEN_P0: AffinePoint = AffinePoint::new_unchecked( ]), ); +/// The P1 constant of the STARK curve. +/// +/// Coordinates: +/// +/// - x: `0x04fa56f376c83db33f9dab2656558f3399099ec1de5e3018b7a6932dba8aa378` +/// - y: `0x03fa0984c931c9e38113e0c0e47e4401562761f92a7a23b45168f4e80ff5b54d` pub const PEDERSEN_P1: AffinePoint = AffinePoint::new_unchecked( Felt::from_raw([ 253000153565733272, @@ -82,6 +124,12 @@ pub const PEDERSEN_P1: AffinePoint = AffinePoint::new_unchecked( ]), ); +/// The P2 constant of the STARK curve. +/// +/// Coordinates: +/// +/// - x: `0x04ba4cc166be8dec764910f75b45f74b40c690c74709e90f3aa372f0bd2d6997` +/// - y: `0x0040301cf5c1751f4b971e46c4ede85fcac5c59a5ce5ae7c48151f27b24b219c` pub const PEDERSEN_P2: AffinePoint = AffinePoint::new_unchecked( Felt::from_raw([ 338510149841406402, @@ -97,6 +145,12 @@ pub const PEDERSEN_P2: AffinePoint = AffinePoint::new_unchecked( ]), ); +/// The P3 constant of the STARK curve. +/// +/// Coordinates: +/// +/// - x: `0x054302dcb0e6cc1c6e44cca8f61a63bb2ca65048d53fb325d36ff12c49a58202` +/// - y: `0x01b77b3e37d13504b348046268d8ae25ce98ad783c25561a879dcc77e99c2426` pub const PEDERSEN_P3: AffinePoint = AffinePoint::new_unchecked( Felt::from_raw([ 425493972656615276, diff --git a/starknet-curve/src/lib.rs b/starknet-curve/src/lib.rs index a092b315..b336e9cb 100644 --- a/starknet-curve/src/lib.rs +++ b/starknet-curve/src/lib.rs @@ -1,4 +1,7 @@ +//! A library with constant parameters for the Stark elliptic curve. + +#![deny(missing_docs)] #![no_std] -#![doc = include_str!("../README.md")] +/// Module containing the Stark elliptic curve parameters. pub mod curve_params; diff --git a/starknet-macros/Cargo.toml b/starknet-macros/Cargo.toml index 82d6471c..05f98a1f 100644 --- a/starknet-macros/Cargo.toml +++ b/starknet-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-macros" -version = "0.2.0" +version = "0.2.1" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -16,9 +16,12 @@ keywords = ["ethereum", "starknet", "web3"] proc-macro = true [dependencies] -starknet-core = { version = "0.11.1", path = "../starknet-core" } +starknet-core = { version = "0.12.0", path = "../starknet-core" } syn = "2.0.15" [features] default = [] use_imported_type = [] + +[lints] +workspace = true diff --git a/starknet-macros/src/lib.rs b/starknet-macros/src/lib.rs index ebeebe38..eb7114ad 100644 --- a/starknet-macros/src/lib.rs +++ b/starknet-macros/src/lib.rs @@ -1,3 +1,8 @@ +//! Procedural macros for the `starknet` crate. This crate provides macros that help make defining +//! certain compile-time constants easier. + +#![deny(missing_docs)] + use proc_macro::TokenStream; use starknet_core::{ types::Felt, @@ -5,6 +10,7 @@ use starknet_core::{ }; use syn::{parse_macro_input, LitStr}; +/// Defines a compile-time constant for a entrypoint selector of a Starknet contract. #[proc_macro] pub fn selector(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as LitStr); @@ -26,6 +32,7 @@ pub fn selector(input: TokenStream) -> TokenStream { .unwrap() } +/// Defines a compile-time constant for a Cairo short string encoding from a human-readable string. #[proc_macro] pub fn short_string(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as LitStr); @@ -47,6 +54,8 @@ pub fn short_string(input: TokenStream) -> TokenStream { .unwrap() } +/// Defines a compile-time constant for a field element from its decimal or hexadecimal +/// representation. #[proc_macro] pub fn felt(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as LitStr); @@ -73,6 +82,7 @@ pub fn felt(input: TokenStream) -> TokenStream { .unwrap() } +/// Defines a compile-time constant for a field element from its decimal representation. #[proc_macro] pub fn felt_dec(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as LitStr); @@ -94,6 +104,7 @@ pub fn felt_dec(input: TokenStream) -> TokenStream { .unwrap() } +/// Defines a compile-time constant for a field element from its hexadecimal representation. #[proc_macro] pub fn felt_hex(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as LitStr); @@ -116,11 +127,11 @@ pub fn felt_hex(input: TokenStream) -> TokenStream { } #[cfg(feature = "use_imported_type")] -fn field_element_path() -> &'static str { +const fn field_element_path() -> &'static str { "Felt" } #[cfg(not(feature = "use_imported_type"))] -fn field_element_path() -> &'static str { +const fn field_element_path() -> &'static str { "::starknet::core::types::Felt" } diff --git a/starknet-providers/Cargo.toml b/starknet-providers/Cargo.toml index 3760028c..335a4fbf 100644 --- a/starknet-providers/Cargo.toml +++ b/starknet-providers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-providers" -version = "0.11.0" +version = "0.12.0" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -14,7 +14,7 @@ keywords = ["ethereum", "starknet", "web3"] exclude = ["test-data/**"] [dependencies] -starknet-core = { version = "0.11.1", path = "../starknet-core" } +starknet-core = { version = "0.12.0", path = "../starknet-core" } async-trait = "0.1.68" auto_impl = "1.0.1" ethereum-types = "0.14.1" @@ -25,7 +25,7 @@ reqwest = { version = "0.11.16", default-features = false, features = ["rustls-t thiserror = "1.0.40" serde = "1.0.160" serde_json = "1.0.96" -serde_with = "2.3.2" +serde_with = "3.9.0" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.9", features = ["js"] } @@ -39,3 +39,6 @@ default = [] no_unknown_fields = [ "starknet-core/no_unknown_fields" ] + +[lints] +workspace = true diff --git a/starknet-providers/src/any.rs b/starknet-providers/src/any.rs index 7bbd7f13..3bc2b0ad 100644 --- a/starknet-providers/src/any.rs +++ b/starknet-providers/src/any.rs @@ -12,7 +12,7 @@ use starknet_core::types::{ use crate::{ jsonrpc::{HttpTransport, JsonRpcClient}, - Provider, ProviderError, SequencerGatewayProvider, + Provider, ProviderError, ProviderRequestData, ProviderResponseData, SequencerGatewayProvider, }; /// A convenient Box-able type that implements the [Provider] trait. This can be useful when you @@ -20,13 +20,15 @@ use crate::{ /// the [Provider] trait itself cannot be Box-ed due to the use of associated type. /// /// A recommended pattern is to make your business logic code (e.g. functions) generic over the -/// [Provider] trait, while using this [AnyProvider] type for bootstrapping your application. +/// [Provider] trait, while using this [`AnyProvider`] type for bootstrapping your application. /// /// NOTE: This type was introduced when [Provider] was not Box-able. It should be reviewed whether /// it's still needed anymore. #[derive(Debug)] pub enum AnyProvider { + /// JSON-RPC provider. JsonRpcHttp(JsonRpcClient), + /// Sequencer gateway provider. SequencerGateway(SequencerGatewayProvider), } @@ -663,4 +665,21 @@ impl Provider for AnyProvider { } } } + + async fn batch_requests( + &self, + requests: R, + ) -> Result, ProviderError> + where + R: AsRef<[ProviderRequestData]> + Send + Sync, + { + match self { + Self::JsonRpcHttp(inner) => { + as Provider>::batch_requests(inner, requests).await + } + Self::SequencerGateway(inner) => { + ::batch_requests(inner, requests).await + } + } + } } diff --git a/starknet-providers/src/jsonrpc/mod.rs b/starknet-providers/src/jsonrpc/mod.rs index e57550cc..211bd3b7 100644 --- a/starknet-providers/src/jsonrpc/mod.rs +++ b/starknet-providers/src/jsonrpc/mod.rs @@ -19,149 +19,186 @@ use starknet_core::{ }, }; -use crate::{provider::ProviderImplError, Provider, ProviderError}; +use crate::{ + provider::ProviderImplError, Provider, ProviderError, ProviderRequestData, ProviderResponseData, +}; mod transports; pub use transports::{HttpTransport, HttpTransportError, JsonRpcTransport}; -#[derive(Debug)] +/// A generic JSON-RPC client with any transport. +/// +/// A "transport" is any implementation that can send JSON-RPC requests and receive responses. This +/// most commonly happens over a network via HTTP connections, as with [`HttpTransport`]. +#[derive(Debug, Clone)] pub struct JsonRpcClient { transport: T, } +/// All JSON-RPC methods as listed by the official specification. #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum JsonRpcMethod { + /// The `starknet_specVersion` method. #[serde(rename = "starknet_specVersion")] SpecVersion, + /// The `starknet_getBlockWithTxHashes` method. #[serde(rename = "starknet_getBlockWithTxHashes")] GetBlockWithTxHashes, + /// The `starknet_getBlockWithTxs` method. #[serde(rename = "starknet_getBlockWithTxs")] GetBlockWithTxs, + /// The `starknet_getBlockWithReceipts` method. #[serde(rename = "starknet_getBlockWithReceipts")] GetBlockWithReceipts, + /// The `starknet_getStateUpdate` method. #[serde(rename = "starknet_getStateUpdate")] GetStateUpdate, + /// The `starknet_getStorageAt` method. #[serde(rename = "starknet_getStorageAt")] GetStorageAt, + /// The `starknet_getTransactionStatus` method. #[serde(rename = "starknet_getTransactionStatus")] GetTransactionStatus, + /// The `starknet_getTransactionByHash` method. #[serde(rename = "starknet_getTransactionByHash")] GetTransactionByHash, + /// The `starknet_getTransactionByBlockIdAndIndex` method. #[serde(rename = "starknet_getTransactionByBlockIdAndIndex")] GetTransactionByBlockIdAndIndex, + /// The `starknet_getTransactionReceipt` method. #[serde(rename = "starknet_getTransactionReceipt")] GetTransactionReceipt, + /// The `starknet_getClass` method. #[serde(rename = "starknet_getClass")] GetClass, + /// The `starknet_getClassHashAt` method. #[serde(rename = "starknet_getClassHashAt")] GetClassHashAt, + /// The `starknet_getClassAt` method. #[serde(rename = "starknet_getClassAt")] GetClassAt, + /// The `starknet_getBlockTransactionCount` method. #[serde(rename = "starknet_getBlockTransactionCount")] GetBlockTransactionCount, + /// The `starknet_call` method. #[serde(rename = "starknet_call")] Call, + /// The `starknet_estimateFee` method. #[serde(rename = "starknet_estimateFee")] EstimateFee, + /// The `starknet_estimateMessageFee` method. #[serde(rename = "starknet_estimateMessageFee")] EstimateMessageFee, + /// The `starknet_blockNumber` method. #[serde(rename = "starknet_blockNumber")] BlockNumber, + /// The `starknet_blockHashAndNumber` method. #[serde(rename = "starknet_blockHashAndNumber")] BlockHashAndNumber, + /// The `starknet_chainId` method. #[serde(rename = "starknet_chainId")] ChainId, + /// The `starknet_syncing` method. #[serde(rename = "starknet_syncing")] Syncing, + /// The `starknet_getEvents` method. #[serde(rename = "starknet_getEvents")] GetEvents, + /// The `starknet_getNonce` method. #[serde(rename = "starknet_getNonce")] GetNonce, + /// The `starknet_addInvokeTransaction` method. #[serde(rename = "starknet_addInvokeTransaction")] AddInvokeTransaction, + /// The `starknet_addDeclareTransaction` method. #[serde(rename = "starknet_addDeclareTransaction")] AddDeclareTransaction, + /// The `starknet_addDeployAccountTransaction` method. #[serde(rename = "starknet_addDeployAccountTransaction")] AddDeployAccountTransaction, + /// The `starknet_traceTransaction` method. #[serde(rename = "starknet_traceTransaction")] TraceTransaction, + /// The `starknet_simulateTransactions` method. #[serde(rename = "starknet_simulateTransactions")] SimulateTransactions, + /// The `starknet_traceBlockTransactions` method. #[serde(rename = "starknet_traceBlockTransactions")] TraceBlockTransactions, } +/// JSON-RPC request. #[derive(Debug, Clone)] pub struct JsonRpcRequest { + /// ID of the request. Useful for identifying responses in certain transports like `WebSocket`. pub id: u64, - pub data: JsonRpcRequestData, -} - -#[derive(Debug, Clone)] -pub enum JsonRpcRequestData { - SpecVersion(SpecVersionRequest), - GetBlockWithTxHashes(GetBlockWithTxHashesRequest), - GetBlockWithTxs(GetBlockWithTxsRequest), - GetBlockWithReceipts(GetBlockWithReceiptsRequest), - GetStateUpdate(GetStateUpdateRequest), - GetStorageAt(GetStorageAtRequest), - GetTransactionStatus(GetTransactionStatusRequest), - GetTransactionByHash(GetTransactionByHashRequest), - GetTransactionByBlockIdAndIndex(GetTransactionByBlockIdAndIndexRequest), - GetTransactionReceipt(GetTransactionReceiptRequest), - GetClass(GetClassRequest), - GetClassHashAt(GetClassHashAtRequest), - GetClassAt(GetClassAtRequest), - GetBlockTransactionCount(GetBlockTransactionCountRequest), - Call(CallRequest), - EstimateFee(EstimateFeeRequest), - EstimateMessageFee(EstimateMessageFeeRequest), - BlockNumber(BlockNumberRequest), - BlockHashAndNumber(BlockHashAndNumberRequest), - ChainId(ChainIdRequest), - Syncing(SyncingRequest), - GetEvents(GetEventsRequest), - GetNonce(GetNonceRequest), - AddInvokeTransaction(AddInvokeTransactionRequest), - AddDeclareTransaction(AddDeclareTransactionRequest), - AddDeployAccountTransaction(AddDeployAccountTransactionRequest), - TraceTransaction(TraceTransactionRequest), - SimulateTransactions(SimulateTransactionsRequest), - TraceBlockTransactions(TraceBlockTransactionsRequest), + /// Data of the requeest. + pub data: ProviderRequestData, } +/// Errors from JSON-RPC client. #[derive(Debug, thiserror::Error)] pub enum JsonRpcClientError { + /// JSON serialization/deserialization erors. #[error(transparent)] JsonError(serde_json::Error), + /// Transport-specific errors. #[error(transparent)] TransportError(T), + /// An unsuccessful response returned from the server is encountered. #[error(transparent)] JsonRpcError(JsonRpcError), } -#[derive(Debug, Deserialize)] +/// An unsuccessful response returned from the server. +#[derive(Debug, Clone, Deserialize)] pub struct JsonRpcError { + /// Error code. pub code: i64, + /// Error message. pub message: String, + /// Additional error data if any. #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } -#[derive(Debug, Deserialize)] +/// JSON-RPC response returned from a server. +#[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum JsonRpcResponse { - Success { id: u64, result: T }, - Error { id: u64, error: JsonRpcError }, + /// Successful response. + Success { + /// Same ID as the corresponding request. + id: u64, + /// Response data. + result: T, + }, + /// Unsuccessful response. + Error { + /// Same ID as the corresponding request. + id: u64, + /// Error details. + error: JsonRpcError, + }, } -/// Failures trying to parse a [JsonRpcError] into [StarknetError]. +/// Failures trying to parse a [`JsonRpcError`] into [`StarknetError`]. +/// +/// [`StarknetError`] is the standard, provider-agnostic error type that all [`Provider`] +/// implementations should strive to return in an error case, in a best-effort basis. This allows +/// for unified error handling logic. +/// +/// However, not all error cases can be properly converted, and this error type represents the cases +/// when such failure happens. #[derive(Debug, thiserror::Error)] pub enum JsonRpcErrorConversionError { + /// The error code is outside of the range specified by the specification. #[error("unknown error code")] UnknownCode, + /// Error data is expected but missing. #[error("missing data field")] MissingData, + /// Error data is malformed. #[error("unable to parse the data field")] DataParsingFailure, } @@ -175,7 +212,8 @@ struct Felt(#[serde_as(as = "UfeHex")] pub FeltPrimitive); struct FeltArray(#[serde_as(as = "Vec")] pub Vec); impl JsonRpcClient { - pub fn new(transport: T) -> Self { + /// Constructs a new [`JsonRpcClient`] from a transport. + pub const fn new(transport: T) -> Self { Self { transport } } } @@ -204,6 +242,199 @@ where } } } + + async fn send_requests( + &self, + requests: R, + ) -> Result, ProviderError> + where + R: AsRef<[ProviderRequestData]> + Send + Sync, + { + let mut results = vec![]; + + let responses = self + .transport + .send_requests(requests.as_ref().to_vec()) + .await + .map_err(JsonRpcClientError::TransportError)?; + + for (request, response) in requests.as_ref().iter().zip(responses.into_iter()) { + match response { + JsonRpcResponse::Success { result, .. } => { + let result = match request { + ProviderRequestData::SpecVersion(_) => ProviderResponseData::SpecVersion( + String::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::GetBlockWithTxHashes(_) => { + ProviderResponseData::GetBlockWithTxHashes( + MaybePendingBlockWithTxHashes::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetBlockWithTxs(_) => { + ProviderResponseData::GetBlockWithTxs( + MaybePendingBlockWithTxs::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetBlockWithReceipts(_) => { + ProviderResponseData::GetBlockWithReceipts( + MaybePendingBlockWithReceipts::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetStateUpdate(_) => { + ProviderResponseData::GetStateUpdate( + MaybePendingStateUpdate::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetStorageAt(_) => ProviderResponseData::GetStorageAt( + Felt::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)? + .0, + ), + ProviderRequestData::GetTransactionStatus(_) => { + ProviderResponseData::GetTransactionStatus( + TransactionStatus::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetTransactionByHash(_) => { + ProviderResponseData::GetTransactionByHash( + Transaction::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetTransactionByBlockIdAndIndex(_) => { + ProviderResponseData::GetTransactionByBlockIdAndIndex( + Transaction::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetTransactionReceipt(_) => { + ProviderResponseData::GetTransactionReceipt( + TransactionReceiptWithBlockInfo::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::GetClass(_) => ProviderResponseData::GetClass( + ContractClass::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::GetClassHashAt(_) => { + ProviderResponseData::GetClassHashAt( + Felt::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)? + .0, + ) + } + ProviderRequestData::GetClassAt(_) => ProviderResponseData::GetClassAt( + ContractClass::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::GetBlockTransactionCount(_) => { + ProviderResponseData::GetBlockTransactionCount( + u64::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::Call(_) => ProviderResponseData::Call( + FeltArray::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)? + .0, + ), + ProviderRequestData::EstimateFee(_) => ProviderResponseData::EstimateFee( + Vec::::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::EstimateMessageFee(_) => { + ProviderResponseData::EstimateMessageFee( + FeeEstimate::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::BlockNumber(_) => ProviderResponseData::BlockNumber( + u64::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::BlockHashAndNumber(_) => { + ProviderResponseData::BlockHashAndNumber( + BlockHashAndNumber::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::ChainId(_) => ProviderResponseData::ChainId( + Felt::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)? + .0, + ), + ProviderRequestData::Syncing(_) => ProviderResponseData::Syncing( + SyncStatusType::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::GetEvents(_) => ProviderResponseData::GetEvents( + EventsPage::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ), + ProviderRequestData::GetNonce(_) => ProviderResponseData::GetNonce( + Felt::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)? + .0, + ), + ProviderRequestData::AddInvokeTransaction(_) => { + ProviderResponseData::AddInvokeTransaction( + InvokeTransactionResult::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::AddDeclareTransaction(_) => { + ProviderResponseData::AddDeclareTransaction( + DeclareTransactionResult::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::AddDeployAccountTransaction(_) => { + ProviderResponseData::AddDeployAccountTransaction( + DeployAccountTransactionResult::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::TraceTransaction(_) => { + ProviderResponseData::TraceTransaction( + TransactionTrace::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::SimulateTransactions(_) => { + ProviderResponseData::SimulateTransactions( + Vec::::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + ProviderRequestData::TraceBlockTransactions(_) => { + ProviderResponseData::TraceBlockTransactions( + Vec::::deserialize(result) + .map_err(JsonRpcClientError::::JsonError)?, + ) + } + }; + + results.push(result); + } + // TODO: add context on index of request causing the error + JsonRpcResponse::Error { error, .. } => { + return Err(match TryInto::::try_into(&error) { + Ok(error) => ProviderError::StarknetError(error), + Err(_) => JsonRpcClientError::::JsonRpcError(error).into(), + }) + } + } + } + + Ok(results) + } } #[cfg_attr(not(target_arch = "wasm32"), async_trait)] @@ -702,6 +933,54 @@ where ) .await } + + async fn batch_requests( + &self, + requests: R, + ) -> Result, ProviderError> + where + R: AsRef<[ProviderRequestData]> + Send + Sync, + { + self.send_requests(requests).await + } +} + +impl ProviderRequestData { + const fn jsonrpc_method(&self) -> JsonRpcMethod { + match self { + Self::SpecVersion(_) => JsonRpcMethod::SpecVersion, + Self::GetBlockWithTxHashes(_) => JsonRpcMethod::GetBlockWithTxHashes, + Self::GetBlockWithTxs(_) => JsonRpcMethod::GetBlockWithTxs, + Self::GetBlockWithReceipts(_) => JsonRpcMethod::GetBlockWithReceipts, + Self::GetStateUpdate(_) => JsonRpcMethod::GetStateUpdate, + Self::GetStorageAt(_) => JsonRpcMethod::GetStorageAt, + Self::GetTransactionStatus(_) => JsonRpcMethod::GetTransactionStatus, + Self::GetTransactionByHash(_) => JsonRpcMethod::GetTransactionByHash, + Self::GetTransactionByBlockIdAndIndex(_) => { + JsonRpcMethod::GetTransactionByBlockIdAndIndex + } + Self::GetTransactionReceipt(_) => JsonRpcMethod::GetTransactionReceipt, + Self::GetClass(_) => JsonRpcMethod::GetClass, + Self::GetClassHashAt(_) => JsonRpcMethod::GetClassHashAt, + Self::GetClassAt(_) => JsonRpcMethod::GetClassAt, + Self::GetBlockTransactionCount(_) => JsonRpcMethod::GetBlockTransactionCount, + Self::Call(_) => JsonRpcMethod::Call, + Self::EstimateFee(_) => JsonRpcMethod::EstimateFee, + Self::EstimateMessageFee(_) => JsonRpcMethod::EstimateMessageFee, + Self::BlockNumber(_) => JsonRpcMethod::BlockNumber, + Self::BlockHashAndNumber(_) => JsonRpcMethod::BlockHashAndNumber, + Self::ChainId(_) => JsonRpcMethod::ChainId, + Self::Syncing(_) => JsonRpcMethod::Syncing, + Self::GetEvents(_) => JsonRpcMethod::GetEvents, + Self::GetNonce(_) => JsonRpcMethod::GetNonce, + Self::AddInvokeTransaction(_) => JsonRpcMethod::AddInvokeTransaction, + Self::AddDeclareTransaction(_) => JsonRpcMethod::AddDeclareTransaction, + Self::AddDeployAccountTransaction(_) => JsonRpcMethod::AddDeployAccountTransaction, + Self::TraceTransaction(_) => JsonRpcMethod::TraceTransaction, + Self::SimulateTransactions(_) => JsonRpcMethod::SimulateTransactions, + Self::TraceBlockTransactions(_) => JsonRpcMethod::TraceBlockTransactions, + } + } } impl<'de> Deserialize<'de> for JsonRpcRequest { @@ -721,128 +1000,128 @@ impl<'de> Deserialize<'de> for JsonRpcRequest { let raw_request = RawRequest::deserialize(deserializer)?; let request_data = match raw_request.method { - JsonRpcMethod::SpecVersion => JsonRpcRequestData::SpecVersion( + JsonRpcMethod::SpecVersion => ProviderRequestData::SpecVersion( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetBlockWithTxHashes => JsonRpcRequestData::GetBlockWithTxHashes( + JsonRpcMethod::GetBlockWithTxHashes => ProviderRequestData::GetBlockWithTxHashes( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetBlockWithTxs => JsonRpcRequestData::GetBlockWithTxs( + JsonRpcMethod::GetBlockWithTxs => ProviderRequestData::GetBlockWithTxs( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetBlockWithReceipts => JsonRpcRequestData::GetBlockWithReceipts( + JsonRpcMethod::GetBlockWithReceipts => ProviderRequestData::GetBlockWithReceipts( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetStateUpdate => JsonRpcRequestData::GetStateUpdate( + JsonRpcMethod::GetStateUpdate => ProviderRequestData::GetStateUpdate( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetStorageAt => JsonRpcRequestData::GetStorageAt( + JsonRpcMethod::GetStorageAt => ProviderRequestData::GetStorageAt( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetTransactionStatus => JsonRpcRequestData::GetTransactionStatus( + JsonRpcMethod::GetTransactionStatus => ProviderRequestData::GetTransactionStatus( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetTransactionByHash => JsonRpcRequestData::GetTransactionByHash( + JsonRpcMethod::GetTransactionByHash => ProviderRequestData::GetTransactionByHash( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), JsonRpcMethod::GetTransactionByBlockIdAndIndex => { - JsonRpcRequestData::GetTransactionByBlockIdAndIndex( + ProviderRequestData::GetTransactionByBlockIdAndIndex( serde_json::from_value::( raw_request.params, ) .map_err(error_mapper)?, ) } - JsonRpcMethod::GetTransactionReceipt => JsonRpcRequestData::GetTransactionReceipt( + JsonRpcMethod::GetTransactionReceipt => ProviderRequestData::GetTransactionReceipt( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetClass => JsonRpcRequestData::GetClass( + JsonRpcMethod::GetClass => ProviderRequestData::GetClass( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetClassHashAt => JsonRpcRequestData::GetClassHashAt( + JsonRpcMethod::GetClassHashAt => ProviderRequestData::GetClassHashAt( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetClassAt => JsonRpcRequestData::GetClassAt( + JsonRpcMethod::GetClassAt => ProviderRequestData::GetClassAt( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), JsonRpcMethod::GetBlockTransactionCount => { - JsonRpcRequestData::GetBlockTransactionCount( + ProviderRequestData::GetBlockTransactionCount( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ) } - JsonRpcMethod::Call => JsonRpcRequestData::Call( + JsonRpcMethod::Call => ProviderRequestData::Call( serde_json::from_value::(raw_request.params).map_err(error_mapper)?, ), - JsonRpcMethod::EstimateFee => JsonRpcRequestData::EstimateFee( + JsonRpcMethod::EstimateFee => ProviderRequestData::EstimateFee( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::EstimateMessageFee => JsonRpcRequestData::EstimateMessageFee( + JsonRpcMethod::EstimateMessageFee => ProviderRequestData::EstimateMessageFee( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::BlockNumber => JsonRpcRequestData::BlockNumber( + JsonRpcMethod::BlockNumber => ProviderRequestData::BlockNumber( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::BlockHashAndNumber => JsonRpcRequestData::BlockHashAndNumber( + JsonRpcMethod::BlockHashAndNumber => ProviderRequestData::BlockHashAndNumber( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::ChainId => JsonRpcRequestData::ChainId( + JsonRpcMethod::ChainId => ProviderRequestData::ChainId( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::Syncing => JsonRpcRequestData::Syncing( + JsonRpcMethod::Syncing => ProviderRequestData::Syncing( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetEvents => JsonRpcRequestData::GetEvents( + JsonRpcMethod::GetEvents => ProviderRequestData::GetEvents( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::GetNonce => JsonRpcRequestData::GetNonce( + JsonRpcMethod::GetNonce => ProviderRequestData::GetNonce( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::AddInvokeTransaction => JsonRpcRequestData::AddInvokeTransaction( + JsonRpcMethod::AddInvokeTransaction => ProviderRequestData::AddInvokeTransaction( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::AddDeclareTransaction => JsonRpcRequestData::AddDeclareTransaction( + JsonRpcMethod::AddDeclareTransaction => ProviderRequestData::AddDeclareTransaction( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), JsonRpcMethod::AddDeployAccountTransaction => { - JsonRpcRequestData::AddDeployAccountTransaction( + ProviderRequestData::AddDeployAccountTransaction( serde_json::from_value::( raw_request.params, ) .map_err(error_mapper)?, ) } - JsonRpcMethod::TraceTransaction => JsonRpcRequestData::TraceTransaction( + JsonRpcMethod::TraceTransaction => ProviderRequestData::TraceTransaction( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::SimulateTransactions => JsonRpcRequestData::SimulateTransactions( + JsonRpcMethod::SimulateTransactions => ProviderRequestData::SimulateTransactions( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), - JsonRpcMethod::TraceBlockTransactions => JsonRpcRequestData::TraceBlockTransactions( + JsonRpcMethod::TraceBlockTransactions => ProviderRequestData::TraceBlockTransactions( serde_json::from_value::(raw_request.params) .map_err(error_mapper)?, ), @@ -884,16 +1163,16 @@ impl TryFrom<&JsonRpcError> for StarknetError { fn try_from(value: &JsonRpcError) -> Result { match value.code { - 1 => Ok(StarknetError::FailedToReceiveTransaction), - 20 => Ok(StarknetError::ContractNotFound), - 24 => Ok(StarknetError::BlockNotFound), - 27 => Ok(StarknetError::InvalidTransactionIndex), - 28 => Ok(StarknetError::ClassHashNotFound), - 29 => Ok(StarknetError::TransactionHashNotFound), - 31 => Ok(StarknetError::PageSizeTooBig), - 32 => Ok(StarknetError::NoBlocks), - 33 => Ok(StarknetError::InvalidContinuationToken), - 34 => Ok(StarknetError::TooManyKeysInFilter), + 1 => Ok(Self::FailedToReceiveTransaction), + 20 => Ok(Self::ContractNotFound), + 24 => Ok(Self::BlockNotFound), + 27 => Ok(Self::InvalidTransactionIndex), + 28 => Ok(Self::ClassHashNotFound), + 29 => Ok(Self::TransactionHashNotFound), + 31 => Ok(Self::PageSizeTooBig), + 32 => Ok(Self::NoBlocks), + 33 => Ok(Self::InvalidContinuationToken), + 34 => Ok(Self::TooManyKeysInFilter), 40 => { let data = ContractErrorData::deserialize( value @@ -902,7 +1181,7 @@ impl TryFrom<&JsonRpcError> for StarknetError { .ok_or(JsonRpcErrorConversionError::MissingData)?, ) .map_err(|_| JsonRpcErrorConversionError::DataParsingFailure)?; - Ok(StarknetError::ContractError(data)) + Ok(Self::ContractError(data)) } 41 => { let data = TransactionExecutionErrorData::deserialize( @@ -912,12 +1191,12 @@ impl TryFrom<&JsonRpcError> for StarknetError { .ok_or(JsonRpcErrorConversionError::MissingData)?, ) .map_err(|_| JsonRpcErrorConversionError::DataParsingFailure)?; - Ok(StarknetError::TransactionExecutionError(data)) + Ok(Self::TransactionExecutionError(data)) } - 51 => Ok(StarknetError::ClassAlreadyDeclared), - 52 => Ok(StarknetError::InvalidTransactionNonce), - 53 => Ok(StarknetError::InsufficientMaxFee), - 54 => Ok(StarknetError::InsufficientAccountBalance), + 51 => Ok(Self::ClassAlreadyDeclared), + 52 => Ok(Self::InvalidTransactionNonce), + 53 => Ok(Self::InsufficientMaxFee), + 54 => Ok(Self::InsufficientAccountBalance), 55 => { let data = String::deserialize( value @@ -926,15 +1205,15 @@ impl TryFrom<&JsonRpcError> for StarknetError { .ok_or(JsonRpcErrorConversionError::MissingData)?, ) .map_err(|_| JsonRpcErrorConversionError::DataParsingFailure)?; - Ok(StarknetError::ValidationFailure(data)) + Ok(Self::ValidationFailure(data)) } - 56 => Ok(StarknetError::CompilationFailed), - 57 => Ok(StarknetError::ContractClassSizeIsTooLarge), - 58 => Ok(StarknetError::NonAccount), - 59 => Ok(StarknetError::DuplicateTx), - 60 => Ok(StarknetError::CompiledClassHashMismatch), - 61 => Ok(StarknetError::UnsupportedTxVersion), - 62 => Ok(StarknetError::UnsupportedContractClassVersion), + 56 => Ok(Self::CompilationFailed), + 57 => Ok(Self::ContractClassSizeIsTooLarge), + 58 => Ok(Self::NonAccount), + 59 => Ok(Self::DuplicateTx), + 60 => Ok(Self::CompiledClassHashMismatch), + 61 => Ok(Self::UnsupportedTxVersion), + 62 => Ok(Self::UnsupportedContractClassVersion), 63 => { let data = String::deserialize( value @@ -943,7 +1222,7 @@ impl TryFrom<&JsonRpcError> for StarknetError { .ok_or(JsonRpcErrorConversionError::MissingData)?, ) .map_err(|_| JsonRpcErrorConversionError::DataParsingFailure)?; - Ok(StarknetError::UnexpectedError(data)) + Ok(Self::UnexpectedError(data)) } 10 => { let data = NoTraceAvailableErrorData::deserialize( @@ -953,7 +1232,7 @@ impl TryFrom<&JsonRpcError> for StarknetError { .ok_or(JsonRpcErrorConversionError::MissingData)?, ) .map_err(|_| JsonRpcErrorConversionError::DataParsingFailure)?; - Ok(StarknetError::NoTraceAvailable(data)) + Ok(Self::NoTraceAvailable(data)) } _ => Err(JsonRpcErrorConversionError::UnknownCode), } diff --git a/starknet-providers/src/jsonrpc/transports/http.rs b/starknet-providers/src/jsonrpc/transports/http.rs index bd54d8b4..fb7e6120 100644 --- a/starknet-providers/src/jsonrpc/transports/http.rs +++ b/starknet-providers/src/jsonrpc/transports/http.rs @@ -3,20 +3,30 @@ use log::trace; use reqwest::{Client, Url}; use serde::{de::DeserializeOwned, Serialize}; -use crate::jsonrpc::{transports::JsonRpcTransport, JsonRpcMethod, JsonRpcResponse}; +use crate::{ + jsonrpc::{transports::JsonRpcTransport, JsonRpcMethod, JsonRpcResponse}, + ProviderRequestData, +}; -#[derive(Debug)] +/// A [`JsonRpcTransport`] implementation that uses HTTP connections. +#[derive(Debug, Clone)] pub struct HttpTransport { client: Client, url: Url, headers: Vec<(String, String)>, } +/// Errors using [`HttpTransport`]. #[derive(Debug, thiserror::Error)] #[error(transparent)] pub enum HttpTransportError { + /// HTTP-related errors. Reqwest(reqwest::Error), + /// JSON serialization/deserialization errors. Json(serde_json::Error), + /// Unexpected response ID. + #[error("unexpected response ID: {0}")] + UnexpectedResponseId(u64), } #[derive(Debug, Serialize)] @@ -28,10 +38,15 @@ struct JsonRpcRequest { } impl HttpTransport { + /// Constructs [`HttpTransport`] from a JSON-RPC server URL, using default HTTP client settings. + /// + /// To use custom HTTP settings (e.g. proxy, timeout), use + /// [`new_with_client`](fn.new_with_client) instead. pub fn new(url: impl Into) -> Self { Self::new_with_client(url, Client::new()) } + /// Constructs [`HttpTransport`] from a JSON-RPC server URL and a custom `reqwest` client. pub fn new_with_client(url: impl Into, client: Client) -> Self { Self { client, @@ -40,8 +55,8 @@ impl HttpTransport { } } - /// Consumes the current [HttpTransport] instance and returns a new one with the header - /// appended. Same as calling [add_header]. + /// Consumes the current [`HttpTransport`] instance and returns a new one with the header + /// appended. Same as calling [`add_header`](fn.add_header). pub fn with_header(self, name: String, value: String) -> Self { let mut headers = self.headers; headers.push((name, value)); @@ -88,7 +103,7 @@ impl JsonRpcTransport for HttpTransport { .post(self.url.clone()) .body(request_body) .header("Content-Type", "application/json"); - for (name, value) in self.headers.iter() { + for (name, value) in &self.headers { request = request.header(name, value); } @@ -101,4 +116,67 @@ impl JsonRpcTransport for HttpTransport { Ok(parsed_response) } + + async fn send_requests( + &self, + requests: R, + ) -> Result>, Self::Error> + where + R: AsRef<[ProviderRequestData]> + Send + Sync, + { + let request_bodies = requests + .as_ref() + .iter() + .enumerate() + .map(|(ind, request)| JsonRpcRequest { + id: ind as u64, + jsonrpc: "2.0", + method: request.jsonrpc_method(), + params: request, + }) + .collect::>(); + + let request_count = request_bodies.len(); + + let request_body = serde_json::to_string(&request_bodies).map_err(Self::Error::Json)?; + trace!("Sending request via JSON-RPC: {}", request_body); + + let mut request = self + .client + .post(self.url.clone()) + .body(request_body) + .header("Content-Type", "application/json"); + for (name, value) in &self.headers { + request = request.header(name, value); + } + + let response = request.send().await.map_err(Self::Error::Reqwest)?; + + let response_body = response.text().await.map_err(Self::Error::Reqwest)?; + trace!("Response from JSON-RPC: {}", response_body); + + let parsed_response: Vec> = + serde_json::from_str(&response_body).map_err(Self::Error::Json)?; + + let mut responses: Vec>> = vec![]; + responses.resize(request_bodies.len(), None); + + // Re-order the responses as servers do not maintain order. + for response_item in parsed_response { + let id = match &response_item { + JsonRpcResponse::Success { id, .. } | JsonRpcResponse::Error { id, .. } => { + *id as usize + } + }; + + if id >= request_count { + return Err(HttpTransportError::UnexpectedResponseId(id as u64)); + } + + responses[id] = Some(response_item); + } + + let responses = responses.into_iter().flatten().collect::>(); + Ok(responses) + } } diff --git a/starknet-providers/src/jsonrpc/transports/mod.rs b/starknet-providers/src/jsonrpc/transports/mod.rs index 7b119f74..3c172c11 100644 --- a/starknet-providers/src/jsonrpc/transports/mod.rs +++ b/starknet-providers/src/jsonrpc/transports/mod.rs @@ -3,17 +3,24 @@ use auto_impl::auto_impl; use serde::{de::DeserializeOwned, Serialize}; use std::error::Error; -use crate::jsonrpc::{JsonRpcMethod, JsonRpcResponse}; +use crate::{ + jsonrpc::{JsonRpcMethod, JsonRpcResponse}, + ProviderRequestData, +}; mod http; pub use http::{HttpTransport, HttpTransportError}; +/// Any type that is capable of producing JSON-RPC responses when given JSON-RPC requests. An +/// implementation does not necessarily use the network, but typically does. #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[auto_impl(&, Box, Arc)] pub trait JsonRpcTransport { + /// Possible errors processing requests. type Error: Error + Send + Sync; + /// Sends a JSON-RPC request to retrieve a response. async fn send_request( &self, method: JsonRpcMethod, @@ -22,4 +29,12 @@ pub trait JsonRpcTransport { where P: Serialize + Send + Sync, R: DeserializeOwned; + + /// Sends multiple JSON-RPC requests in parallel. + async fn send_requests( + &self, + requests: R, + ) -> Result>, Self::Error> + where + R: AsRef<[ProviderRequestData]> + Send + Sync; } diff --git a/starknet-providers/src/lib.rs b/starknet-providers/src/lib.rs index 31dde2b2..a8392dd3 100644 --- a/starknet-providers/src/lib.rs +++ b/starknet-providers/src/lib.rs @@ -1,13 +1,23 @@ -#![doc = include_str!("../README.md")] +//! Clients for interacting with Starknet nodes and sequencers. +//! +//! This crate provides the [`Provider`] trait for abstraction over means of accessing the Starknet +//! network. The most commonly used implementation is [`JsonRpcClient`] with +//! [`HttpTransport`](jsonrpc::HttpTransport). + +#![deny(missing_docs)] mod provider; -pub use provider::{Provider, ProviderError}; +pub use provider::{Provider, ProviderError, ProviderRequestData, ProviderResponseData}; +// Sequencer-related functionalities are mostly deprecated so we skip the docs. +/// Module containing types related to the (now deprecated) sequencer gateway client. +#[allow(missing_docs)] pub mod sequencer; pub use sequencer::{ GatewayClientError as SequencerGatewayProviderError, SequencerGatewayProvider, }; +/// Module containing types related to JSON-RPC clients and servers. pub mod jsonrpc; pub use jsonrpc::JsonRpcClient; diff --git a/starknet-providers/src/provider.rs b/starknet-providers/src/provider.rs index 0ee4958a..f513e52c 100644 --- a/starknet-providers/src/provider.rs +++ b/starknet-providers/src/provider.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; use auto_impl::auto_impl; +use serde::Serialize; use starknet_core::types::{ - BlockHashAndNumber, BlockId, BroadcastedDeclareTransaction, + requests::*, BlockHashAndNumber, BlockId, BroadcastedDeclareTransaction, BroadcastedDeployAccountTransaction, BroadcastedInvokeTransaction, BroadcastedTransaction, ContractClass, DeclareTransactionResult, DeployAccountTransactionResult, EventFilter, EventsPage, FeeEstimate, Felt, FunctionCall, InvokeTransactionResult, @@ -12,14 +13,24 @@ use starknet_core::types::{ }; use std::{any::Any, error::Error, fmt::Debug}; +/// A generic interface for any type allowing communication with a Starknet network. +/// +/// Historically, the only official way to access the network is through the sequencer gateway, +/// implemented by [`SequencerGatewayProvider`](crate::sequencer::SequencerGatewayProvider), which +/// has since been deprecated. Currently, the recommended way of accessing the network is via the +/// JSON-RPC specification, implemented with [`JsonRpcClient`](crate::jsonrpc::JsonRpcClient). +/// +/// The legacy [`SequencerGatewayProvider`](crate::sequencer::SequencerGatewayProvider) still +/// implements this trait for backward compatibility reasons, but most of its methods no longer work +/// in practice, as public sequencer servers have generally block access to most methods. #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[auto_impl(&, Box, Arc)] pub trait Provider { - /// Returns the version of the Starknet JSON-RPC specification being used + /// Returns the version of the Starknet JSON-RPC specification being used. async fn spec_version(&self) -> Result; - /// Get block information with transaction hashes given the block id + /// Gets block information with transaction hashes given the block id. async fn get_block_with_tx_hashes( &self, block_id: B, @@ -27,7 +38,7 @@ pub trait Provider { where B: AsRef + Send + Sync; - /// Get block information with full transactions given the block id + /// Gets block information with full transactions given the block id. async fn get_block_with_txs( &self, block_id: B, @@ -35,7 +46,7 @@ pub trait Provider { where B: AsRef + Send + Sync; - /// Get block information with full transactions and receipts given the block id + /// Gets block information with full transactions and receipts given the block id. async fn get_block_with_receipts( &self, block_id: B, @@ -43,7 +54,7 @@ pub trait Provider { where B: AsRef + Send + Sync; - /// Get the information about the result of executing the requested block + /// Gets the information about the result of executing the requested block. async fn get_state_update( &self, block_id: B, @@ -51,7 +62,7 @@ pub trait Provider { where B: AsRef + Send + Sync; - /// Get the value of the storage at the given address and key + /// Gets the value of the storage at the given address and key. async fn get_storage_at( &self, contract_address: A, @@ -63,8 +74,8 @@ pub trait Provider { K: AsRef + Send + Sync, B: AsRef + Send + Sync; - /// Gets the transaction status (possibly reflecting that the tx is still in - /// the mempool, or dropped from it) + /// Gets the transaction status (possibly reflecting that the tx is still in the mempool, or + /// dropped from it). async fn get_transaction_status( &self, transaction_hash: H, @@ -72,7 +83,7 @@ pub trait Provider { where H: AsRef + Send + Sync; - /// Get the details and status of a submitted transaction + /// Gets the details and status of a submitted transaction. async fn get_transaction_by_hash( &self, transaction_hash: H, @@ -80,7 +91,7 @@ pub trait Provider { where H: AsRef + Send + Sync; - /// Get the details of a transaction by a given block id and index + /// Gets the details of a transaction by a given block id and index. async fn get_transaction_by_block_id_and_index( &self, block_id: B, @@ -89,7 +100,7 @@ pub trait Provider { where B: AsRef + Send + Sync; - /// Get the details of a transaction by a given block number and index + /// Gets the details of a transaction by a given block number and index. async fn get_transaction_receipt( &self, transaction_hash: H, @@ -97,7 +108,7 @@ pub trait Provider { where H: AsRef + Send + Sync; - /// Get the contract class definition in the given block associated with the given hash + /// Gets the contract class definition in the given block associated with the given hash. async fn get_class( &self, block_id: B, @@ -107,7 +118,8 @@ pub trait Provider { B: AsRef + Send + Sync, H: AsRef + Send + Sync; - /// Get the contract class hash in the given block for the contract deployed at the given address + /// Gets the contract class hash in the given block for the contract deployed at the given + /// address. async fn get_class_hash_at( &self, block_id: B, @@ -117,7 +129,7 @@ pub trait Provider { B: AsRef + Send + Sync, A: AsRef + Send + Sync; - /// Get the contract class definition in the given block at the given address + /// Gets the contract class definition in the given block at the given address. async fn get_class_at( &self, block_id: B, @@ -127,18 +139,18 @@ pub trait Provider { B: AsRef + Send + Sync, A: AsRef + Send + Sync; - /// Get the number of transactions in a block given a block id + /// Gets the number of transactions in a block given a block id. async fn get_block_transaction_count(&self, block_id: B) -> Result where B: AsRef + Send + Sync; - /// Call a starknet function without creating a Starknet transaction + /// Calls a starknet function without creating a Starknet transaction. async fn call(&self, request: R, block_id: B) -> Result, ProviderError> where R: AsRef + Send + Sync, B: AsRef + Send + Sync; - /// Estimate the fee for a given Starknet transaction + /// Estimates the fee for a given Starknet transaction. async fn estimate_fee( &self, request: R, @@ -150,6 +162,7 @@ pub trait Provider { S: AsRef<[SimulationFlagForEstimateFee]> + Send + Sync, B: AsRef + Send + Sync; + /// Estimates the fee for sending an L1-to-L2 message. async fn estimate_message_fee( &self, message: M, @@ -159,19 +172,19 @@ pub trait Provider { M: AsRef + Send + Sync, B: AsRef + Send + Sync; - /// Get the most recent accepted block number + /// Gets the most recent accepted block number. async fn block_number(&self) -> Result; - /// Get the most recent accepted block hash and number + /// Gets the most recent accepted block hash and number. async fn block_hash_and_number(&self) -> Result; - /// Return the currently configured Starknet chain id + /// Returns the currently configured Starknet chain id. async fn chain_id(&self) -> Result; - /// Returns an object about the sync status, or false if the node is not synching + /// Returns an object about the sync status, or false if the node is not synching. async fn syncing(&self) -> Result; - /// Returns all events matching the given filter + /// Returns all events matching the given filter. async fn get_events( &self, filter: EventFilter, @@ -179,7 +192,7 @@ pub trait Provider { chunk_size: u64, ) -> Result; - /// Get the nonce associated with the given address in the given block + /// Gets the nonce associated with the given address in the given block. async fn get_nonce( &self, block_id: B, @@ -189,7 +202,7 @@ pub trait Provider { B: AsRef + Send + Sync, A: AsRef + Send + Sync; - /// Submit a new transaction to be added to the chain + /// Submits a new transaction to be added to the chain. async fn add_invoke_transaction( &self, invoke_transaction: I, @@ -197,7 +210,7 @@ pub trait Provider { where I: AsRef + Send + Sync; - /// Submit a new transaction to be added to the chain + /// Submits a new transaction to be added to the chain. async fn add_declare_transaction( &self, declare_transaction: D, @@ -205,7 +218,7 @@ pub trait Provider { where D: AsRef + Send + Sync; - /// Submit a new deploy account transaction + /// Submits a new deploy account transaction. async fn add_deploy_account_transaction( &self, deploy_account_transaction: D, @@ -213,8 +226,8 @@ pub trait Provider { where D: AsRef + Send + Sync; - /// For a given executed transaction, return the trace of its execution, including internal - /// calls + /// For a given executed transaction, returns the trace of its execution, including internal + /// calls. async fn trace_transaction( &self, transaction_hash: H, @@ -222,12 +235,13 @@ pub trait Provider { where H: AsRef + Send + Sync; - /// Simulate a given sequence of transactions on the requested state, and generate the execution - /// traces. Note that some of the transactions may revert, in which case no error is thrown, but - /// revert details can be seen on the returned trace object. . Note that some of the - /// transactions may revert, this will be reflected by the revert_error property in the trace. - /// Other types of failures (e.g. unexpected error or failure in the validation phase) will - /// result in TRANSACTION_EXECUTION_ERROR. + /// Simulates a given sequence of transactions on the requested state, and generate the + /// execution traces. Note that some of the transactions may revert, in which case no error is + /// thrown, but revert details can be seen on the returned trace object. + /// + /// Note that some of the transactions may revert, this will be reflected by the `revert_error` + /// property in the trace. Other types of failures (e.g. unexpected error or failure in the + /// validation phase) will result in `TRANSACTION_EXECUTION_ERROR`. async fn simulate_transactions( &self, block_id: B, @@ -239,7 +253,7 @@ pub trait Provider { T: AsRef<[BroadcastedTransaction]> + Send + Sync, S: AsRef<[SimulationFlag]> + Send + Sync; - /// Retrieve traces for all transactions in the given block. + /// Retrieves traces for all transactions in the given block. async fn trace_block_transactions( &self, block_id: B, @@ -247,7 +261,16 @@ pub trait Provider { where B: AsRef + Send + Sync; - /// Same as [estimate_fee], but only with one estimate. + /// Sends multiple requests in parallel. The function call fails if any of the requests fails. + /// Implementations must guarantee that responses follow the exact order as the requests. + async fn batch_requests( + &self, + requests: R, + ) -> Result, ProviderError> + where + R: AsRef<[ProviderRequestData]> + Send + Sync; + + /// Same as [`estimate_fee`](fn.estimate_fee), but only with one estimate. async fn estimate_fee_single( &self, request: R, @@ -271,7 +294,7 @@ pub trait Provider { } } - /// Same as [simulate_transactions], but only with one simulation. + /// Same as [`simulate_transactions`](fn.simulate_transactions), but only with one simulation. async fn simulate_transaction( &self, block_id: B, @@ -302,23 +325,166 @@ pub trait Provider { /// Trait for implementation-specific error type. These errors are irrelevant in most cases, /// assuming that users typically care more about the specifics of RPC errors instead of the -/// underlying transport. Therefore, it makes little sense to bloat [ProviderError] with a generic +/// underlying transport. Therefore, it makes little sense to bloat [`ProviderError`] with a generic /// parameter just for these errors. Instead, they're erased to this trait object. /// -/// This trait is used instead of a plain [std::error::Error] to allow downcasting, in case access +/// This trait is used instead of a plain [`std::error::Error`] to allow downcasting, in case access /// to the specific error type is indeed desired. This is achieved with the `as_any()` method. pub trait ProviderImplError: Error + Debug + Send + Sync { fn as_any(&self) -> &dyn Any; } +/// Errors using any [`Provider`] implementation. This type is deliberately not made generic such +/// that: +/// +/// - the [`Provider`] trait itself can be boxed; +/// - error handling is easier. +/// +/// As a downside, the [`Other`](ProviderError::Other) variant contains a boxed implementation- +/// specific error. It's generally expected that users of [`Provider`] would not need to care about +/// these errors, but in the case where they do, it's slightly harder to access than if generics are +/// used instead. #[derive(Debug, thiserror::Error)] pub enum ProviderError { + /// A Starknet-related error, usually regarding the state or transaction. #[error(transparent)] StarknetError(StarknetError), + /// The request fails as the client is rate-limited. #[error("Request rate limited")] RateLimited, + /// When estimating fees for or simulating a single transaction, the server unexpectedly returns + /// data for zero or more than one transactions. #[error("Array length mismatch")] ArrayLengthMismatch, + /// Boxed implementation-specific errors. #[error("{0}")] Other(Box), } + +/// Typed request data for [`Provider`] requests. +#[derive(Debug, Clone, Serialize)] +#[serde(untagged)] +pub enum ProviderRequestData { + /// Request data for `starknet_specVersion`. + SpecVersion(SpecVersionRequest), + /// Request data for `starknet_getBlockWithTxHashes`. + GetBlockWithTxHashes(GetBlockWithTxHashesRequest), + /// Request data for `starknet_getBlockWithTxs`. + GetBlockWithTxs(GetBlockWithTxsRequest), + /// Request data for `starknet_getBlockWithReceipts`. + GetBlockWithReceipts(GetBlockWithReceiptsRequest), + /// Request data for `starknet_getStateUpdate`. + GetStateUpdate(GetStateUpdateRequest), + /// Request data for `starknet_getStorageAt`. + GetStorageAt(GetStorageAtRequest), + /// Request data for `starknet_getTransactionStatus`. + GetTransactionStatus(GetTransactionStatusRequest), + /// Request data for `starknet_getTransactionByHash`. + GetTransactionByHash(GetTransactionByHashRequest), + /// Request data for `starknet_getTransactionByBlockIdAndIndex`. + GetTransactionByBlockIdAndIndex(GetTransactionByBlockIdAndIndexRequest), + /// Request data for `starknet_getTransactionReceipt`. + GetTransactionReceipt(GetTransactionReceiptRequest), + /// Request data for `starknet_getClass`. + GetClass(GetClassRequest), + /// Request data for `starknet_getClassHashAt`. + GetClassHashAt(GetClassHashAtRequest), + /// Request data for `starknet_getClassAt`. + GetClassAt(GetClassAtRequest), + /// Request data for `starknet_getBlockTransactionCount`. + GetBlockTransactionCount(GetBlockTransactionCountRequest), + /// Request data for `starknet_call`. + Call(CallRequest), + /// Request data for `starknet_estimateFee`. + EstimateFee(EstimateFeeRequest), + /// Request data for `starknet_estimateMessageFee`. + EstimateMessageFee(EstimateMessageFeeRequest), + /// Request data for `starknet_blockNumber`. + BlockNumber(BlockNumberRequest), + /// Request data for `starknet_blockHashAndNumber`. + BlockHashAndNumber(BlockHashAndNumberRequest), + /// Request data for `starknet_chainId`. + ChainId(ChainIdRequest), + /// Request data for `starknet_syncing`. + Syncing(SyncingRequest), + /// Request data for `starknet_getEvents`. + GetEvents(GetEventsRequest), + /// Request data for `starknet_getNonce`. + GetNonce(GetNonceRequest), + /// Request data for `starknet_addInvokeTransaction`. + AddInvokeTransaction(AddInvokeTransactionRequest), + /// Request data for `starknet_addDeclareTransaction`. + AddDeclareTransaction(AddDeclareTransactionRequest), + /// Request data for `starknet_addDeployAccountTransaction`. + AddDeployAccountTransaction(AddDeployAccountTransactionRequest), + /// Request data for `starknet_traceTransaction`. + TraceTransaction(TraceTransactionRequest), + /// Request data for `starknet_simulateTransactions`. + SimulateTransactions(SimulateTransactionsRequest), + /// Request data for `starknet_traceBlockTransactions`. + TraceBlockTransactions(TraceBlockTransactionsRequest), +} + +/// Typed response data for [`Provider`] responses. +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Clone)] +pub enum ProviderResponseData { + /// Response data for `starknet_specVersion`. + SpecVersion(String), + /// Response data for `starknet_getBlockWithTxHashes`. + GetBlockWithTxHashes(MaybePendingBlockWithTxHashes), + /// Response data for `starknet_getBlockWithTxs`. + GetBlockWithTxs(MaybePendingBlockWithTxs), + /// Response data for `starknet_getBlockWithReceipts`. + GetBlockWithReceipts(MaybePendingBlockWithReceipts), + /// Response data for `starknet_getStateUpdate`. + GetStateUpdate(MaybePendingStateUpdate), + /// Response data for `starknet_getStorageAt`. + GetStorageAt(Felt), + /// Response data for `starknet_getTransactionStatus`. + GetTransactionStatus(TransactionStatus), + /// Response data for `starknet_getTransactionByHash`. + GetTransactionByHash(Transaction), + /// Response data for `starknet_getTransactionByBlockIdAndIndex`. + GetTransactionByBlockIdAndIndex(Transaction), + /// Response data for `starknet_getTransactionReceipt`. + GetTransactionReceipt(TransactionReceiptWithBlockInfo), + /// Response data for `starknet_getClass`. + GetClass(ContractClass), + /// Response data for `starknet_getClassHashAt`. + GetClassHashAt(Felt), + /// Response data for `starknet_getClassAt`. + GetClassAt(ContractClass), + /// Response data for `starknet_getBlockTransactionCount`. + GetBlockTransactionCount(u64), + /// Response data for `starknet_call`. + Call(Vec), + /// Response data for `starknet_estimateFee`. + EstimateFee(Vec), + /// Response data for `starknet_estimateMessageFee`. + EstimateMessageFee(FeeEstimate), + /// Response data for `starknet_blockNumber`. + BlockNumber(u64), + /// Response data for `starknet_blockHashAndNumber`. + BlockHashAndNumber(BlockHashAndNumber), + /// Response data for `starknet_chainId`. + ChainId(Felt), + /// Response data for `starknet_syncing`. + Syncing(SyncStatusType), + /// Response data for `starknet_getEvents`. + GetEvents(EventsPage), + /// Response data for `starknet_getNonce`. + GetNonce(Felt), + /// Response data for `starknet_addInvokeTransaction`. + AddInvokeTransaction(InvokeTransactionResult), + /// Response data for `starknet_addDeclareTransaction`. + AddDeclareTransaction(DeclareTransactionResult), + /// Response data for `starknet_addDeployAccountTransaction`. + AddDeployAccountTransaction(DeployAccountTransactionResult), + /// Response data for `starknet_traceTransaction`. + TraceTransaction(TransactionTrace), + /// Response data for `starknet_simulateTransactions`. + SimulateTransactions(Vec), + /// Response data for `starknet_traceBlockTransactions`. + TraceBlockTransactions(Vec), +} diff --git a/starknet-providers/src/sequencer/mod.rs b/starknet-providers/src/sequencer/mod.rs index 8e83a11e..233a6eae 100644 --- a/starknet-providers/src/sequencer/mod.rs +++ b/starknet-providers/src/sequencer/mod.rs @@ -37,19 +37,21 @@ pub enum GatewayClientError { /// JSON serialization/deserialization error #[error(transparent)] Serde(SerdeJsonError), - /// Sequencer error responses not parsable into [StarknetError] + /// Sequencer error responses not parsable into [`StarknetError`] #[error(transparent)] SequencerError(SequencerError), - /// Method is not supported (only when using as [Provider]) + /// Method is not supported (only when using as [`Provider`](crate::Provider)) #[error("method not supported")] MethodNotSupported, - /// Model conversion error (only when using as [Provider]) + /// Model conversion error (only when using as [`Provider`](crate::Provider)) #[error("unable to convert gateway models to jsonrpc types")] ModelConversionError, - /// Simulating multiple transactions is not supported (only when using as [Provider]) + /// Simulating multiple transactions is not supported (only when using as + /// [`Provider`](crate::Provider)) #[error("simulating multiple transactions not supported")] BulkSimulationNotSupported, - /// At least one of the simulation flags is not supported (only when using as [Provider]) + /// At least one of the simulation flags is not supported (only when using as + /// [`Provider`](crate::Provider)) #[error("unsupported simulation flag")] UnsupportedSimulationFlag, } @@ -139,8 +141,8 @@ impl SequencerGatewayProvider { ) } - /// Consumes the current [SequencerGatewayProvider] instance and returns a new one with the - /// header appended. Same as calling [add_header]. + /// Consumes the current [`SequencerGatewayProvider`] instance and returns a new one with the + /// header appended. Same as calling [`add_header`](fn.add_header). pub fn with_header(self, name: String, value: String) -> Self { let mut headers = self.headers; headers.push((name, value)); @@ -174,9 +176,6 @@ enum RawFieldElementResponse { SequencerError(SequencerError), } -#[derive(Deserialize)] -struct EmptyObject {} - impl SequencerGatewayProvider { fn extend_gateway_url(&self, segment: &str) -> Url { let mut url = self.gateway_url.clone(); @@ -197,7 +196,7 @@ impl SequencerGatewayProvider { trace!("Sending GET request to sequencer API ({})", url); let mut request = self.client.get(url); - for (name, value) in self.headers.iter() { + for (name, value) in &self.headers { request = request.header(name, value); } @@ -231,7 +230,7 @@ impl SequencerGatewayProvider { .post(url) .header("Content-Type", "application/json") .body(request_body); - for (name, value) in self.headers.iter() { + for (name, value) in &self.headers { request = request.header(name, value); } @@ -316,6 +315,24 @@ impl SequencerGatewayProvider { .into() } + #[deprecated( + note = "Sequencer-specific functions are deprecated. Use it via the Provider trait instead." + )] + pub async fn get_state_update_with_block( + &self, + block_identifier: BlockId, + ) -> Result { + let mut request_url = self.extend_feeder_gateway_url("get_state_update"); + append_block_id(&mut request_url, block_identifier); + request_url + .query_pairs_mut() + .append_pair("includeBlock", "true"); + + self.send_get_request::>(request_url) + .await? + .into() + } + #[deprecated( note = "Sequencer-specific functions are deprecated. Use it via the Provider trait instead." )] @@ -460,29 +477,27 @@ impl From for ProviderError { fn from(value: SequencerError) -> Self { let matching_code = match value.code { ErrorCode::BlockNotFound => Some(StarknetError::BlockNotFound), - ErrorCode::EntryPointNotFoundInContract => None, - ErrorCode::InvalidProgram => None, - ErrorCode::TransactionFailed => { + ErrorCode::EntryPointNotFoundInContract + | ErrorCode::InvalidContractClass + | ErrorCode::DeprecatedEndpoint + | ErrorCode::MalformedRequest + | ErrorCode::InvalidProgram => None, + ErrorCode::TransactionFailed | ErrorCode::ValidateFailure => { Some(StarknetError::ValidationFailure(value.message.clone())) } - ErrorCode::TransactionNotFound => Some(StarknetError::ContractNotFound), - ErrorCode::UninitializedContract => Some(StarknetError::ContractNotFound), - ErrorCode::MalformedRequest => None, + ErrorCode::TransactionNotFound | ErrorCode::UninitializedContract => { + Some(StarknetError::ContractNotFound) + } ErrorCode::UndeclaredClass => Some(StarknetError::ClassHashNotFound), ErrorCode::InvalidTransactionNonce => Some(StarknetError::InvalidTransactionNonce), - ErrorCode::ValidateFailure => { - Some(StarknetError::ValidationFailure(value.message.clone())) - } ErrorCode::ClassAlreadyDeclared => Some(StarknetError::ClassAlreadyDeclared), ErrorCode::CompilationFailed => Some(StarknetError::CompilationFailed), ErrorCode::InvalidCompiledClassHash => Some(StarknetError::CompiledClassHashMismatch), ErrorCode::DuplicatedTransaction => Some(StarknetError::DuplicateTx), - ErrorCode::InvalidContractClass => None, - ErrorCode::DeprecatedEndpoint => None, }; match matching_code { - Some(code) => ProviderError::StarknetError(code), + Some(code) => Self::StarknetError(code), None => GatewayClientError::SequencerError(value).into(), } } @@ -524,10 +539,10 @@ where { let temp_value = serde_json::Value::deserialize(deserializer)?; if let Ok(value) = T::deserialize(&temp_value) { - return Ok(GatewayResponse::Data(value)); + return Ok(Self::Data(value)); } if let Ok(value) = SequencerError::deserialize(&temp_value) { - return Ok(GatewayResponse::SequencerError(value)); + return Ok(Self::SequencerError(value)); } Err(serde::de::Error::custom( "data did not match any variant of enum GatewayResponse", @@ -568,9 +583,7 @@ mod tests { for raw in [ include_str!("../../test-data/raw_gateway_responses/get_class_by_hash/1_cairo_0.txt"), include_str!("../../test-data/raw_gateway_responses/get_class_by_hash/3_cairo_1.txt"), - ] - .into_iter() - { + ] { serde_json::from_str::>(raw).unwrap(); } } diff --git a/starknet-providers/src/sequencer/models/contract.rs b/starknet-providers/src/sequencer/models/contract.rs index 04584d51..0ef6a2e6 100644 --- a/starknet-providers/src/sequencer/models/contract.rs +++ b/starknet-providers/src/sequencer/models/contract.rs @@ -91,7 +91,7 @@ impl CompressedSierraClass { let compressed_program = gzip_encoder.finish().map_err(DecompressProgramError::Io)?; - Ok(CompressedSierraClass { + Ok(Self { sierra_program: compressed_program, contract_class_version: flattened_class.contract_class_version.clone(), entry_points_by_type: flattened_class.entry_points_by_type.clone(), diff --git a/starknet-providers/src/sequencer/models/conversions.rs b/starknet-providers/src/sequencer/models/conversions.rs index 8e2c6efe..6af9f96a 100644 --- a/starknet-providers/src/sequencer/models/conversions.rs +++ b/starknet-providers/src/sequencer/models/conversions.rs @@ -604,8 +604,9 @@ impl TryFrom for core::TransactionFinalityStatus { fn try_from(value: TransactionFinalityStatus) -> Result { match value { - TransactionFinalityStatus::NotReceived => Err(ConversionError), - TransactionFinalityStatus::Received => Err(ConversionError), + TransactionFinalityStatus::NotReceived | TransactionFinalityStatus::Received => { + Err(ConversionError) + } TransactionFinalityStatus::AcceptedOnL2 => Ok(Self::AcceptedOnL2), TransactionFinalityStatus::AcceptedOnL1 => Ok(Self::AcceptedOnL1), } @@ -1209,7 +1210,7 @@ fn convert_execution_result( } } -fn convert_legacy_entry_point( +const fn convert_legacy_entry_point( value: core::LegacyContractEntryPoint, ) -> contract_legacy::RawLegacyEntryPoint { // WARNING: this causes pre-0.11.0 contract declaration to fail due to `offset` issue diff --git a/starknet-providers/src/sequencer/models/mod.rs b/starknet-providers/src/sequencer/models/mod.rs index 3e269958..c1c0922e 100644 --- a/starknet-providers/src/sequencer/models/mod.rs +++ b/starknet-providers/src/sequencer/models/mod.rs @@ -46,7 +46,7 @@ mod contract; pub use contract::{CompressedLegacyContractClass, DeployedClass}; pub mod state_update; -pub use state_update::StateUpdate; +pub use state_update::{StateUpdate, StateUpdateWithBlock}; pub mod trace; pub use trace::{BlockTraces, TransactionTrace}; diff --git a/starknet-providers/src/sequencer/models/serde_impls.rs b/starknet-providers/src/sequencer/models/serde_impls.rs index f54d344f..92eaae14 100644 --- a/starknet-providers/src/sequencer/models/serde_impls.rs +++ b/starknet-providers/src/sequencer/models/serde_impls.rs @@ -17,10 +17,10 @@ pub(crate) mod u64_hex { deserializer.deserialize_any(U64HexVisitor) } - impl<'de> Visitor<'de> for U64HexVisitor { + impl Visitor<'_> for U64HexVisitor { type Value = u64; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "string") } @@ -53,10 +53,10 @@ pub(crate) mod u128_hex { deserializer.deserialize_any(U128HexVisitor) } - impl<'de> Visitor<'de> for U128HexVisitor { + impl Visitor<'_> for U128HexVisitor { type Value = u128; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "string") } @@ -92,10 +92,10 @@ pub(crate) mod u64_hex_opt { deserializer.deserialize_any(U64HexOptVisitor) } - impl<'de> Visitor<'de> for U64HexOptVisitor { + impl Visitor<'_> for U64HexOptVisitor { type Value = Option; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "null or string") } diff --git a/starknet-providers/src/sequencer/models/state_update.rs b/starknet-providers/src/sequencer/models/state_update.rs index 0447b96c..c0e56316 100644 --- a/starknet-providers/src/sequencer/models/state_update.rs +++ b/starknet-providers/src/sequencer/models/state_update.rs @@ -3,6 +3,16 @@ use serde_with::serde_as; use starknet_core::{serde::unsigned_field_element::UfeHex, types::Felt}; use std::collections::HashMap; +use super::Block; + +#[serde_as] +#[derive(Debug, Deserialize)] +#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] +pub struct StateUpdateWithBlock { + pub state_update: StateUpdate, + pub block: Block, +} + #[serde_as] #[derive(Debug, Deserialize)] #[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))] diff --git a/starknet-providers/src/sequencer/models/transaction.rs b/starknet-providers/src/sequencer/models/transaction.rs index 388ace7d..da14f044 100644 --- a/starknet-providers/src/sequencer/models/transaction.rs +++ b/starknet-providers/src/sequencer/models/transaction.rs @@ -246,13 +246,13 @@ pub enum DataAvailabilityMode { struct DataAvailabilityModeVisitor; impl TransactionType { - pub fn transaction_hash(&self) -> Felt { + pub const fn transaction_hash(&self) -> Felt { match self { - TransactionType::Declare(inner) => inner.transaction_hash, - TransactionType::Deploy(inner) => inner.transaction_hash, - TransactionType::DeployAccount(inner) => inner.transaction_hash, - TransactionType::InvokeFunction(inner) => inner.transaction_hash, - TransactionType::L1Handler(inner) => inner.transaction_hash, + Self::Declare(inner) => inner.transaction_hash, + Self::Deploy(inner) => inner.transaction_hash, + Self::DeployAccount(inner) => inner.transaction_hash, + Self::InvokeFunction(inner) => inner.transaction_hash, + Self::L1Handler(inner) => inner.transaction_hash, } } } @@ -278,10 +278,10 @@ impl<'de> Deserialize<'de> for DataAvailabilityMode { } } -impl<'de> Visitor<'de> for DataAvailabilityModeVisitor { +impl Visitor<'_> for DataAvailabilityModeVisitor { type Value = DataAvailabilityMode; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(formatter, "integer") } diff --git a/starknet-providers/src/sequencer/provider.rs b/starknet-providers/src/sequencer/provider.rs index b6b44bcd..b046838b 100644 --- a/starknet-providers/src/sequencer/provider.rs +++ b/starknet-providers/src/sequencer/provider.rs @@ -17,7 +17,7 @@ use starknet_core::types::{ use crate::{ provider::ProviderImplError, sequencer::{models::conversions::ConversionError, GatewayClientError}, - Provider, ProviderError, SequencerGatewayProvider, + Provider, ProviderError, ProviderRequestData, ProviderResponseData, SequencerGatewayProvider, }; use super::models::TransactionFinalityStatus; @@ -113,7 +113,10 @@ impl Provider for SequencerGatewayProvider { .await?; // `NotReceived` is not a valid status for JSON-RPC. It's an error. - if let Some(TransactionFinalityStatus::NotReceived) = &status.finality_status { + if matches!( + &status.finality_status, + Some(TransactionFinalityStatus::NotReceived) + ) { return Err(ProviderError::StarknetError( StarknetError::TransactionHashNotFound, )); @@ -411,6 +414,20 @@ impl Provider for SequencerGatewayProvider { GatewayClientError::MethodNotSupported, ))) } + + async fn batch_requests( + &self, + requests: R, + ) -> Result, ProviderError> + where + R: AsRef<[ProviderRequestData]> + Send + Sync, + { + // Not implemented for now. It's technically possible to simulate this by running multiple + // requests in parallel. + Err(ProviderError::Other(Box::new( + GatewayClientError::MethodNotSupported, + ))) + } } impl ProviderImplError for GatewayClientError { diff --git a/starknet-providers/tests/jsonrpc.rs b/starknet-providers/tests/jsonrpc.rs index bddd2ab4..ba813fae 100644 --- a/starknet-providers/tests/jsonrpc.rs +++ b/starknet-providers/tests/jsonrpc.rs @@ -1,5 +1,6 @@ use starknet_core::{ types::{ + requests::{CallRequest, GetBlockTransactionCountRequest}, BlockId, BlockTag, BroadcastedInvokeTransaction, BroadcastedInvokeTransactionV1, BroadcastedTransaction, ContractClass, DeclareTransaction, DeployAccountTransaction, EthAddress, EventFilter, ExecuteInvocation, ExecutionResult, Felt, FunctionCall, @@ -12,13 +13,13 @@ use starknet_core::{ }; use starknet_providers::{ jsonrpc::{HttpTransport, JsonRpcClient}, - Provider, ProviderError, + Provider, ProviderError, ProviderRequestData, ProviderResponseData, }; use url::Url; fn create_jsonrpc_client() -> JsonRpcClient { let rpc_url = std::env::var("STARKNET_RPC") - .unwrap_or("https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_7".into()); + .unwrap_or_else(|_| "https://pathfinder.rpc.sepolia.starknet.rs/rpc/v0_7".into()); JsonRpcClient::new(HttpTransport::new(Url::parse(&rpc_url).unwrap())) } @@ -873,6 +874,48 @@ async fn jsonrpc_trace_deploy_account() { } } +#[tokio::test] +async fn jsonrpc_batch() { + let rpc_client = create_jsonrpc_client(); + + let responses = rpc_client + .batch_requests([ + ProviderRequestData::GetBlockTransactionCount(GetBlockTransactionCountRequest { + block_id: BlockId::Number(20_000), + }), + ProviderRequestData::Call(CallRequest { + request: FunctionCall { + contract_address: Felt::from_hex( + "049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + ) + .unwrap(), + entry_point_selector: get_selector_from_name("balanceOf").unwrap(), + calldata: vec![Felt::from_hex( + "03f47d3911396b6d579fd7848cf576286ab6f96dda977915d6c7b10f3dd2315b", + ) + .unwrap()], + }, + block_id: BlockId::Tag(BlockTag::Latest), + }), + ]) + .await + .unwrap(); + + match &responses[0] { + ProviderResponseData::GetBlockTransactionCount(count) => { + assert_eq!(*count, 6); + } + _ => panic!("unexpected response type"), + } + + match &responses[1] { + ProviderResponseData::Call(eth_balance) => { + assert!(eth_balance[0] > Felt::ZERO); + } + _ => panic!("unexpected response type"), + } +} + // NOTE: `addXxxxTransaction` methods are harder to test here since they require signatures. These // are integration tests anyways, so we might as well just leave the job to th tests in // `starknet-accounts`. diff --git a/starknet-signers/Cargo.toml b/starknet-signers/Cargo.toml index 75467e7c..4192307a 100644 --- a/starknet-signers/Cargo.toml +++ b/starknet-signers/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starknet-signers" -version = "0.9.0" +version = "0.10.0" authors = ["Jonathan LEI "] license = "MIT OR Apache-2.0" edition = "2021" @@ -13,19 +13,18 @@ Starknet signer implementations keywords = ["ethereum", "starknet", "web3"] [dependencies] -starknet-core = { version = "0.11.1", path = "../starknet-core" } -starknet-crypto = { version = "0.7.0", path = "../starknet-crypto" } +starknet-core = { version = "0.12.0", path = "../starknet-core" } +starknet-crypto = { version = "0.7.3", path = "../starknet-crypto" } async-trait = "0.1.68" auto_impl = "1.0.1" thiserror = "1.0.40" crypto-bigint = { version = "0.5.1", default-features = false } rand = { version = "0.8.5", features = ["std_rng"] } coins-bip32 = { version = "0.11.1", optional = true } +coins-ledger = { version = "0.12.0", default-features = false, optional = true } +semver = { version = "1.0.23", optional = true } -# Using a fork until https://github.com/summa-tx/coins/issues/137 is fixed -coins-ledger = { git = "https://github.com/xJonathanLEI/coins", rev = "0e3be5db0b18b683433de6b666556b99c726e785", default-features = false, optional = true } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] eth-keystore = { version = "0.5.0", default-features = false } [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -37,4 +36,7 @@ wasm-bindgen-test = "0.3.34" [features] default = [] -ledger = ["coins-bip32", "coins-ledger"] +ledger = ["coins-bip32", "coins-ledger", "semver"] + +[lints] +workspace = true diff --git a/starknet-signers/src/key_pair.rs b/starknet-signers/src/key_pair.rs index 31a7cefe..e89f52da 100644 --- a/starknet-signers/src/key_pair.rs +++ b/starknet-signers/src/key_pair.rs @@ -6,23 +6,29 @@ use starknet_core::{ }; use starknet_crypto::get_public_key; +/// A ECDSA signing (private) key on the STARK curve. #[derive(Debug, Clone)] pub struct SigningKey { secret_scalar: Felt, } +/// A ECDSA verifying (public) key on the STARK curve. #[derive(Debug, Clone)] pub struct VerifyingKey { scalar: Felt, } -#[cfg(not(target_arch = "wasm32"))] +/// Errors using an encrypted JSON keystore. +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] #[derive(Debug, thiserror::Error)] pub enum KeystoreError { + /// The file path is invalid. #[error("invalid path")] InvalidPath, + /// The decrypted secret scalar is not a valid private key. #[error("invalid decrypted secret scalar")] InvalidScalar, + /// Upstream `eth-keystore` error propagated. #[error(transparent)] Inner(eth_keystore::KeystoreError), } @@ -47,12 +53,13 @@ impl SigningKey { Self { secret_scalar } } - pub fn from_secret_scalar(secret_scalar: Felt) -> Self { + /// Constructs [`SigningKey`] directly from a secret scalar. + pub const fn from_secret_scalar(secret_scalar: Felt) -> Self { Self { secret_scalar } } /// Loads the private key from a Web3 Secret Storage Definition keystore. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub fn from_keystore

(path: P, password: &str) -> Result where P: AsRef, @@ -63,7 +70,7 @@ impl SigningKey { } /// Encrypts and saves the private key to a Web3 Secret Storage Definition JSON file. - #[cfg(not(target_arch = "wasm32"))] + #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub fn save_as_keystore

(&self, path: P, password: &str) -> Result<(), KeystoreError> where P: AsRef, @@ -92,28 +99,35 @@ impl SigningKey { Ok(()) } - pub fn secret_scalar(&self) -> Felt { + /// Gets the secret scalar in the signing key. + pub const fn secret_scalar(&self) -> Felt { self.secret_scalar } + /// Derives the verifying (public) key that corresponds to the signing key. pub fn verifying_key(&self) -> VerifyingKey { VerifyingKey::from_scalar(get_public_key(&self.secret_scalar)) } + /// Signs a raw hash using ECDSA for a signature. pub fn sign(&self, hash: &Felt) -> Result { ecdsa_sign(&self.secret_scalar, hash).map(|sig| sig.into()) } } impl VerifyingKey { - pub fn from_scalar(scalar: Felt) -> Self { + /// Constructs [`VerifyingKey`] directly from a scalar. + pub const fn from_scalar(scalar: Felt) -> Self { Self { scalar } } - pub fn scalar(&self) -> Felt { + /// Gets the scalar in the verifying key. + pub const fn scalar(&self) -> Felt { self.scalar } + /// Verifies that an ECDSA signature is valid for the verifying key against a certain message + /// hash. pub fn verify(&self, hash: &Felt, signature: &Signature) -> Result { ecdsa_verify(&self.scalar, hash, signature) } diff --git a/starknet-signers/src/ledger.rs b/starknet-signers/src/ledger.rs index 140d787c..2d385242 100644 --- a/starknet-signers/src/ledger.rs +++ b/starknet-signers/src/ledger.rs @@ -5,9 +5,10 @@ use coins_ledger::{ APDUAnswer, APDUCommand, Ledger, }; use crypto_bigint::{ArrayEncoding, U256}; +use semver::Version; use starknet_core::{crypto::Signature, types::Felt}; -use crate::{Signer, VerifyingKey}; +use crate::{Signer, SignerInteractivityContext, VerifyingKey}; pub use coins_bip32::path::DerivationPath; @@ -19,29 +20,51 @@ const EIP_2645_PURPOSE: u32 = 0x80000a55; const EIP_2645_PATH_LENGTH: usize = 6; +const VERSION_SIZE: usize = 3; const PUBLIC_KEY_SIZE: usize = 65; const SIGNATURE_SIZE: usize = 65; +/// Ledger app wrapper that implements the [`Signer`] trait. #[derive(Debug)] pub struct LedgerSigner { - transport: Ledger, + app: LedgerStarknetApp, derivation_path: DerivationPath, } +/// A handle for communicating with the Ledger Starknet app. +#[derive(Debug)] +pub struct LedgerStarknetApp { + transport: Ledger, +} + +/// Errors using the Ledger hardware wallet. #[derive(Debug, thiserror::Error)] pub enum LedgerError { + /// The HD wallet derivation path is malformed or does not conform to EIP-2645. #[error("derivation path is empty, not prefixed with m/2645', or is not 6-level long")] InvalidDerivationPath, + /// Error communicating with the Ledger hardware device. #[error(transparent)] TransportError(coins_ledger::LedgerError), + /// An unknown response code is returned from the device. #[error("unknown response code from Ledger: {0}")] UnknownResponseCode(u16), + /// The response code returned from the device does not indicate success. #[error("failed Ledger request: {0}")] UnsuccessfulRequest(APDUResponseCodes), + /// The response has an unexpected size. #[error("unexpected response length - expected: {expected}; actual: {actual}")] - UnexpectedResponseLength { expected: usize, actual: usize }, + UnexpectedResponseLength { + /// The expected response size. + expected: usize, + /// The actual response size. + actual: usize, + }, } +/// The `GetPubKey` Ledger command. +struct GetVersion; + /// The `GetPubKey` Ledger command. struct GetPubKeyCommand { display: bool, @@ -77,8 +100,6 @@ impl LedgerSigner { /// /// Currently, the Ledger app only enforces the length and the first level of the path. pub async fn new(derivation_path: DerivationPath) -> Result { - let transport = Ledger::init().await?; - if !matches!(derivation_path.iter().next(), Some(&EIP_2645_PURPOSE)) || derivation_path.len() != EIP_2645_PATH_LENGTH { @@ -86,7 +107,7 @@ impl LedgerSigner { } Ok(Self { - transport, + app: LedgerStarknetApp::new().await?, derivation_path, }) } @@ -98,12 +119,72 @@ impl Signer for LedgerSigner { type SignError = LedgerError; async fn get_public_key(&self) -> Result { + self.app + .get_public_key(self.derivation_path.clone(), false) + .await + } + + async fn sign_hash(&self, hash: &Felt) -> Result { + self.app.sign_hash(self.derivation_path.clone(), hash).await + } + + fn is_interactive(&self, _context: SignerInteractivityContext<'_>) -> bool { + true + } +} + +impl LedgerStarknetApp { + /// Initializes the Starknet Ledger app. Attempts to find and connect to a Ledger device. The + /// device must be unlocked and have the Starknet app open. + pub async fn new() -> Result { + let transport = Ledger::init().await?; + + Ok(Self { transport }) + } + + /// Gets the Ledger app version. + pub async fn get_version(&self) -> Result { + let response = self.transport.exchange(&GetVersion.into()).await?; + + let data = get_apdu_data(&response)?; + if data.len() != VERSION_SIZE { + return Err(LedgerError::UnexpectedResponseLength { + expected: VERSION_SIZE, + actual: data.len(), + }); + } + + Ok(Version::new(data[0] as u64, data[1] as u64, data[2] as u64)) + } + + /// Gets a public key from the app for a particular derivation path, with optional on-device + /// confirmation for extra security. + /// + /// The derivation path _must_ follow EIP-2645, i.e. having `2645'` as its "purpose" level as + /// per BIP-44, as the Ledger app does not allow other paths to be used. + /// + /// The path _must_ also be 6-level in length. An example path for Starknet would be: + /// + /// `m/2645'/1195502025'/1470455285'/0'/0'/0` + /// + /// where: + /// + /// - `2645'` is the EIP-2645 prefix + /// - `1195502025'`, decimal for `0x4741e9c9`, is the 31 lowest bits for `sha256(starknet)` + /// - `1470455285'`, decimal for `0x57a55df5`, is the 31 lowest bits for `sha256(starkli)` + /// + /// Currently, the Ledger app only enforces the length and the first level of the path. + pub async fn get_public_key( + &self, + derivation_path: DerivationPath, + display: bool, + ) -> Result { let response = self .transport .exchange( &GetPubKeyCommand { - display: false, - path: self.derivation_path.clone(), + display, + path: derivation_path, } .into(), ) @@ -123,13 +204,34 @@ impl Signer for LedgerSigner { Ok(VerifyingKey::from_scalar(pubkey_x)) } - async fn sign_hash(&self, hash: &Felt) -> Result { + /// Requests a signature for a **raw hash** with a certain derivation path. Currently the Ledger + /// app only supports blind signing raw hashes. + /// + /// The derivation path _must_ follow EIP-2645, i.e. having `2645'` as its "purpose" level as + /// per BIP-44, as the Ledger app does not allow other paths to be used. + /// + /// The path _must_ also be 6-level in length. An example path for Starknet would be: + /// + /// `m/2645'/1195502025'/1470455285'/0'/0'/0` + /// + /// where: + /// + /// - `2645'` is the EIP-2645 prefix + /// - `1195502025'`, decimal for `0x4741e9c9`, is the 31 lowest bits for `sha256(starknet)` + /// - `1470455285'`, decimal for `0x57a55df5`, is the 31 lowest bits for `sha256(starkli)` + /// + /// Currently, the Ledger app only enforces the length and the first level of the path. + pub async fn sign_hash( + &self, + derivation_path: DerivationPath, + hash: &Felt, + ) -> Result { get_apdu_data( &self .transport .exchange( &SignHashCommand1 { - path: self.derivation_path.clone(), + path: derivation_path, } .into(), ) @@ -171,6 +273,19 @@ impl From for LedgerError { } } +impl From for APDUCommand { + fn from(_value: GetVersion) -> Self { + Self { + cla: CLA_STARKNET, + ins: 0x00, + p1: 0x00, + p2: 0x00, + data: APDUData::new(&[]), + response_len: None, + } + } +} + impl From for APDUCommand { fn from(value: GetPubKeyCommand) -> Self { let path = value diff --git a/starknet-signers/src/lib.rs b/starknet-signers/src/lib.rs index 50903fab..ab8ec717 100644 --- a/starknet-signers/src/lib.rs +++ b/starknet-signers/src/lib.rs @@ -1,19 +1,27 @@ +//! Starknet signer interface and common implementations. + +#![deny(missing_docs)] + mod key_pair; pub use key_pair::{SigningKey, VerifyingKey}; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub use key_pair::KeystoreError; mod signer; -pub use signer::Signer; +pub use signer::{Signer, SignerInteractivityContext}; +/// Module containing types related to the use of a simple in-memory signer. pub mod local_wallet; pub use local_wallet::LocalWallet; +/// Module containing types related to the Ledger hardware wallet. #[cfg(feature = "ledger")] pub mod ledger; #[cfg(feature = "ledger")] pub use ledger::{DerivationPath, LedgerError, LedgerSigner}; +/// An error type that indicates an error cannot possibly occur. Used as placeholder where +/// [`Result`] is expected. #[derive(Debug, thiserror::Error)] pub enum Infallible {} diff --git a/starknet-signers/src/local_wallet.rs b/starknet-signers/src/local_wallet.rs index 23c00b28..4f220fbe 100644 --- a/starknet-signers/src/local_wallet.rs +++ b/starknet-signers/src/local_wallet.rs @@ -1,4 +1,4 @@ -use crate::{Infallible, Signer, SigningKey, VerifyingKey}; +use crate::{Infallible, Signer, SignerInteractivityContext, SigningKey, VerifyingKey}; use async_trait::async_trait; use starknet_core::{ @@ -6,18 +6,23 @@ use starknet_core::{ types::Felt, }; +/// A signer that simply holds the signing (private) key in memory for performing cryptographic +/// operations. It's recommended to use hardware-based signers for use cases involving real value. #[derive(Debug, Clone)] pub struct LocalWallet { private_key: SigningKey, } +/// Errors using [`LocalWallet`]. #[derive(Debug, thiserror::Error)] pub enum SignError { + /// ECDSA signature error. #[error(transparent)] EcdsaSignError(EcdsaSignError), } impl LocalWallet { + /// Constructs [`LocalWallet`] from a [`SigningKey`]. pub fn from_signing_key(key: SigningKey) -> Self { key.into() } @@ -36,6 +41,10 @@ impl Signer for LocalWallet { async fn sign_hash(&self, hash: &Felt) -> Result { Ok(self.private_key.sign(hash)?) } + + fn is_interactive(&self, _context: SignerInteractivityContext<'_>) -> bool { + false + } } impl From for LocalWallet { diff --git a/starknet-signers/src/signer.rs b/starknet-signers/src/signer.rs index 0f586f82..f7569b10 100644 --- a/starknet-signers/src/signer.rs +++ b/starknet-signers/src/signer.rs @@ -2,17 +2,61 @@ use crate::VerifyingKey; use async_trait::async_trait; use auto_impl::auto_impl; -use starknet_core::{crypto::Signature, types::Felt}; +use starknet_core::{ + crypto::Signature, + types::{Call, Felt}, +}; use std::error::Error; +/// Any signer that can provide a public key as [`Felt`], and sign a raw hash for a signature +/// encoded as [`Vec`]. #[cfg_attr(not(target_arch = "wasm32"), async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[auto_impl(&, Box, Arc)] pub trait Signer { + /// Possible errors for calling [`get_public_key`](fn.get_public_key). type GetPublicKeyError: Error + Send + Sync; + /// Possible errors for calling [`sign`](fn.sign). type SignError: Error + Send + Sync; + /// Retrieves the verifying (public) key from the signer. async fn get_public_key(&self) -> Result; + /// Requests an ECDSA signature for a message hash. + /// + /// Signing a raw hash is known as "blind signing". For interactive signers (e.g. hardware + /// wallets) that can theoretically provide better security properties via "clear signing", + /// using blind signing is bad practice. + /// + /// However, as of this writing, no actual interactive signer implementation offers clear + /// signing. When this changes in the future, this trait shall be altered to allow such clear + /// signing capabilities. async fn sign_hash(&self, hash: &Felt) -> Result; + + /// Whether the underlying signer implementation is interactive, such as a hardware wallet. + /// Implementations should return `true` if the signing operation is very expensive, even if not + /// strictly "interactive" as in requiring human input. + /// + /// This mainly affects the transaction simulation strategy used by higher-level types. With + /// non-interactive signers, it's fine to sign multiple times for getting the most accurate + /// estimation/simulation possible; but with interactive signers, they would accept less + /// accurate results to minimize signing requests. + fn is_interactive(&self, context: SignerInteractivityContext<'_>) -> bool; +} + +/// Context for helping signer implementations make decisions on whether to act interactively or +/// not, useful for signers with dynamic interactivity. +/// +/// This type only exposes execution details as context, with everything else falling under the +/// `Other` variant, as it's deemed very much pointless to act differently in those scenarios. +/// When an execution is requested, only the list of calls is exposed. +#[derive(Debug, Clone, Copy)] +pub enum SignerInteractivityContext<'a> { + /// An execution is being requested. + Execution { + /// The list of calls being authorized. + calls: &'a [Call], + }, + /// A class declaration or account deployment is being requested. + Other, }