diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..b5cb523 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,42 @@ +on: + push: + branches: + - main + pull_request: + +name: Linting +jobs: + lint: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v3 + + - name: Use Rust cache + uses: Swatinem/rust-cache@v1 + with: + cache-on-failure: true + - name: Install prettier + run: | + yarn global add prettier + - name: Check Markdown format + run: | + prettier --check "**/*.md" + - name: Check Yaml format + run: | + prettier --check "**/*.{yaml,yml}" + + - name: Compile contracts + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.3.1" + - run: | + cd ./contracts && make generate_artifacts && scarb fmt --check + + - name: Check Rust format + run: | + cargo fmt --all -- --check + - name: Run Clippy lints + run: | + cargo clippy --all --all-targets --all-features -- -D warnings diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..f74228a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,119 @@ +on: + push: + tags: + - "cainome/v*.*.*" + - "cainome-*/v*.*.*" + +name: "Release" +jobs: + crate-info: + name: "Extract crate info" + runs-on: "ubuntu-latest" + outputs: + crate: ${{ steps.derive.outputs.crate }} + version: ${{ steps.derive.outputs.version }} + + steps: + - id: "derive" + name: "Derive crate info from Git tag" + run: | + FULL_REF="${{ github.ref }}" + REGEX="^refs\/tags\/([a-z\\-]*)\/v(.*)$" + [[ $FULL_REF =~ $REGEX ]]; + + echo "crate=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT + echo "version=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT + + # Just in case we accidentally release something not on main. + commit-branch-check: + name: "Check commit branch" + runs-on: "ubuntu-latest" + needs: ["crate-info"] + + steps: + - name: "Checkout source code" + uses: "actions/checkout@v3" + with: + fetch-depth: 0 + + - name: "Check if commit is on main" + run: | + COMMIT_HASH=$(git log -1 --format=%H ${{ github.ref }}) + GREP_OUTPUT=$(git log origin/main --format=%H | grep "$COMMIT_HASH") + + if [ -z "$GREP_OUTPUT" ]; then + echo "Cannot release commits not on the main branch" + exit 1 + fi + + crate-version-check: + name: "Check crate version" + runs-on: "ubuntu-latest" + needs: ["crate-info"] + + steps: + - name: "Checkout source code" + uses: "actions/checkout@v3" + + - name: "Check against Cargo.toml" + run: | + if [ "cainome" != "${{ needs.crate-info.outputs.crate }}" ]; then + CRATE_NAME="${{ needs.crate-info.outputs.crate }}" + cd ./crates/"${CRATE_NAME#*-}" + fi + + GREP_OUTPUT=$(cat Cargo.toml | grep "^version = \"${{ needs.crate-info.outputs.version }}\"$") + + if [ -z "$GREP_OUTPUT" ]; then + echo "Crate version mismatch" + exit 1 + fi + + build: + name: "Build for ${{ matrix.os }}" + runs-on: "${{ matrix.os }}" + needs: ["crate-info"] + + strategy: + matrix: + os: + - "ubuntu-latest" + - "windows-latest" + - "macos-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 crate" + run: | + cargo build --package ${{ needs.crate-info.outputs.crate }} --all-targets + + # crates-io-release: + # name: "Release to crates.io" + # runs-on: "ubuntu-latest" + + # needs: + # - "crate-info" + # - "commit-branch-check" + # - "crate-version-check" + # - "build" + + # steps: + # - name: "Checkout source code" + # uses: "actions/checkout@v3" + + # - name: "Login to crates.io" + # run: | + # cargo login ${{ secrets.CRATES_IO_API_TOKEN }} + + # - name: "Public crate" + # run: | + # cargo publish --package ${{ needs.crate-info.outputs.crate }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d669aff --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +on: + push: + branches: + - main + pull_request: + +name: Tests +jobs: + unix-test: + name: Unix tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-11] + toolchain: [stable, nightly] + + steps: + - name: Checkout source code + uses: actions/checkout@v3 + + - name: Compile contracts + uses: software-mansion/setup-scarb@v1 + with: + scarb-version: "2.3.1" + - run: | + cd ./contracts && make generate_artifacts + + - name: Setup toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + profile: minimal + override: true + + - name: Run cargo tests + uses: nick-fields/retry@v2 + with: + timeout_minutes: 20 + max_attempts: 3 + retry_wait_seconds: 30 + command: cargo test --workspace --all-features diff --git a/.github/workflows/udeps.yml b/.github/workflows/udeps.yml new file mode 100644 index 0000000..0b0df50 --- /dev/null +++ b/.github/workflows/udeps.yml @@ -0,0 +1,35 @@ +on: + push: + branches: + - main + pull_request: + +name: Unused deps +jobs: + udeps: + runs-on: ubuntu-latest + steps: + - name: Checkout source code + uses: actions/checkout@v3 + + - 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/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..08914ed --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2272 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "auto_impl" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cainome" +version = "0.1.0" +dependencies = [ + "cainome-cairo-serde", + "cainome-parser", + "cainome-rs", + "starknet", + "tokio", + "url", +] + +[[package]] +name = "cainome-cairo-serde" +version = "0.1.0" +dependencies = [ + "starknet", + "thiserror", +] + +[[package]] +name = "cainome-parser" +version = "0.1.0" +dependencies = [ + "quote", + "serde_json", + "starknet", + "syn 2.0.39", + "thiserror", +] + +[[package]] +name = "cainome-rs" +version = "0.1.0" +dependencies = [ + "anyhow", + "cainome-cairo-serde", + "cainome-parser", + "proc-macro2", + "quote", + "serde_json", + "starknet", + "syn 2.0.39", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.39", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest", + "hex", + "hmac", + "pbkdf2", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror", + "uuid", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hermit-abi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parity-scale-codec" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +dependencies = [ + "toml_datetime", + "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.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-rustls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" +dependencies = [ + "hmac", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_json_pythonic" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62212da9872ca2a0cad0093191ee33753eddff9266cbbc1b4a602d13a3a768db" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "starknet" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb139c5e6f6c6da627080e33cc00b3fc1c9733403034ca1ee9c42a95c337c7f" +dependencies = [ + "starknet-accounts", + "starknet-contract", + "starknet-core", + "starknet-crypto", + "starknet-ff", + "starknet-macros", + "starknet-providers", + "starknet-signers", +] + +[[package]] +name = "starknet-accounts" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3743932c80ad2a5868c2dd4ef729de4e12060c88e73e4bb678a5f8e51b105e53" +dependencies = [ + "async-trait", + "auto_impl", + "starknet-core", + "starknet-providers", + "starknet-signers", + "thiserror", +] + +[[package]] +name = "starknet-contract" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e55aac528c5376e1626d5a8d4daaf280bfd08f909dadc729e5b009203d6ec21" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "starknet-accounts", + "starknet-core", + "starknet-providers", + "thiserror", +] + +[[package]] +name = "starknet-core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50e281d4fdb97988a3d5c7b7cae22b9d67bb2ef9be2cfafc17a1e35542cb53" +dependencies = [ + "base64 0.21.5", + "flate2", + "hex", + "serde", + "serde_json", + "serde_json_pythonic", + "serde_with", + "sha3", + "starknet-crypto", + "starknet-ff", +] + +[[package]] +name = "starknet-crypto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c03f5ac70f9b067f48db7d2d70bdf18ee0f731e8192b6cfa679136becfcdb0" +dependencies = [ + "crypto-bigint", + "hex", + "hmac", + "num-bigint", + "num-integer", + "num-traits", + "rfc6979", + "sha2", + "starknet-crypto-codegen", + "starknet-curve", + "starknet-ff", + "zeroize", +] + +[[package]] +name = "starknet-crypto-codegen" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6527b845423542c8a16e060ea1bc43f67229848e7cd4c4d80be994a84220ce" +dependencies = [ + "starknet-curve", + "starknet-ff", + "syn 2.0.39", +] + +[[package]] +name = "starknet-curve" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68a0d87ae56572abf83ddbfd44259a7c90dbeeee1629a1ffe223e7f9a8f3052" +dependencies = [ + "starknet-ff", +] + +[[package]] +name = "starknet-ff" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7584bc732e4d2a8ccebdd1dda8236f7940a79a339e30ebf338d45c329659e36c" +dependencies = [ + "ark-ff", + "bigdecimal", + "crypto-bigint", + "getrandom", + "hex", + "num-bigint", + "serde", +] + +[[package]] +name = "starknet-macros" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840be1a7eb5735863eee47d3a3f26df45b9be2c519e8da294e74b4d0524d77d1" +dependencies = [ + "starknet-core", + "syn 2.0.39", +] + +[[package]] +name = "starknet-providers" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b08084f36ff7f11743ec71f33f0b11d439cbe0524058def299eb47de1ef1c28" +dependencies = [ + "async-trait", + "auto_impl", + "ethereum-types", + "flate2", + "log", + "reqwest", + "serde", + "serde_json", + "serde_with", + "starknet-core", + "thiserror", + "url", +] + +[[package]] +name = "starknet-signers" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91919d8f318f0b5bcc4ff5849fbd3fb46adaaa72e0bf204742bab7c822425ff4" +dependencies = [ + "async-trait", + "auto_impl", + "crypto-bigint", + "eth-keystore", + "rand", + "starknet-core", + "starknet-crypto", + "thiserror", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "time" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +dependencies = [ + "deranged", + "itoa", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.1.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67b5f0a4e7a27a64c651977932b9dc5667ca7fc31ac44b03ed37a0cf42fdfff" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..055b8a8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "cainome" +version = "0.1.0" +edition = "2021" + +[workspace] +members = [ + "crates/cairo-serde", + "crates/parser", + "crates/rs", +] + +[workspace.dependencies] +# workspace crates +cainome-cairo-serde = { path = "crates/cairo-serde" } +cainome-parser = { path = "crates/parser" } + +# serde +serde = { version = "1.0", default-features = false, features = ["alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["std"] } +thiserror = "1.0" +anyhow = "1.0" + +starknet = "0.8.0" + +[dependencies] +cainome-parser.workspace = true +cainome-cairo-serde.workspace = true +cainome-rs = { path = "crates/rs", optional = true } + +[dev-dependencies] +starknet.workspace = true +tokio = { version = "1.15.0", features = ["full"] } +url = "2.2.2" + +[features] +default = [] +abigen-rs = ["cainome-rs"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..11e493e --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Cainome: bindings generation from Cairo ABI + +Cainome is a library to generate bindings from Cairo ABI. + +Cainome architecture provides a flexible way to work with Cairo ABI +for different languages (backends). + +## Project structure + +- **parser**: a run-time library to parse an ABI file into `Token`s [README](./crates/parser/README.md). +- **cairo-serde**: a compile-time library that implements serialization for native Rust types from `FieldElement` buffer [README](./crates/cairo-serde/README.md). +- **rs**: a compile-time library backend for the `abigen` macro to generate rust bindings [README](./crates/rs/README.md). +- **ts**: a compile-time library backend to generate `TypeScript` bindings (coming soon). + +Currently those crates are not published on crates.io, please consider using them with the release tags. + +## Cainome meaning + +Cainome is a word combining `Cairo` and `Genome`. The idea of `Cairo ABI` being the DNA of our ecosystem, +and like the genome expresses several genes which turn into proteins, from an `ABI` we can generate several bindings in different languages. diff --git a/contracts/.gitignore b/contracts/.gitignore new file mode 100644 index 0000000..a3c6f49 --- /dev/null +++ b/contracts/.gitignore @@ -0,0 +1,2 @@ +/target +/abi diff --git a/contracts/.tool-versions b/contracts/.tool-versions new file mode 100644 index 0000000..697917e --- /dev/null +++ b/contracts/.tool-versions @@ -0,0 +1 @@ +scarb 2.3.1 diff --git a/contracts/Makefile b/contracts/Makefile new file mode 100644 index 0000000..e9a939d --- /dev/null +++ b/contracts/Makefile @@ -0,0 +1,47 @@ +config := --account katana-0 \ + --rpc http://0.0.0.0:5050 + +scarb_build := target/dev/contracts_ +sierra := .contract_class.json +artifacts := ./abi/ + +generate_artifacts: + scarb build + mkdir -p ${artifacts} + + jq .abi ${scarb_build}simple_events${sierra} > ${artifacts}events.abi.json + jq .abi ${scarb_build}simple_get_set${sierra} > ${artifacts}simple_get_set.abi.json + jq .abi ${scarb_build}simple_types${sierra} > ${artifacts}simple_types.abi.json + jq .abi ${scarb_build}components_contract${sierra} > ${artifacts}components.abi.json + jq .abi ${scarb_build}enums${sierra} > ${artifacts}enums.abi.json + jq .abi ${scarb_build}option_result${sierra} > ${artifacts}option_result.abi.json + jq .abi ${scarb_build}simple_interface${sierra} > ${artifacts}simple_interface.abi.json + jq .abi ${scarb_build}structs${sierra} > ${artifacts}structs.abi.json + + +setup: setup_simple_get_set + +# Declare and deploy the simple_get_set contract on katana. +setup_simple_get_set: + scarb build + $(eval class_hash=$(shell starkli class-hash target/dev/contracts_simple_get_set.contract_class.json)) + starkli declare target/dev/contracts_simple_get_set.contract_class.json ${config} + starkli deploy ${class_hash} --salt 0x1234 ${config} + +# # Declare and deploy the basic contract on katana. +# setup_basic: +# $(eval class_hash=$(shell starkli class-hash target/dev/contracts_basic.sierra.json)) +# starkli declare target/dev/contracts_basic.sierra.json ${config} +# starkli deploy ${class_hash} --salt 0x1234 ${config} + +# # Declare and deploy the basic contract on katana. +# setup_gen: +# $(eval class_hash=$(shell starkli class-hash target/dev/contracts_gen.sierra.json)) +# starkli declare target/dev/contracts_gen.sierra.json ${config} +# starkli deploy ${class_hash} --salt 0x1234 ${config} + +# # Declare and deploy the event contract on katana. +# setup_event: +# $(eval class_hash=$(shell starkli class-hash target/dev/contracts_event.sierra.json)) +# starkli declare target/dev/contracts_event.sierra.json ${config} +# starkli deploy ${class_hash} --salt 0x1234 ${config} diff --git a/contracts/Scarb.lock b/contracts/Scarb.lock new file mode 100644 index 0000000..83cca99 --- /dev/null +++ b/contracts/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "contracts" +version = "0.1.0" diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml new file mode 100644 index 0000000..8447e83 --- /dev/null +++ b/contracts/Scarb.toml @@ -0,0 +1,10 @@ +[package] +name = "contracts" +version = "0.1.0" + +[dependencies] +starknet = "2.3.1" + +[[target.starknet-contract]] +sierra = true +casm = true diff --git a/contracts/src/abicov/components.cairo b/contracts/src/abicov/components.cairo new file mode 100644 index 0000000..b9933f2 --- /dev/null +++ b/contracts/src/abicov/components.cairo @@ -0,0 +1,156 @@ +//! A contract with components. +#[starknet::interface] +trait ISimple { + fn read_data(self: @TContractState) -> felt252; + fn write_data(ref self: TContractState, data: felt252); +} + +#[starknet::component] +mod simple_component { + #[storage] + struct Storage { + data: felt252, + } + + #[derive(Drop, Serde)] + struct MyStruct { + a: felt252, + b: felt252, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Written: Written + } + + #[derive(Drop, starknet::Event)] + struct Written { + before: felt252, + after: felt252, + } + + #[embeddable_as(Simple)] + impl SimpleImpl< + TContractState, +HasComponent + > of super::ISimple> { + fn read_data(self: @ComponentState) -> felt252 { + self.data.read() + } + + fn write_data(ref self: ComponentState, data: felt252) { + let before = self.data.read(); + self.data.write(data); + self.emit(Written { before, after: data }); + } + } +} + +#[starknet::component] +mod simple_component_other { + #[storage] + struct Storage { + data: felt252, + } + + #[derive(Drop, Serde)] + struct MyStruct { + data: u256, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Written: Written + } + + #[derive(Drop, starknet::Event)] + struct Written { + data: felt252, + } + + #[embeddable_as(SimpleOther)] + impl SimpleImpl< + TContractState, +HasComponent + > of super::ISimple> { + fn read_data(self: @ComponentState) -> felt252 { + self.data.read() + } + + fn write_data(ref self: ComponentState, data: felt252) { + self.data.write(data); + self.emit(Written { data }); + } + } +} + +#[starknet::contract] +mod components_contract { + use super::simple_component; + use super::simple_component_other; + + component!(path: simple_component, storage: simple, event: SimpleEvent); + component!(path: simple_component_other, storage: simple_other, event: SimpleEventOther); + + #[abi(embed_v0)] + impl SimpleImpl = simple_component::Simple; + impl SimpleOtherImpl = simple_component_other::SimpleOther; + + #[storage] + struct Storage { + value: felt252, + #[substorage(v0)] + simple: simple_component::Storage, + #[substorage(v0)] + simple_other: simple_component_other::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + OutterEvent: OutterEvent, + // With flat, only the selector `Written` is set in keys. + #[flat] + SimpleEvent: simple_component::Event, + // Without flat, the first selector in the keys is `SimpleEventOther`, and + // the second is `Written`. + SimpleEventOther: simple_component_other::Event, + } + + #[derive(Drop, starknet::Event)] + struct OutterEvent {} + + #[external(v0)] + fn simple(ref self: ContractState) { + self.simple.write_data(0xaa); + self.value.write(0xff); + } + + #[external(v0)] + fn simple_other(ref self: ContractState) { + self.simple_other.write_data(0xaa); + self.value.write(0xee); + } + + #[external(v0)] + fn array_struct_simple(ref self: ContractState) -> Span { + array![].span() + } + + #[external(v0)] + fn array_struct_simple_other( + ref self: ContractState + ) -> Span { + array![].span() + } + + #[external(v0)] + fn tuple_events( + ref self: ContractState + ) -> (simple_component::MyStruct, simple_component_other::MyStruct) { + ( + simple_component::MyStruct { a: 1, b: 2, }, + simple_component_other::MyStruct { data: 'other', }, + ) + } +} diff --git a/contracts/src/abicov/enums.cairo b/contracts/src/abicov/enums.cairo new file mode 100644 index 0000000..bb96b9e --- /dev/null +++ b/contracts/src/abicov/enums.cairo @@ -0,0 +1,67 @@ +//! A contract with enums. + +#[derive(Serde, Drop, starknet::Store)] +enum SimpleEnum { + Variant1, + Variant2, +} + +#[derive(Serde, Drop, starknet::Store)] +enum TypedEnum { + Variant1: felt252, + Variant2: u256, + Variant3: (felt252, u256), +} + +#[derive(Serde, Drop, starknet::Store)] +enum MixedEnum { + Variant1: felt252, + Variant2, +} + +#[starknet::contract] +mod enums { + use super::{SimpleEnum, TypedEnum, MixedEnum}; + + #[storage] + struct Storage { + simple: SimpleEnum, + typed: TypedEnum, + mixed: MixedEnum, + } + + #[external(v0)] + fn get_simple_1(self: @ContractState) -> SimpleEnum { + self.simple.read() + } + + #[external(v0)] + fn get_simple_2(self: @ContractState) -> SimpleEnum { + SimpleEnum::Variant2 + } + + #[external(v0)] + fn get_typed_1(self: @ContractState) -> TypedEnum { + TypedEnum::Variant1(0x123) + } + + #[external(v0)] + fn get_typed_2(self: @ContractState) -> TypedEnum { + TypedEnum::Variant2(0xff_u256) + } + + #[external(v0)] + fn get_typed_3(self: @ContractState) -> TypedEnum { + TypedEnum::Variant3((1, 0xffffff_u256)) + } + + #[external(v0)] + fn get_mixed_1(self: @ContractState) -> MixedEnum { + MixedEnum::Variant1(0x123) + } + + #[external(v0)] + fn get_mixed_2(self: @ContractState) -> MixedEnum { + MixedEnum::Variant2 + } +} diff --git a/contracts/src/abicov/option_result.cairo b/contracts/src/abicov/option_result.cairo new file mode 100644 index 0000000..8688391 --- /dev/null +++ b/contracts/src/abicov/option_result.cairo @@ -0,0 +1,59 @@ +//! A simple contract without interfaces +//! to test very basic types. + +#[starknet::contract] +mod option_result { + use starknet::{ClassHash, ContractAddress, EthAddress}; + + #[derive(Serde, Drop)] + struct GenericOne { + a: T, + b: felt252, + c: u256, + } + + #[storage] + struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState) {} + + #[external(v0)] + fn result_ok_unit(self: @ContractState, res: Result<(), felt252>) -> Result { + Result::Ok(2_u64) + } + + #[external(v0)] + fn result_ok_struct( + self: @ContractState, res: Result, felt252> + ) -> Result { + Result::Ok(2_u64) + } + + #[external(v0)] + fn result_ok_tuple_struct( + self: @ContractState, res: Result<(GenericOne, felt252), felt252> + ) -> Result { + Result::Ok(2_u64) + } + + #[external(v0)] + fn result_ok(self: @ContractState, res: Result) -> Result { + Result::Ok(2_u64) + } + + #[external(v0)] + fn result_err(self: @ContractState, res: Result) -> Result { + Result::Err(0xff_u256) + } + + #[external(v0)] + fn option_some(self: @ContractState, opt: Option) -> Option> { + Option::Some(array![1, 2].span()) + } + + #[external(v0)] + fn option_none(self: @ContractState, opt: Option) -> Option { + Option::None + } +} diff --git a/contracts/src/abicov/simple_events.cairo b/contracts/src/abicov/simple_events.cairo new file mode 100644 index 0000000..b3a3336 --- /dev/null +++ b/contracts/src/abicov/simple_events.cairo @@ -0,0 +1,87 @@ +//! A simple contract with events. +//! +#[starknet::contract] +mod simple_events { + #[storage] + struct Storage { + value: felt252, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + EventOnlyKey: EventOnlyKey, + EventOnlyData: EventOnlyData, + EventAll: EventAll, + EventMultiple: EventMultiple, + EventNothing: EventNothing, + SuperEvent: EventWithOtherName, + } + + #[derive(Drop, starknet::Event)] + struct EventOnlyKey { + #[key] + value: felt252, + } + + #[derive(Drop, starknet::Event)] + struct EventOnlyData { + value: felt252, + } + + #[derive(Drop, starknet::Event)] + struct EventAll { + #[key] + header: felt252, + value: Span, + } + + #[derive(Drop, starknet::Event)] + struct EventMultiple { + #[key] + key1: felt252, + #[key] + key2: felt252, + data1: felt252, + data2: u256, + data3: (felt252, felt252), + } + + #[derive(Drop, starknet::Event)] + struct EventNothing {} + + #[derive(Drop, starknet::Event)] + struct EventWithOtherName { + value: felt252, + } + + #[external(v0)] + fn emit_only_key(ref self: ContractState) { + self.emit(EventOnlyKey { value: 1 }); + } + + #[external(v0)] + fn emit_only_data(ref self: ContractState) { + self.emit(EventOnlyData { value: 1 }); + } + + #[external(v0)] + fn emit_all(ref self: ContractState) { + self.emit(EventAll { header: 1, value: array![1].span() }); + } + + #[external(v0)] + fn emit_multiple(ref self: ContractState) { + self.emit(EventMultiple { key1: 1, key2: 2, data1: 3, data2: 4_u256, data3: (5, 6), }); + } + + #[external(v0)] + fn emit_nothing(ref self: ContractState) { + self.emit(EventNothing {}); + } + + #[external(v0)] + fn emit_super(ref self: ContractState) { + self.emit(EventWithOtherName { value: 1 }); + } +} diff --git a/contracts/src/abicov/simple_interface.cairo b/contracts/src/abicov/simple_interface.cairo new file mode 100644 index 0000000..68bdf0d --- /dev/null +++ b/contracts/src/abicov/simple_interface.cairo @@ -0,0 +1,28 @@ +//! A simple contract with an interface. +//! +#[starknet::interface] +trait MyInterface { + fn get_value(self: @T) -> felt252; + fn set_value(ref self: T, value: felt252); +} + +#[starknet::contract] +mod simple_interface { + use super::MyInterface; + + #[storage] + struct Storage { + value: felt252, + } + + #[external(v0)] + impl MyInterfaceImpl of MyInterface { + fn get_value(self: @ContractState) -> felt252 { + self.value.read() + } + + fn set_value(ref self: ContractState, value: felt252) { + self.value.write(value); + } + } +} diff --git a/contracts/src/abicov/simple_types.cairo b/contracts/src/abicov/simple_types.cairo new file mode 100644 index 0000000..ce64658 --- /dev/null +++ b/contracts/src/abicov/simple_types.cairo @@ -0,0 +1,128 @@ +//! A simple contract without interfaces +//! to test very basic types. + +#[starknet::contract] +mod simple_types { + use starknet::{ClassHash, ContractAddress, EthAddress}; + + #[storage] + struct Storage { + felt: felt252, + uint256: u256, + uint64: u64, + address: ContractAddress, + class_hash: ClassHash, + eth_address: EthAddress, + tuple: (felt252, u256), + boolean: bool, + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.felt.write(0x1234); + } + + #[external(v0)] + fn get_bool(self: @ContractState) -> bool { + self.boolean.read() + } + + #[external(v0)] + fn set_bool(ref self: ContractState, v: bool) { + self.boolean.write(v); + } + + #[external(v0)] + fn get_felt(self: @ContractState) -> felt252 { + self.felt.read() + } + + #[external(v0)] + fn set_felt(ref self: ContractState, felt: felt252) { + self.felt.write(felt); + } + + #[external(v0)] + fn get_u256(self: @ContractState) -> u256 { + self.uint256.read() + } + + #[external(v0)] + fn set_u256(ref self: ContractState, uint256: u256) { + self.uint256.write(uint256); + } + + #[external(v0)] + fn get_u64(self: @ContractState) -> u64 { + self.uint64.read() + } + + #[external(v0)] + fn set_u64(ref self: ContractState, uint64: u64) { + self.uint64.write(uint64); + } + + #[external(v0)] + fn get_address(self: @ContractState) -> ContractAddress { + self.address.read() + } + + #[external(v0)] + fn set_address(ref self: ContractState, address: ContractAddress) { + self.address.write(address); + } + + #[external(v0)] + fn get_class_hash(self: @ContractState) -> ClassHash { + self.class_hash.read() + } + + #[external(v0)] + fn set_class_hash(ref self: ContractState, class_hash: ClassHash) { + self.class_hash.write(class_hash); + } + + #[external(v0)] + fn get_eth_address(self: @ContractState) -> EthAddress { + self.eth_address.read() + } + + #[external(v0)] + fn set_eth_address(ref self: ContractState, eth_address: EthAddress) { + self.eth_address.write(eth_address); + } + + #[external(v0)] + fn get_tuple(self: @ContractState) -> (felt252, u256) { + self.tuple.read() + } + + #[external(v0)] + fn set_tuple(ref self: ContractState, tuple: (felt252, u256)) { + self.tuple.write(tuple); + } + + #[external(v0)] + fn get_array(self: @ContractState) -> Span { + let felt = self.felt.read(); + let uint256 = self.uint256.read(); + let uint64 = self.uint64.read(); + + array![felt, uint256.low.into(), uint256.high.into(), uint64.into(),].span() + } + + #[external(v0)] + fn set_array(ref self: ContractState, data: Span) { + assert(data.len() == 4, 'bad data len (expected 4)'); + self.felt.write(*data[0]); + self + .uint256 + .write( + u256 { + low: (*data[1]).try_into().expect('invalid u128'), + high: (*data[2]).try_into().expect('invalid u128') + } + ); + self.uint64.write((*data[3]).try_into().expect('invalid u64')); + } +} diff --git a/contracts/src/abicov/structs.cairo b/contracts/src/abicov/structs.cairo new file mode 100644 index 0000000..d944609 --- /dev/null +++ b/contracts/src/abicov/structs.cairo @@ -0,0 +1,137 @@ +//! A contract with structs. +use starknet::{ClassHash, ContractAddress, EthAddress}; + +#[derive(Serde, Drop)] +struct Simple { + felt: felt252, + uint256: u256, + uint64: u64, + address: ContractAddress, + class_hash: ClassHash, + eth_address: EthAddress, + tuple: (felt252, u256), + span: Span, +} + +#[derive(Serde, Drop)] +struct StructWithStruct { + simple: Simple, +} + +#[derive(Serde, Drop)] +struct GenericOne { + a: T, + b: felt252, + c: u256, +} + +#[derive(Serde, Drop)] +struct GenericTwo { + a: T, + b: U, + c: felt252, +} + +// NOT SUPPORTED. +// #[derive(Serde, Drop)] +// struct GenericThree { +// a: T, +// b: U, +// c: V, +// } + +// NOT SUPPORTED. +// #[derive(Serde, Drop)] +// struct GenericOfGeneric { +// a: GenericOne, +// } + +#[starknet::contract] +mod structs { + use super::{Simple, StructWithStruct, GenericOne, GenericTwo}; + + #[storage] + struct Storage {} + + #[external(v0)] + fn get_simple(self: @ContractState) -> Simple { + Simple { + felt: 1, + uint256: 2_u256, + uint64: 3_u64, + address: 0x1234.try_into().unwrap(), + class_hash: 0x1122.try_into().unwrap(), + eth_address: 0x3344.try_into().unwrap(), + tuple: (1, 2_u256), + span: array![1, 2, 3, 4].span(), + } + } + + #[external(v0)] + fn set_simple(ref self: ContractState, simple: Simple) {} + + #[external(v0)] + fn get_struct_w_struct(self: @ContractState) -> StructWithStruct { + StructWithStruct { + simple: Simple { + felt: 1, + uint256: 2_u256, + uint64: 3_u64, + address: 0x1234.try_into().unwrap(), + class_hash: 0x1122.try_into().unwrap(), + eth_address: 0x3344.try_into().unwrap(), + tuple: (1, 2_u256), + span: array![1, 2, 3, 4].span(), + } + } + } + + #[external(v0)] + fn set_struct_w_struct(ref self: ContractState, sws: StructWithStruct) {} + + #[external(v0)] + fn get_generic_one(self: @ContractState) -> GenericOne { + GenericOne { a: 1, b: 2, c: 3_u256, } + } + + #[external(v0)] + fn get_generic_one_array(self: @ContractState) -> GenericOne> { + GenericOne { a: array![1, 2].span(), b: 2, c: 3_u256, } + } + + #[external(v0)] + fn set_generic_one(ref self: ContractState, generic: GenericOne) {} + + #[external(v0)] + fn set_generic_two_2(ref self: ContractState, generic: GenericTwo) {} + + #[external(v0)] + fn set_generic_two_0(ref self: ContractState, generic: GenericTwo) {} + + #[external(v0)] + fn set_generic_two(ref self: ContractState, generic: GenericTwo) {} + + #[external(v0)] + fn get_generic_two(self: @ContractState) -> GenericTwo { + GenericTwo { a: 1, b: 2_u256, c: 3, } + } + + + #[external(v0)] + fn set_tuple_generic( + ref self: ContractState, value: (GenericOne, GenericTwo) + ) {} + + #[external(v0)] + fn get_tuple_of_array_generic(self: @ContractState) -> (Span>, Span) { + (array![GenericOne { a: 0x1, b: 0x2, c: 0x3_u256, },].span(), array![1, 2, 3].span(),) + } +// #[external(v0)] +// fn set_generic_three_1(ref self: ContractState, generic: GenericThree) {} + +// #[external(v0)] +// fn set_generic_three_2(ref self: ContractState, generic: GenericThree) {} + +// #[external(v0)] +// fn set_generic_three_3(ref self: ContractState, generic: GenericThree) {} +} diff --git a/contracts/src/basic.cairo b/contracts/src/basic.cairo new file mode 100644 index 0000000..9a5ff4d --- /dev/null +++ b/contracts/src/basic.cairo @@ -0,0 +1,20 @@ +#[starknet::contract] +mod basic { + #[storage] + struct Storage { + v1: felt252, + v2: u256, + v3: felt252, + } + + #[external(v0)] + fn set_storage(ref self: ContractState, v1: felt252, v2: u256) { + self.v1.write(v1); + self.v2.write(v2); + } + + #[external(v0)] + fn read_storage_tuple(self: @ContractState) -> (felt252, u256) { + (self.v1.read(), self.v2.read()) + } +} diff --git a/contracts/src/event.cairo b/contracts/src/event.cairo new file mode 100644 index 0000000..ca9b7db --- /dev/null +++ b/contracts/src/event.cairo @@ -0,0 +1,57 @@ +#[starknet::contract] +mod event { + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + MyEventA: MyEventA, + MyEventB: MyEventB, + MyEventC: MyEventC, + } + + #[derive(Drop, starknet::Event)] + struct MyEventA { + #[key] + header: felt252, + value: Span, + } + + #[derive(Drop, starknet::Event)] + struct MyEventB { + value: felt252, + } + + #[derive(Drop, starknet::Event)] + struct MyEventC { + #[key] + v1: felt252, + #[key] + v2: felt252, + v3: felt252, + v4: ContractAddress, + } + + #[external(v0)] + fn read(ref self: ContractState) -> felt252 { + 2 + } + + #[external(v0)] + fn emit_a(ref self: ContractState, header: felt252, value: Span) { + self.emit(MyEventA { header, value, }); + } + + #[external(v0)] + fn emit_b(ref self: ContractState, value: felt252) { + self.emit(MyEventB { value, }); + } + + #[external(v0)] + fn emit_c(ref self: ContractState, v1: felt252, v2: felt252, v3: felt252, v4: ContractAddress) { + self.emit(MyEventC { v1, v2, v3, v4, }); + } +} diff --git a/contracts/src/gen.cairo b/contracts/src/gen.cairo new file mode 100644 index 0000000..fe47df7 --- /dev/null +++ b/contracts/src/gen.cairo @@ -0,0 +1,34 @@ +#[starknet::contract] +mod gen { + use starknet::ContractAddress; + + #[storage] + struct Storage { + v1: felt252, + v2: felt252, + } + + #[derive(Serde, Drop)] + struct MyStruct { + f1: felt252, + f2: T, + f3: felt252, + } + + #[external(v0)] + fn func1(ref self: ContractState, a: MyStruct) { + self.v1.write(a.f1); + self.v2.write(a.f2); + } + + #[external(v0)] + fn func2(ref self: ContractState, a: MyStruct) { + self.v1.write(a.f2.low.into()); + self.v2.write(a.f2.high.into()); + } + + #[external(v0)] + fn read(self: @ContractState) -> (felt252, felt252) { + (self.v1.read(), self.v2.read()) + } +} diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo new file mode 100644 index 0000000..611d5a8 --- /dev/null +++ b/contracts/src/lib.cairo @@ -0,0 +1,11 @@ +mod abicov { + mod components; + mod enums; + mod simple_events; + mod simple_interface; + mod simple_types; + mod structs; + mod option_result; +} + +mod simple_get_set; diff --git a/contracts/src/simple_get_set.cairo b/contracts/src/simple_get_set.cairo new file mode 100644 index 0000000..67559c6 --- /dev/null +++ b/contracts/src/simple_get_set.cairo @@ -0,0 +1,60 @@ +#[starknet::contract] +mod simple_get_set { + #[storage] + struct Storage { + a: felt252, + b: u256, + } + + #[derive(Serde, Drop)] + enum TestEnum { + V1: felt252, + V2, + } + + #[external(v0)] + fn get_set_enum(self: @ContractState, v: TestEnum) -> TestEnum { + match v { + TestEnum::V1(v) => TestEnum::V1(v), + TestEnum::V2 => TestEnum::V2, + } + } + + #[external(v0)] + fn get_a(self: @ContractState) -> felt252 { + self.a.read() + } + + #[external(v0)] + fn set_a(ref self: ContractState, a: felt252) { + self.a.write(a); + } + + #[external(v0)] + fn get_b(self: @ContractState) -> u256 { + self.b.read() + } + + #[external(v0)] + fn set_b(ref self: ContractState, b: u256) { + self.b.write(b); + } + + #[external(v0)] + fn set_array(ref self: ContractState, data: Span) { + assert(data.len() == 3, 'bad data len'); + self.a.write(*data[0]); + self + .b + .write( + u256 { low: (*data[1]).try_into().unwrap(), high: (*data[2]).try_into().unwrap() } + ); + } + + #[external(v0)] + fn get_array(self: @ContractState) -> Span { + let b = self.b.read(); + + array![self.a.read(), b.low.into(), b.high.into(),].span() + } +} diff --git a/crates/cairo-serde/Cargo.toml b/crates/cairo-serde/Cargo.toml new file mode 100644 index 0000000..4bf3297 --- /dev/null +++ b/crates/cairo-serde/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cainome-cairo-serde" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +starknet.workspace = true +thiserror.workspace = true diff --git a/crates/cairo-serde/README.md b/crates/cairo-serde/README.md new file mode 100644 index 0000000..8ab58b7 --- /dev/null +++ b/crates/cairo-serde/README.md @@ -0,0 +1,91 @@ +# Cairo Serde + +Cairo serde is a compile-time library that implement a trait `CairoSerde` on Rust native types. +By implementing this trait, the Rust type becomes (de)serializable from / into an array of `FieldElement`. + +## Built-in types + +The types considered built-in by Cairo Serde are the following: + +```rust +pub const CAIRO_BASIC_STRUCTS: [&str; 4] = ["Span", "ClassHash", "ContractAddress", "EthAddress"]; + +pub const CAIRO_BASIC_ENUMS: [&str; 3] = ["Option", "Result", "bool"]; +``` + +All those types, even if they are represented in the ABI as an `enum` or a `struct`, has their built-in Cairo Serde implementation in this crate. + +# Supported types + +Cairo Serde provides serialization support for the following types: + +- `boolean` -> `bool`. +- `felt252` -> `starknet::core::types::FieldElement`. +- `integers (signed and unsigned)` -> `u[8,16,32,64,128], i[8,16,32,64,128], usize`. +- `Option` -> `Option` +- `Result` -> `Result` +- `ContractAddress` -> Custom type in this crate `ContractAddress`. +- `EthAddress` -> Custom type in this crate `EthAddress` (TODO: use the EthAddress from `starknet-rs`). +- `ClassHash` -> Custom type in this crate `ClassHash`. +- `Array/Span` -> `Vec`. +- `Tuple` -> native tuples + the unit `()` type. + +## `CairoSerde` trait + +Cairo Serde trait has for now a first interface that is the following: + +```rust +pub trait CairoSerde { + type RustType; + + fn serialized_size(_rust: &Self::RustType) -> usize; + fn serialize(rust: &Self::RustType) -> Vec; + fn deserialize(felts: &[FieldElement], offset: usize) -> Result; +} +``` + +For now, while using the `deserilialize` method, you must provide the index in the buffer. + +Some work that is in the roadmap: + +- Adding a `serialize_to(rust: &Self::RustType, out: &mut Vec)` to avoid allocating a new array for each type in a big felt buffer. +- Adding/modifying to `deserialize(felts: &[FieldElement]) -> Result` without the offset using rust slice. The motivation of using an explicit offset in the first version was to keep the context of the current deserialization operation in the global buffer. + +## Examples + +```rust +# Array/Span + +# The length is automatically inserted as the first element of the `Vec` +# and all the values are converted into `FieldElement`. +let v: Vec = vec![1, 2, 3]; +let felts = Vec::::serialize(&v); + +let values = Vec::::deserialize(&felts, 0).unwrap(); +``` + +```rust +# Option + +# The variant index is handled by the library. +let o: Option = None; +let felts = Option::::serialize(&o); + +let felts = vec![FieldElement::ONE]; +let o = Option::::deserialize(&felts, 0).unwrap(); + +let o = Some(u32::MAX); +let felts = Option::::serialize(&o); + +let felts = vec![FieldElement::ZERO, FieldElement::from(u32::MAX)]; +let o = Option::::deserialize(&felts, 0).unwrap(); +``` + +```rust +# Tuples +let v = (FieldElement::ONE, 128_u32); +let felts = <(FieldElement, u32)>::serialize(&v); + +let felts = vec![FieldElement::THREE, 99_u32.into()]; +let vals = <(FieldElement, u32)>::deserialize(&felts, 0).unwrap(); +``` diff --git a/crates/cairo-serde/src/call.rs b/crates/cairo-serde/src/call.rs new file mode 100644 index 0000000..fd7851e --- /dev/null +++ b/crates/cairo-serde/src/call.rs @@ -0,0 +1,46 @@ +//! This file must be in the proc_macro2 crate that must be reworked. +use crate::{CairoSerde, Error, Result as CairoResult}; +use starknet::core::types::{BlockId, BlockTag, FunctionCall}; +use std::marker::PhantomData; + +#[derive(Debug)] +pub struct FCall<'p, P, T> { + pub call_raw: FunctionCall, + pub block_id: BlockId, + provider: &'p P, + rust_type: PhantomData, +} + +impl<'p, P, T> FCall<'p, P, T> +where + P: starknet::providers::Provider + Sync, + T: CairoSerde, +{ + pub fn new(call_raw: FunctionCall, provider: &'p P) -> Self { + Self { + call_raw, + block_id: BlockId::Tag(BlockTag::Pending), + provider, + rust_type: PhantomData, + } + } + + pub fn provider(self) -> &'p P { + self.provider + } + + pub fn block_id(mut self, block_id: BlockId) -> Self { + self.block_id = block_id; + self + } + + pub async fn call(self) -> CairoResult { + let r = self + .provider + .call(self.call_raw, self.block_id) + .await + .map_err(|err| Error::Deserialize(format!("Deserialization error {}", err)))?; + + T::cairo_deserialize(&r, 0) + } +} diff --git a/crates/cairo-serde/src/error.rs b/crates/cairo-serde/src/error.rs new file mode 100644 index 0000000..7b9fa03 --- /dev/null +++ b/crates/cairo-serde/src/error.rs @@ -0,0 +1,31 @@ +use super::CairoSerde; + +use starknet::core::types::FieldElement; + +/// Cairo types result. +pub type Result = core::result::Result; + +/// A cairo type error. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum Error { + #[error("Invalid type found {0:?}.")] + InvalidTypeString(String), + #[error("Error during serialization {0:?}.")] + Serialize(String), + #[error("Error during deserialization {0:?}.")] + Deserialize(String), +} + +impl CairoSerde for Error { + type RustType = Self; + + fn cairo_serialize(_rust: &Self::RustType) -> Vec { + vec![] + } + + fn cairo_deserialize(_felts: &[FieldElement], _offset: usize) -> Result { + Ok(Error::Deserialize( + "Error cairotype deserialized?".to_string(), + )) + } +} diff --git a/crates/cairo-serde/src/lib.rs b/crates/cairo-serde/src/lib.rs new file mode 100644 index 0000000..299f9a5 --- /dev/null +++ b/crates/cairo-serde/src/lib.rs @@ -0,0 +1,57 @@ +//! This crate contains the definition of traits and types +//! that map to Cairo types that can then be (de)serializable from an array of `FieldElement`. +//! +//! Some of the Cairo types are provided in the ABI event if they are very generic +//! like `Option`, `Result`, etc... +//! This crate provides the `CairoSerde` implementation for those types and all basic +//! types from Cairo (integers, felt etc...). +//! +mod error; +pub use error::{Error, Result}; + +pub mod call; +pub mod types; +pub use types::array_legacy::*; +pub use types::starknet::*; +pub use types::*; + +use ::starknet::core::types::FieldElement; + +/// Basic cairo structs that are already implemented inside +/// this crate and hence skipped during ABI generation. +pub const CAIRO_BASIC_STRUCTS: [&str; 4] = ["Span", "ClassHash", "ContractAddress", "EthAddress"]; + +/// Same as `CAIRO_BASIC_STRUCTS`, but for enums. +pub const CAIRO_BASIC_ENUMS: [&str; 3] = ["Option", "Result", "bool"]; + +/// CairoSerde trait to implement in order to serialize/deserialize +/// a Rust type to/from a CairoSerde. +pub trait CairoSerde { + /// The corresponding Rust type. + type RustType; + + /// The serialized size of the type in felts, if known at compile time. + const SERIALIZED_SIZE: Option = Some(1); + + /// Whether the serialized size is dynamic. + const DYNAMIC: bool = Self::SERIALIZED_SIZE.is_none(); + + /// Calculates the serialized size of the data for a single felt + /// it will always be 1. + /// If the type is dynamic, SERIALIZED_SIZE is None, but this + + /// function is overriden to correctly compute the size. + #[inline] + fn cairo_serialized_size(_rust: &Self::RustType) -> usize { + Self::SERIALIZED_SIZE.unwrap() + } + + /// Serializes the given type into a FieldElement sequence. + fn cairo_serialize(rust: &Self::RustType) -> Vec; + + /// TODO: add serialize_to(rust: &Self::RustType, out: &mut Vec) + /// for large buffers optimization. + + /// Deserializes an array of felts into the given type. + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result; +} diff --git a/crates/cairo-serde/src/types/array.rs b/crates/cairo-serde/src/types/array.rs new file mode 100644 index 0000000..c4b5063 --- /dev/null +++ b/crates/cairo-serde/src/types/array.rs @@ -0,0 +1,145 @@ +//! CairoSerde implementation for `Vec`. +//! They are used for Array and Span cairo types. +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +impl CairoSerde for Vec +where + T: CairoSerde, +{ + type RustType = Vec; + + const SERIALIZED_SIZE: Option = None; + + #[inline] + fn cairo_serialized_size(rust: &Self::RustType) -> usize { + let data = rust; + // 1 + because the length is always the first felt. + 1 + data.iter().map(T::cairo_serialized_size).sum::() + } + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + let mut out: Vec = vec![rust.len().into()]; + rust.iter().for_each(|r| out.extend(T::cairo_serialize(r))); + out + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize an array: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + let len: usize = usize::from_str_radix(format!("{:x}", felts[offset]).as_str(), 16) + .map_err(|_| { + Error::Deserialize("First felt of an array must fit into usize".to_string()) + })?; + + if offset + len >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize an array of length {}: offset ({}) : buffer {:?}", + len, offset, felts, + ))); + } + + let mut out: Vec = vec![]; + let mut offset = offset + 1; + + loop { + if out.len() == len { + break; + } + + let rust: RT = T::cairo_deserialize(felts, offset)?; + offset += T::cairo_serialized_size(&rust); + out.push(rust); + } + + Ok(out) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize_array() { + let v: Vec = vec![1, 2, 3]; + let felts = Vec::::cairo_serialize(&v); + assert_eq!(felts.len(), 4); + assert_eq!(felts[0], FieldElement::from(3_u32)); + assert_eq!(felts[1], FieldElement::ONE); + assert_eq!(felts[2], FieldElement::TWO); + assert_eq!(felts[3], FieldElement::THREE); + } + + #[test] + fn test_deserialize_array() { + let felts: Vec = vec![ + FieldElement::from(2_u32), + FieldElement::from(123_u32), + FieldElement::from(9988_u32), + ]; + + let vals = Vec::::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(vals.len(), 2); + assert_eq!(vals[0], 123_u32); + assert_eq!(vals[1], 9988_u32); + } + + #[test] + fn test_serialize_array_nested() { + let v: Vec> = vec![vec![1, 2], vec![3]]; + let felts = Vec::>::cairo_serialize(&v); + assert_eq!(felts.len(), 6); + assert_eq!(felts[0], FieldElement::TWO); + assert_eq!(felts[1], FieldElement::TWO); + assert_eq!(felts[2], FieldElement::ONE); + assert_eq!(felts[3], FieldElement::TWO); + assert_eq!(felts[4], FieldElement::ONE); + assert_eq!(felts[5], FieldElement::THREE); + } + + #[test] + fn test_deserialize_array_nested() { + let felts: Vec = vec![ + FieldElement::TWO, + FieldElement::TWO, + FieldElement::ONE, + FieldElement::TWO, + FieldElement::ONE, + FieldElement::THREE, + ]; + + let vals = Vec::>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(vals.len(), 2); + assert_eq!(vals[0], vec![1, 2]); + assert_eq!(vals[1], vec![3]); + } + + #[test] + fn test_serialize_array_tuple() { + let v: Vec<(u32, FieldElement)> = vec![(12, FieldElement::TWO)]; + let felts = Vec::<(u32, FieldElement)>::cairo_serialize(&v); + assert_eq!(felts.len(), 3); + assert_eq!(felts[0], FieldElement::from(1_u32)); + assert_eq!(felts[1], FieldElement::from(12_u32)); + assert_eq!(felts[2], FieldElement::TWO); + } + + #[test] + fn test_deserialize_array_tuple() { + let felts: Vec = vec![ + FieldElement::from(1_u32), + FieldElement::from(12_u32), + FieldElement::TWO, + ]; + + let vals = Vec::<(u32, FieldElement)>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(vals.len(), 1); + assert_eq!(vals[0], (12, FieldElement::TWO)); + } +} diff --git a/crates/cairo-serde/src/types/array_legacy.rs b/crates/cairo-serde/src/types/array_legacy.rs new file mode 100644 index 0000000..a28c20d --- /dev/null +++ b/crates/cairo-serde/src/types/array_legacy.rs @@ -0,0 +1,82 @@ +//! Dedicated struct for cairo 0 arrays, where len is not prefixed. +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +#[derive(Debug, Clone, PartialEq)] +pub struct CairoArrayLegacy(pub Vec); + +impl CairoArrayLegacy { + pub fn from_slice(slice: &[T]) -> Self { + Self(slice.to_vec()) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl From> for CairoArrayLegacy { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl CairoSerde for CairoArrayLegacy +where + T: CairoSerde, +{ + type RustType = CairoArrayLegacy; + + const SERIALIZED_SIZE: Option = None; + + #[inline] + fn cairo_serialized_size(rust: &Self::RustType) -> usize { + let data = &rust.0; + // In cairo 0, the length is always passed as an argument. + data.iter().map(T::cairo_serialized_size).sum::() + } + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + let mut out: Vec = vec![]; + rust.0 + .iter() + .for_each(|r| out.extend(T::cairo_serialize(r))); + out + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize an array: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + let mut out: Vec = vec![]; + let mut offset = offset; + let len = felts[offset - 1]; + + if FieldElement::from(offset) + len >= FieldElement::from(felts.len()) { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize an array of length {}: offset ({}) : buffer {:?}", + len, offset, felts, + ))); + } + + loop { + if FieldElement::from(out.len()) == len { + break; + } + + let rust: RT = T::cairo_deserialize(felts, offset)?; + offset += T::cairo_serialized_size(&rust); + out.push(rust); + } + + Ok(CairoArrayLegacy(out)) + } +} diff --git a/crates/cairo-serde/src/types/boolean.rs b/crates/cairo-serde/src/types/boolean.rs new file mode 100644 index 0000000..624b45d --- /dev/null +++ b/crates/cairo-serde/src/types/boolean.rs @@ -0,0 +1,52 @@ +//! CairoSerde implementation for bool. +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +impl CairoSerde for bool { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + vec![FieldElement::from(*rust as u32)] + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize a boolean: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + if felts[offset] == FieldElement::ONE { + Ok(true) + } else { + Ok(false) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize_bool() { + let v = true; + let felts = bool::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::ONE); + + let v = false; + let felts = bool::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::ZERO); + } + + #[test] + fn test_deserialize_bool() { + let felts = vec![FieldElement::ZERO, FieldElement::ONE, FieldElement::TWO]; + assert!(!bool::cairo_deserialize(&felts, 0).unwrap()); + assert!(bool::cairo_deserialize(&felts, 1).unwrap()); + assert!(!bool::cairo_deserialize(&felts, 2).unwrap()); + } +} diff --git a/crates/cairo-serde/src/types/felt.rs b/crates/cairo-serde/src/types/felt.rs new file mode 100644 index 0000000..92c50a4 --- /dev/null +++ b/crates/cairo-serde/src/types/felt.rs @@ -0,0 +1,51 @@ +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +impl CairoSerde for FieldElement { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + vec![*rust] + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize a felt: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + Ok(felts[offset]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize_field_element() { + let f = FieldElement::ZERO; + let felts = FieldElement::cairo_serialize(&f); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::ZERO); + } + + #[test] + fn test_deserialize_field_element() { + let felts = vec![FieldElement::ZERO, FieldElement::ONE, FieldElement::TWO]; + assert_eq!( + FieldElement::cairo_deserialize(&felts, 0).unwrap(), + FieldElement::ZERO + ); + assert_eq!( + FieldElement::cairo_deserialize(&felts, 1).unwrap(), + FieldElement::ONE + ); + assert_eq!( + FieldElement::cairo_deserialize(&felts, 2).unwrap(), + FieldElement::TWO + ); + } +} diff --git a/crates/cairo-serde/src/types/integers.rs b/crates/cairo-serde/src/types/integers.rs new file mode 100644 index 0000000..8e7f840 --- /dev/null +++ b/crates/cairo-serde/src/types/integers.rs @@ -0,0 +1,243 @@ +//! CairoSerde implementation for integers (signed/unsigned). +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +macro_rules! implement_trait_for_unsigned { + ($type:ty) => { + impl CairoSerde for $type { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + vec![FieldElement::from(*rust)] + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize a unsigned integer: offset ({}) : buffer {:?}", + offset, + felts, + ))); + } + + let temp: u128 = felts[offset].try_into().unwrap(); + Ok(temp as $type) + } + } + }; +} + +macro_rules! implement_trait_for_signed { + ($type:ty) => { + impl CairoSerde for $type { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + vec![FieldElement::from(*rust as usize)] + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize a signed integer: offset ({}) : buffer {:?}", + offset, + felts, + ))); + } + + let temp: u128 = felts[offset].try_into().unwrap(); + Ok(temp as $type) + } + } + }; +} + +implement_trait_for_unsigned!(u8); +implement_trait_for_unsigned!(u16); +implement_trait_for_unsigned!(u32); +implement_trait_for_unsigned!(u64); +implement_trait_for_unsigned!(u128); +implement_trait_for_unsigned!(usize); + +implement_trait_for_signed!(i8); +implement_trait_for_signed!(i16); +implement_trait_for_signed!(i32); +implement_trait_for_signed!(i64); +implement_trait_for_signed!(i128); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize_u8() { + let v = 12_u8; + let felts = u8::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(12_u8)); + } + + #[test] + fn test_deserialize_u8() { + let felts = vec![FieldElement::from(12_u8), FieldElement::from(10_u8)]; + assert_eq!(u8::cairo_deserialize(&felts, 0).unwrap(), 12); + assert_eq!(u8::cairo_deserialize(&felts, 1).unwrap(), 10); + } + + #[test] + fn test_serialize_u16() { + let v = 12_u16; + let felts = u16::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(12_u16)); + } + + #[test] + fn test_deserialize_u16() { + let felts = vec![FieldElement::from(12_u16), FieldElement::from(10_u8)]; + assert_eq!(u16::cairo_deserialize(&felts, 0).unwrap(), 12); + assert_eq!(u16::cairo_deserialize(&felts, 1).unwrap(), 10); + } + + #[test] + fn test_serialize_u32() { + let v = 123_u32; + let felts = u32::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(123_u32)); + } + + #[test] + fn test_deserialize_u32() { + let felts = vec![FieldElement::from(123_u32), FieldElement::from(99_u32)]; + assert_eq!(u32::cairo_deserialize(&felts, 0).unwrap(), 123); + assert_eq!(u32::cairo_deserialize(&felts, 1).unwrap(), 99); + } + + #[test] + fn test_serialize_u64() { + let v = 123_u64; + let felts = u64::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(123_u64)); + } + + #[test] + fn test_deserialize_u64() { + let felts = vec![FieldElement::from(123_u64), FieldElement::from(99_u64)]; + assert_eq!(u64::cairo_deserialize(&felts, 0).unwrap(), 123); + assert_eq!(u64::cairo_deserialize(&felts, 1).unwrap(), 99); + } + + #[test] + fn test_serialize_u128() { + let v = 123_u128; + let felts = u128::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(123_u128)); + } + + #[test] + fn test_deserialize_u128() { + let felts = vec![FieldElement::from(123_u128), FieldElement::from(99_u128)]; + assert_eq!(u128::cairo_deserialize(&felts, 0).unwrap(), 123); + assert_eq!(u128::cairo_deserialize(&felts, 1).unwrap(), 99); + } + + #[test] + fn test_serialize_usize() { + let v = 123; + let felts = usize::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(123_u128)); + } + + #[test] + fn test_deserialize_usize() { + let felts = vec![FieldElement::from(123_u128), FieldElement::from(99_u64)]; + assert_eq!(usize::cairo_deserialize(&felts, 0).unwrap(), 123); + assert_eq!(usize::cairo_deserialize(&felts, 1).unwrap(), 99); + } + + #[test] + fn test_serialize_i8() { + let v = i8::MAX; + let felts = i8::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(i8::MAX as u8)); + } + + #[test] + fn test_deserialize_i8() { + let felts = vec![ + FieldElement::from(i8::MAX as u8), + FieldElement::from(i8::MAX as u8), + ]; + assert_eq!(i8::cairo_deserialize(&felts, 0).unwrap(), i8::MAX); + assert_eq!(i8::cairo_deserialize(&felts, 1).unwrap(), i8::MAX); + } + + #[test] + fn test_serialize_i16() { + let v = i16::MAX; + let felts = i16::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(i16::MAX as u16)); + } + + #[test] + fn test_deserialize_i16() { + let felts = vec![ + FieldElement::from(i16::MAX as u16), + FieldElement::from(i16::MAX as u16), + ]; + assert_eq!(i16::cairo_deserialize(&felts, 0).unwrap(), i16::MAX); + assert_eq!(i16::cairo_deserialize(&felts, 1).unwrap(), i16::MAX); + } + + #[test] + fn test_serialize_i32() { + let v = i32::MAX; + let felts = i32::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(i32::MAX as u32)); + } + + #[test] + fn test_deserialize_i32() { + let felts = vec![ + FieldElement::from(i32::MAX as u32), + FieldElement::from(i32::MAX as u32), + ]; + assert_eq!(i32::cairo_deserialize(&felts, 0).unwrap(), i32::MAX); + assert_eq!(i32::cairo_deserialize(&felts, 1).unwrap(), i32::MAX); + } + + #[test] + fn test_serialize_i64() { + let v = i64::MAX; + let felts = i64::cairo_serialize(&v); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(i64::MAX as u64)); + } + + #[test] + fn test_deserialize_i64() { + let felts = vec![ + FieldElement::from(i64::MAX as u64), + FieldElement::from(i64::MAX as u64), + ]; + assert_eq!(i64::cairo_deserialize(&felts, 0).unwrap(), i64::MAX); + assert_eq!(i64::cairo_deserialize(&felts, 1).unwrap(), i64::MAX); + } + + #[test] + fn test_deserialize_i128() { + let felts = vec![ + FieldElement::from(i128::MAX as u128), + FieldElement::from(i128::MAX as u128), + ]; + assert_eq!(i128::cairo_deserialize(&felts, 0).unwrap(), i128::MAX); + assert_eq!(i128::cairo_deserialize(&felts, 1).unwrap(), i128::MAX); + } +} diff --git a/crates/cairo-serde/src/types/mod.rs b/crates/cairo-serde/src/types/mod.rs new file mode 100644 index 0000000..fe1b620 --- /dev/null +++ b/crates/cairo-serde/src/types/mod.rs @@ -0,0 +1,117 @@ +pub mod array; +pub mod array_legacy; +pub mod boolean; +pub mod felt; +pub mod integers; +pub mod option; +pub mod result; +pub mod starknet; +pub mod tuple; + +#[cfg(test)] +mod tests { + use crate::CairoSerde; + use ::starknet::core::types::FieldElement; + + #[test] + fn test_serialize_several_values() { + let o = Some(u32::MAX); + let r = Err(FieldElement::TWO); + let a: Vec = vec![1, 2, 3]; + + let mut felts = vec![]; + felts.extend(Option::::cairo_serialize(&o)); + felts.extend(Result::::cairo_serialize(&r)); + felts.extend(Vec::::cairo_serialize(&a)); + + assert_eq!(felts.len(), 8); + assert_eq!(felts[0], FieldElement::ZERO); + assert_eq!(felts[1], FieldElement::from(u32::MAX)); + assert_eq!(felts[2], FieldElement::ONE); + assert_eq!(felts[3], FieldElement::TWO); + assert_eq!(felts[4], FieldElement::THREE); + assert_eq!(felts[5], FieldElement::ONE); + assert_eq!(felts[6], FieldElement::TWO); + assert_eq!(felts[7], FieldElement::THREE); + } + + #[test] + fn test_serialize_several_values_with_unit() { + let o = Some(u32::MAX); + let r = Ok(()); + let a: Vec = vec![1, 2, 3]; + + let mut felts = vec![]; + felts.extend(Option::::cairo_serialize(&o)); + felts.extend(Result::<(), FieldElement>::cairo_serialize(&r)); + felts.extend(Vec::::cairo_serialize(&a)); + + assert_eq!(felts.len(), 7); + assert_eq!(felts[0], FieldElement::ZERO); + assert_eq!(felts[1], FieldElement::from(u32::MAX)); + assert_eq!(felts[2], FieldElement::ZERO); + assert_eq!(felts[3], FieldElement::THREE); + assert_eq!(felts[4], FieldElement::ONE); + assert_eq!(felts[5], FieldElement::TWO); + assert_eq!(felts[6], FieldElement::THREE); + } + + #[test] + fn test_deserialize_several_values() { + let felts = vec![ + FieldElement::ZERO, + FieldElement::from(u32::MAX), + FieldElement::ONE, + FieldElement::TWO, + FieldElement::THREE, + FieldElement::ONE, + FieldElement::TWO, + FieldElement::THREE, + ]; + + let mut offset = 0; + + let o = Option::::cairo_deserialize(&felts, offset).unwrap(); + offset += Option::::cairo_serialized_size(&o); + + let r = Result::::cairo_deserialize(&felts, offset).unwrap(); + offset += Result::::cairo_serialized_size(&r); + + let a = Vec::::cairo_deserialize(&felts, offset).unwrap(); + offset += Vec::::cairo_serialized_size(&a); + + assert_eq!(o, Some(u32::MAX)); + assert_eq!(r, Err(FieldElement::TWO)); + assert_eq!(a, vec![1, 2, 3]); + assert_eq!(offset, felts.len()); + } + + #[test] + fn test_deserialize_several_values_with_unit() { + let felts = vec![ + FieldElement::ZERO, + FieldElement::from(u32::MAX), + FieldElement::ZERO, + FieldElement::THREE, + FieldElement::ONE, + FieldElement::TWO, + FieldElement::THREE, + ]; + + let mut offset = 0; + + let o = Option::::cairo_deserialize(&felts, offset).unwrap(); + offset += Option::::cairo_serialized_size(&o); + + let r = Result::<(), FieldElement>::cairo_deserialize(&felts, offset).unwrap(); + offset += Result::<(), FieldElement>::cairo_serialized_size(&r); + + let a = Vec::::cairo_deserialize(&felts, offset).unwrap(); + offset += Vec::::cairo_serialized_size(&a); + + assert_eq!(o, Some(u32::MAX)); + assert_eq!(r, Ok(())); + assert_eq!(a, vec![1, 2, 3]); + assert_eq!(offset, felts.len()); + } +} diff --git a/crates/cairo-serde/src/types/option.rs b/crates/cairo-serde/src/types/option.rs new file mode 100644 index 0000000..79486cf --- /dev/null +++ b/crates/cairo-serde/src/types/option.rs @@ -0,0 +1,156 @@ +//! CairoSerde implementation for Option. +//! +//! In cairo, `Some` is the first field and `None` the second one. +//! To follow the serialization rule, `Some` has index 0, and `None` index 1. +//! +//! https://github.com/starkware-libs/cairo/blob/main/corelib/src/option.cairo#L6 +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +impl CairoSerde for Option +where + T: CairoSerde, +{ + type RustType = Option; + + #[inline] + fn cairo_serialized_size(rust: &Self::RustType) -> usize { + match rust { + Some(d) => 1 + T::cairo_serialized_size(d), + None => 1, + } + } + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + let mut out = vec![]; + + match rust { + Some(r) => { + out.push(FieldElement::ZERO); + out.extend(T::cairo_serialize(r)); + } + None => out.push(FieldElement::ONE), + }; + + out + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize an Option: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + let idx = felts[offset]; + + if idx == FieldElement::ZERO { + // + 1 as the offset value is the index of the enum. + Ok(Option::Some(T::cairo_deserialize(felts, offset + 1)?)) + } else if idx == FieldElement::ONE { + Ok(Option::None) + } else { + Err(Error::Deserialize( + "Option is expected 0 or 1 index only".to_string(), + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use starknet::core::types::FieldElement; + + #[test] + fn test_option_some_cairo_serialize() { + let o = Some(u32::MAX); + let felts = Option::::cairo_serialize(&o); + assert_eq!(felts.len(), 2); + assert_eq!(felts[0], FieldElement::ZERO); + assert_eq!(felts[1], FieldElement::from(u32::MAX)); + } + + #[test] + fn test_option_some_cairo_deserialize() { + let felts = vec![FieldElement::ZERO, FieldElement::from(u32::MAX)]; + let o = Option::::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(o, Some(u32::MAX)); + + let felts = vec![ + FieldElement::THREE, + FieldElement::ZERO, + FieldElement::from(u32::MAX), + ]; + let o = Option::::cairo_deserialize(&felts, 1).unwrap(); + assert_eq!(o, Some(u32::MAX)); + } + + #[test] + fn test_option_some_unit_cairo_serialize() { + let o = Some(()); + let felts = Option::<()>::cairo_serialize(&o); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::ZERO); + } + + #[test] + fn test_option_some_unit_cairo_deserialize() { + let felts = vec![FieldElement::ZERO]; + let o = Option::<()>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(o, Some(())); + } + + #[test] + fn test_option_some_array_cairo_serialize() { + let o = Some(vec![u32::MAX, u32::MAX]); + let felts = Option::>::cairo_serialize(&o); + assert_eq!(felts.len(), 4); + assert_eq!(felts[0], FieldElement::ZERO); + assert_eq!(felts[1], FieldElement::from(2_u32)); + assert_eq!(felts[2], FieldElement::from(u32::MAX)); + assert_eq!(felts[3], FieldElement::from(u32::MAX)); + } + + #[test] + fn test_option_some_array_cairo_deserialize() { + let felts = vec![ + FieldElement::ZERO, + FieldElement::from(2_u32), + FieldElement::from(u32::MAX), + FieldElement::from(u32::MAX), + ]; + let o = Option::>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(o, Some(vec![u32::MAX, u32::MAX])); + + let felts = vec![ + FieldElement::THREE, + FieldElement::ZERO, + FieldElement::from(2_u32), + FieldElement::from(u32::MAX), + FieldElement::from(u32::MAX), + ]; + let o = Option::>::cairo_deserialize(&felts, 1).unwrap(); + assert_eq!(o, Some(vec![u32::MAX, u32::MAX])); + } + + #[test] + fn test_option_none_cairo_serialize() { + let o: Option = None; + let felts = Option::::cairo_serialize(&o); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::ONE); + } + + #[test] + fn test_option_none_cairo_deserialize() { + let felts = vec![FieldElement::ONE]; + let o = Option::::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(o, None); + + let felts = vec![FieldElement::THREE, FieldElement::ONE]; + let o = Option::::cairo_deserialize(&felts, 1).unwrap(); + assert_eq!(o, None); + } +} diff --git a/crates/cairo-serde/src/types/result.rs b/crates/cairo-serde/src/types/result.rs new file mode 100644 index 0000000..c5a8c07 --- /dev/null +++ b/crates/cairo-serde/src/types/result.rs @@ -0,0 +1,113 @@ +//! CairoSerde implementation for Result. +//! +//! https://github.com/starkware-libs/cairo/blob/main/corelib/src/result.cairo#L6 +use crate::{CairoSerde, Error as CairoError, Result as CairoResult}; +use starknet::core::types::FieldElement; + +impl CairoSerde for Result +where + T: CairoSerde, + E: CairoSerde, +{ + type RustType = Result; + + #[inline] + fn cairo_serialized_size(rust: &Self::RustType) -> usize { + match rust { + Ok(d) => 1 + T::cairo_serialized_size(d), + Err(e) => 1 + E::cairo_serialized_size(e), + } + } + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + let mut out = vec![]; + + match rust { + Result::Ok(r) => { + out.push(FieldElement::ZERO); + out.extend(T::cairo_serialize(r)); + } + Result::Err(e) => { + out.push(FieldElement::ONE); + out.extend(E::cairo_serialize(e)); + } + }; + + out + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> CairoResult { + if offset >= felts.len() { + return Err(CairoError::Deserialize(format!( + "Buffer too short to deserialize a Result: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + let idx = felts[offset]; + + if idx == FieldElement::ZERO { + // + 1 as the offset value is the index of the enum. + CairoResult::Ok(Ok(T::cairo_deserialize(felts, offset + 1)?)) + } else if idx == FieldElement::ONE { + CairoResult::Ok(Err(E::cairo_deserialize(felts, offset + 1)?)) + } else { + Err(CairoError::Deserialize( + "Result is expected 0 or 1 index only".to_string(), + )) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use starknet::core::types::FieldElement; + + #[test] + fn test_result_ok_cairo_serialize() { + let r = Ok(u32::MAX); + let felts = Result::::cairo_serialize(&r); + assert_eq!(felts.len(), 2); + assert_eq!(felts[0], FieldElement::ZERO); + assert_eq!(felts[1], FieldElement::from(u32::MAX)); + } + + #[test] + fn test_result_ok_cairo_deserialize() { + let felts = vec![FieldElement::ZERO, FieldElement::from(u32::MAX)]; + let r = Result::::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(r, Ok(u32::MAX)); + } + + #[test] + fn test_result_ok_unit_cairo_serialize() { + let r = Ok(()); + let felts = Result::<(), FieldElement>::cairo_serialize(&r); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::ZERO); + } + + #[test] + fn test_result_ok_unit_cairo_deserialize() { + let felts = vec![FieldElement::ZERO]; + let r = Result::<(), FieldElement>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(r, Ok(())); + } + + #[test] + fn test_result_err_cairo_serialize() { + let r = Err(FieldElement::ONE); + let felts = Result::::cairo_serialize(&r); + assert_eq!(felts.len(), 2); + assert_eq!(felts[0], FieldElement::ONE); + assert_eq!(felts[1], FieldElement::ONE); + } + + #[test] + fn test_result_err_cairo_deserialize() { + let felts = vec![FieldElement::ONE, FieldElement::ONE]; + let r = Result::::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(r, Err(FieldElement::ONE)); + } +} diff --git a/crates/cairo-serde/src/types/starknet.rs b/crates/cairo-serde/src/types/starknet.rs new file mode 100644 index 0000000..c4f3037 --- /dev/null +++ b/crates/cairo-serde/src/types/starknet.rs @@ -0,0 +1,180 @@ +//! CairoSerde implementation for starknet types. +//! +//! They are alf `FieldElement` under the hood. +use crate::{CairoSerde, Error, Result}; +use starknet::core::types::FieldElement; + +/// ContractAddress. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ContractAddress(pub FieldElement); + +impl From for ContractAddress { + fn from(item: FieldElement) -> Self { + Self(item) + } +} + +impl From for FieldElement { + fn from(item: ContractAddress) -> Self { + item.0 + } +} + +impl CairoSerde for ContractAddress { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + FieldElement::cairo_serialize(&rust.0) + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize a ContractAddress: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + Ok(ContractAddress(FieldElement::cairo_deserialize( + felts, offset, + )?)) + } +} + +/// ClassHash. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct ClassHash(pub FieldElement); + +impl From for ClassHash { + fn from(item: FieldElement) -> Self { + Self(item) + } +} + +impl From for FieldElement { + fn from(item: ClassHash) -> Self { + item.0 + } +} + +impl CairoSerde for ClassHash { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + FieldElement::cairo_serialize(&rust.0) + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize a ClassHash: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + Ok(ClassHash(FieldElement::cairo_deserialize(felts, offset)?)) + } +} + +/// EthAddress. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct EthAddress(pub FieldElement); + +impl From for EthAddress { + fn from(item: FieldElement) -> Self { + Self(item) + } +} + +impl From for FieldElement { + fn from(item: EthAddress) -> Self { + item.0 + } +} + +impl CairoSerde for EthAddress { + type RustType = Self; + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + FieldElement::cairo_serialize(&rust.0) + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + if offset >= felts.len() { + return Err(Error::Deserialize(format!( + "Buffer too short to deserialize an EthAddress: offset ({}) : buffer {:?}", + offset, felts, + ))); + } + + Ok(EthAddress(FieldElement::cairo_deserialize(felts, offset)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_contract_address_cairo_serialize() { + let contract_address = ContractAddress(FieldElement::from(1_u32)); + let felts = ContractAddress::cairo_serialize(&contract_address); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(1_u32)); + } + + #[test] + fn test_contract_address_cairo_deserialize() { + let felts = vec![FieldElement::from(1_u32)]; + let contract_address = ContractAddress::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(contract_address, ContractAddress(FieldElement::from(1_u32))) + } + + #[test] + fn test_class_hash_cairo_serialize() { + let class_hash = ClassHash(FieldElement::from(1_u32)); + let felts = ClassHash::cairo_serialize(&class_hash); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(1_u32)); + } + + #[test] + fn test_class_hash_cairo_deserialize() { + let felts = vec![FieldElement::from(1_u32)]; + let class_hash = ClassHash::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(class_hash, ClassHash(FieldElement::from(1_u32))) + } + + #[test] + fn test_eth_address_cairo_serialize() { + let eth_address = EthAddress(FieldElement::from(1_u32)); + let felts = EthAddress::cairo_serialize(ð_address); + assert_eq!(felts.len(), 1); + assert_eq!(felts[0], FieldElement::from(1_u32)); + } + + #[test] + fn test_eth_address_cairo_deserialize() { + let felts = vec![FieldElement::from(1_u32)]; + let eth_address = EthAddress::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(eth_address, EthAddress(FieldElement::from(1_u32))) + } + + #[test] + fn test_contract_address_from() { + let contract_address = ContractAddress::from(FieldElement::from(1_u32)); + assert_eq!(contract_address, ContractAddress(FieldElement::from(1_u32))) + } + + #[test] + fn test_class_hash_from() { + let class_hash = ClassHash::from(FieldElement::from(1_u32)); + assert_eq!(class_hash, ClassHash(FieldElement::from(1_u32))) + } + + #[test] + fn test_eth_address_from() { + let eth_address = EthAddress::from(FieldElement::from(1_u32)); + assert_eq!(eth_address, EthAddress(FieldElement::from(1_u32))) + } +} diff --git a/crates/cairo-serde/src/types/tuple.rs b/crates/cairo-serde/src/types/tuple.rs new file mode 100644 index 0000000..dd4eb9b --- /dev/null +++ b/crates/cairo-serde/src/types/tuple.rs @@ -0,0 +1,110 @@ +//! CairoSerde implementation for tuples. +use crate::{CairoSerde, Result}; +use starknet::core::types::FieldElement; + +impl CairoSerde for () { + type RustType = Self; + + #[inline] + fn cairo_serialized_size(_rust: &Self::RustType) -> usize { + 0 + } + + fn cairo_serialize(_rust: &Self::RustType) -> Vec { + vec![] + } + + fn cairo_deserialize(_felts: &[FieldElement], _offset: usize) -> Result { + Ok(()) + } +} + +macro_rules! impl_tuples { + ($num:expr, $( $ty:ident : $rt:ident : $var:ident : $no:tt ),+ $(,)?) => { + impl<$( $ty, $rt ),+> CairoSerde for ($( $ty, )+) + where + $($ty: CairoSerde,)+ + { + type RustType = ($( $rt ),*); + + const SERIALIZED_SIZE: Option = None; + + #[inline] + fn cairo_serialized_size(rust: &Self::RustType) -> usize { + let mut size = 0; + $( + size += $ty::cairo_serialized_size(& rust.$no); + )* + + size + } + + fn cairo_serialize(rust: &Self::RustType) -> Vec { + let mut out: Vec = vec![]; + + $( out.extend($ty::cairo_serialize(& rust.$no)); )* + + out + } + + fn cairo_deserialize(felts: &[FieldElement], offset: usize) -> Result { + let mut offset = offset; + + $( + let $var : $rt = $ty::cairo_deserialize(felts, offset)?; + offset += $ty::cairo_serialized_size(& $var); + )* + + // Remove warning. + let _offset = offset; + + Ok(($( $var ),*)) + } + } + } +} + +impl_tuples!(2, A:RA:r0:0, B:RB:r1:1); +impl_tuples!(3, A:RA:r0:0, B:RB:r1:1, C:RC:r2:2); +impl_tuples!(4, A:RA:r0:0, B:RB:r1:1, C:RC:r2:2, D:RD:r3:3); +impl_tuples!(5, A:RA:r0:0, B:RB:r1:1, C:RC:r2:2, D:RD:r3:3, E:RE:r4:4); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serialize_tuple2() { + let v = (FieldElement::ONE, 128_u32); + let felts = <(FieldElement, u32)>::cairo_serialize(&v); + assert_eq!(felts.len(), 2); + assert_eq!(felts[0], FieldElement::ONE); + assert_eq!(felts[1], FieldElement::from(128_u32)); + } + + #[test] + fn test_deserialize_tuple2() { + let felts = vec![FieldElement::THREE, 99_u32.into()]; + let vals = <(FieldElement, u32)>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(vals.0, FieldElement::THREE); + assert_eq!(vals.1, 99_u32); + } + + #[test] + fn test_serialize_tuple2_array() { + let v = (vec![FieldElement::ONE], 128_u32); + let felts = <(Vec, u32)>::cairo_serialize(&v); + assert_eq!(felts.len(), 3); + assert_eq!(felts[0], FieldElement::ONE); + assert_eq!(felts[1], FieldElement::ONE); + assert_eq!(felts[2], FieldElement::from(128_u32)); + } + + #[test] + fn test_deserialize_tuple2_array() { + let felts = vec![FieldElement::ONE, FieldElement::ONE, 99_u32.into()]; + let vals = <(Vec, u32)>::cairo_deserialize(&felts, 0).unwrap(); + assert_eq!(vals.0, vec![FieldElement::ONE]); + assert_eq!(vals.1, 99_u32); + } +} diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml new file mode 100644 index 0000000..5e5c6c2 --- /dev/null +++ b/crates/parser/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cainome-parser" +version = "0.1.0" +edition = "2021" + +[dependencies] +starknet.workspace = true +thiserror.workspace = true +syn = { version = "2.0", features = [ "extra-traits" ]} +quote = "1.0" +serde_json.workspace = true diff --git a/crates/parser/README.md b/crates/parser/README.md new file mode 100644 index 0000000..45fc228 --- /dev/null +++ b/crates/parser/README.md @@ -0,0 +1,72 @@ +# Parser (Cainome) + +A run-time library to generate an intermediate representation of Cairo ABI. + +The parser is in charge to parse the ABI entries and convert then into tokens. Those tokens represent the ABI in a comprehensive manner to then be used for lowering into different languages. + +# Tokens + +The Cairo ABI is represented by a set of 6 tokens: + +- basic (`CoreBasic`): corelib types, which are every types starting with `core::` that can fit into a single felt and the unit (`()`) type. This excludes `Array`, which is processed on it's own token. +- array (`Array`): `Array` and `Span` are included in this token. `Span` is normally a struct, but considered as `Array` by the parser. +- tuple (`Tuple`): tuple of any length >= 1. +- composite (`Composite`): any type defined in the ABI as a struct or an enum. All composite type name is automatically converted into `PascalCase`. +- function (`Function`): views and externals functions. +- generic argument (`GenericArg`): a generic argument, resolved with it's letter (`A`, `B`...). + +# Genericity + +As Cairo is a language that support generic arguments, the ABI does not include any information about the generic argument typing. Types are totally flatten in the ABI. + +```rust +struct GenericOne { + a: A, + b: felt252, + c: u256, +} +``` + +Will exist in the ABI as many time as a function uses `GenericOne` with a different `A` value. +For instance, if one function output is: + +``` +fn my_func(self: @ContractState) -> GenericOne; +``` + +The ABI will contain: + +```json + { + "type": "struct", + "name": "contracts::abicov::structs::GenericOne::", + "members": [ + { + "name": "a", + "type": "core::felt252" + }, + { + "name": "b", + "type": "core::felt252" + }, + { + "name": "c", + "type": "core::integer::u256" + } + ] + }, +``` + +And here as you can see, we've lost the information about the genericity. + +To deal with that, Cainome has (for now) a very simple algorithm: + +1. It gathers all the structs and enums with the exact same type path `contracts::abicov::structs::GenericOne` in this example. +2. It resolves the genericity of `structs` and `enums`, meaning that if the generic argument is `core::felt252`, all the tokens found in the members (recursively) will have the `CoreBasic` token replaced by `GenericArg` and the corresponding letter. In the example above, the member `a` will become `GenericArg("A")`. +3. Finally, the tokens are ordered in a map with `structs`, `enums` and `functions`. + +# Events + +Events at top level are `enums`. And those enums, have some variants that are `struct` and others are `enums`. The parser clearly labels any composite that is an event, which allow further processing dedicated for the events. + +Auto deserialization from `EmittedEvent` coming soon. diff --git a/crates/parser/src/abi/conversions.rs b/crates/parser/src/abi/conversions.rs new file mode 100644 index 0000000..1416d28 --- /dev/null +++ b/crates/parser/src/abi/conversions.rs @@ -0,0 +1,177 @@ +use starknet::core::types::contract::{ + AbiEnum, AbiEventEnum, AbiEventStruct, AbiStruct, EventFieldKind, + StateMutability as StarknetStateMutability, +}; + +use crate::tokens::{CompositeInner, CompositeInnerKind, CompositeType, StateMutability, Token}; +use crate::Error; + +impl From for StateMutability { + fn from(value: StarknetStateMutability) -> Self { + match value { + StarknetStateMutability::External => StateMutability::External, + StarknetStateMutability::View => StateMutability::View, + } + } +} + +impl From for CompositeInnerKind { + fn from(value: EventFieldKind) -> Self { + match value { + EventFieldKind::Key => CompositeInnerKind::Key, + EventFieldKind::Data => CompositeInnerKind::Data, + EventFieldKind::Nested => CompositeInnerKind::Nested, + EventFieldKind::Flat => CompositeInnerKind::Flat, + } + } +} + +impl TryFrom<&AbiStruct> for Token { + type Error = Error; + + fn try_from(value: &AbiStruct) -> Result { + let mut t = Token::parse(&value.name)?; + + if let Token::Composite(ref mut c) = t { + c.r#type = CompositeType::Struct; + + for (i, m) in value.members.iter().enumerate() { + c.inners.push(CompositeInner { + index: i, + name: m.name.clone(), + token: Token::parse(&m.r#type).unwrap(), + kind: CompositeInnerKind::NotUsed, + }); + } + + if !c.generic_args.is_empty() { + let mut token = Token::Composite(c.clone()); + for (g_name, g_token) in c.generic_args.iter() { + token = token.resolve_generic(g_name, &g_token.type_path()); + } + + return Ok(token); + } + + Ok(t) + } else { + Err(Error::ParsingFailed(format!( + "AbiStruct is expected to be a Composite token, got `{:?}`", + value, + ))) + } + } +} + +impl TryFrom<&AbiEnum> for Token { + type Error = Error; + + fn try_from(value: &AbiEnum) -> Result { + let mut t = Token::parse(&value.name)?; + + if let Token::Composite(ref mut c) = t { + c.r#type = CompositeType::Enum; + + for (i, v) in value.variants.iter().enumerate() { + c.inners.push(CompositeInner { + index: i, + name: v.name.clone(), + token: Token::parse(&v.r#type).unwrap(), + kind: CompositeInnerKind::NotUsed, + }); + } + + if !c.generic_args.is_empty() { + let mut token = Token::Composite(c.clone()); + for (g_name, g_token) in c.generic_args.iter() { + token = token.resolve_generic(g_name, &g_token.type_path()); + } + + return Ok(token); + } + + Ok(t) + } else { + Err(Error::ParsingFailed(format!( + "AbiEnum is expected to be a Composite token, got `{:?}`", + value, + ))) + } + } +} + +impl TryFrom<&AbiEventStruct> for Token { + type Error = Error; + + fn try_from(value: &AbiEventStruct) -> Result { + let mut t = Token::parse(&value.name)?; + + if let Token::Composite(ref mut c) = t { + c.r#type = CompositeType::Struct; + c.is_event = true; + + for (i, m) in value.members.iter().enumerate() { + c.inners.push(CompositeInner { + index: i, + name: m.name.clone(), + token: Token::parse(&m.r#type).unwrap(), + kind: m.kind.clone().into(), + }); + } + + if !c.generic_args.is_empty() { + let mut token = Token::Composite(c.clone()); + for (g_name, g_token) in c.generic_args.iter() { + token = token.resolve_generic(g_name, &g_token.type_path()); + } + + return Ok(token); + } + + Ok(t) + } else { + Err(Error::ParsingFailed(format!( + "AbiEventStruct is expected to be a Composite token, got `{:?}`", + value, + ))) + } + } +} + +impl TryFrom<&AbiEventEnum> for Token { + type Error = Error; + + fn try_from(value: &AbiEventEnum) -> Result { + let mut t = Token::parse(&value.name)?; + + if let Token::Composite(ref mut c) = t { + c.r#type = CompositeType::Enum; + c.is_event = true; + + for (i, v) in value.variants.iter().enumerate() { + c.inners.push(CompositeInner { + index: i, + name: v.name.clone(), + token: Token::parse(&v.r#type).unwrap(), + kind: v.kind.clone().into(), + }); + } + + if !c.generic_args.is_empty() { + let mut token = Token::Composite(c.clone()); + for (g_name, g_token) in c.generic_args.iter() { + token = token.resolve_generic(g_name, &g_token.type_path()); + } + + return Ok(token); + } + + Ok(t) + } else { + Err(Error::ParsingFailed(format!( + "AbiEventEnum is expected to be a Composite token, got `{:?}`", + value, + ))) + } + } +} diff --git a/crates/parser/src/abi/mod.rs b/crates/parser/src/abi/mod.rs new file mode 100644 index 0000000..0748000 --- /dev/null +++ b/crates/parser/src/abi/mod.rs @@ -0,0 +1,6 @@ +pub mod parser; + +mod conversions; + +// TODO: add ABI files and parse them to assert +// the tokens content. diff --git a/crates/parser/src/abi/parser.rs b/crates/parser/src/abi/parser.rs new file mode 100644 index 0000000..85be0ba --- /dev/null +++ b/crates/parser/src/abi/parser.rs @@ -0,0 +1,240 @@ +use starknet::core::types::contract::{AbiEntry, AbiEvent, TypedAbiEvent}; +use std::collections::HashMap; + +use crate::tokens::{Array, CompositeInner, CompositeType, CoreBasic, Function, Token}; +use crate::CainomeResult; + +pub struct AbiParser {} + +impl AbiParser { + /// Organizes the tokens by cairo types. + pub fn organize_tokens( + tokens: HashMap, + type_aliases: &HashMap, + ) -> HashMap> { + let mut structs = vec![]; + let mut enums = vec![]; + let mut functions = vec![]; + + for (_, mut t) in tokens { + for (type_path, alias) in type_aliases { + t.apply_alias(type_path, alias); + } + + match t { + Token::Composite(ref c) => { + match c.r#type { + CompositeType::Struct => structs.push(t), + CompositeType::Enum => enums.push(t), + _ => (), // TODO: warn? + } + } + Token::Function(_) => functions.push(t), + _ => (), // TODO: warn? + } + } + + let mut out = HashMap::new(); + out.insert("structs".to_string(), structs); + out.insert("enums".to_string(), enums); + out.insert("functions".to_string(), functions); + out + } + + /// Parse all tokens in the ABI. + pub fn collect_tokens(entries: &[AbiEntry]) -> CainomeResult> { + let mut token_candidates: HashMap> = HashMap::new(); + + for entry in entries { + Self::collect_entry_token(entry, &mut token_candidates)?; + } + + let mut tokens = Self::filter_struct_enum_tokens(token_candidates); + + for entry in entries { + Self::collect_entry_function(entry, &mut tokens)?; + } + + Ok(tokens) + } + + /// + fn collect_entry_function( + entry: &AbiEntry, + tokens: &mut HashMap, + ) -> CainomeResult<()> { + match entry { + AbiEntry::Function(f) => { + let mut func = Function::new(&f.name, f.state_mutability.clone().into()); + + // For functions, we don't need to deal with generics as we need + // the flatten type. Parsing the token is enough. + for i in &f.inputs { + func.inputs.push((i.name.clone(), Token::parse(&i.r#type)?)); + } + + for o in &f.outputs { + func.outputs.push(Token::parse(&o.r#type)?); + } + + tokens.insert(f.name.clone(), Token::Function(func)); + } + AbiEntry::Interface(interface) => { + for entry in &interface.items { + Self::collect_entry_function(entry, tokens)?; + } + } + _ => (), + } + + Ok(()) + } + + /// + fn collect_entry_token( + entry: &AbiEntry, + tokens: &mut HashMap>, + ) -> CainomeResult<()> { + match entry { + AbiEntry::Struct(s) => { + if Array::parse(&s.name).is_ok() { + // Spans can be found as a struct entry in the ABI. We don't want + // them as Composite, they are considered as arrays. + return Ok(()); + }; + + // Some struct may be basics, we want to skip them. + if CoreBasic::parse(&s.name).is_ok() { + return Ok(()); + }; + + let token: Token = s.try_into()?; + let entry = tokens.entry(token.type_path()).or_default(); + entry.push(token); + } + AbiEntry::Enum(e) => { + // Some enums may be basics, we want to skip them. + if CoreBasic::parse(&e.name).is_ok() { + return Ok(()); + }; + + let token: Token = e.try_into()?; + let entry = tokens.entry(token.type_path()).or_default(); + entry.push(token); + } + AbiEntry::Event(ev) => { + let mut token: Token; + match ev { + AbiEvent::Typed(typed_e) => match typed_e { + TypedAbiEvent::Struct(s) => { + // Some enums may be basics, we want to skip them. + if CoreBasic::parse(&s.name).is_ok() { + return Ok(()); + }; + + token = s.try_into()?; + } + TypedAbiEvent::Enum(e) => { + // Some enums may be basics, we want to skip them. + if CoreBasic::parse(&e.name).is_ok() { + return Ok(()); + }; + + token = e.try_into()?; + + // All types inside an event enum are also events. + // To ensure correctness of the tokens, we + // set the boolean is_event to true for each variant + // inner token (if any). + + // An other solution would have been to looks for the type + // inside existing tokens, and clone it. More computation, + // but less logic. + + // An enum if a composite, safe to expect here. + if let Token::Composite(ref mut c) = token { + for i in &mut c.inners { + if let Token::Composite(ref mut ic) = i.token { + ic.is_event = true; + } + } + } + } + }, + AbiEvent::Untyped(_) => { + // Cairo 0. + return Ok(()); + } + }; + + let entry = tokens.entry(token.type_path()).or_default(); + entry.push(token); + } + AbiEntry::Interface(interface) => { + for entry in &interface.items { + Self::collect_entry_token(entry, tokens)?; + } + } + _ => (), + }; + + Ok(()) + } + + fn filter_struct_enum_tokens( + token_candidates: HashMap>, + ) -> HashMap { + let mut tokens_filtered: HashMap = HashMap::new(); + for (name, tokens) in token_candidates.into_iter() { + if tokens.len() == 1 { + // Only token with this type path -> we keep it without comparison. + tokens_filtered.insert(name, tokens[0].clone()); + } else if let Token::Composite(composite_0) = &tokens[0] { + // Currently, it's hard to know the original generic arguments + // for each struct/enum member types. + // The following algorithm simply takes the most abundant + // type for each member. + + let mut unique_composite = composite_0.clone(); + // Clear the inner list as they will be compared to select + // the most accurate. + unique_composite.inners.clear(); + + for inner in &composite_0.inners { + let mut inner_tokens: HashMap = HashMap::new(); + + for __t in &tokens { + for __t_inner in + &__t.to_composite().expect("only composite expected").inners + { + if __t_inner.name != inner.name { + continue; + } + + let type_path = __t_inner.token.type_path(); + + let counter = if let Some(c) = inner_tokens.get(&type_path) { + (c.0 + 1, c.1.clone()) + } else { + (1, __t_inner.clone()) + }; + + inner_tokens.insert(type_path, counter); + } + } + + // Take the most abundant type path for each members, sorted by + // the usize counter in descending order. + let mut entries: Vec<_> = inner_tokens.into_iter().collect(); + entries.sort_by(|a, b| b.1 .0.cmp(&a.1 .0)); + + unique_composite.inners.push(entries[0].1 .1.clone()); + } + + tokens_filtered.insert(name, Token::Composite(unique_composite)); + } + } + + tokens_filtered + } +} diff --git a/crates/parser/src/error.rs b/crates/parser/src/error.rs new file mode 100644 index 0000000..fc54002 --- /dev/null +++ b/crates/parser/src/error.rs @@ -0,0 +1,16 @@ +use syn::Error as SynError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error(transparent)] + Syn(#[from] SynError), + #[error("Token initialization error: {0}")] + TokenInitFailed(String), + #[error("Conversion error: {0}")] + ConversionFailed(String), + #[error("Parser error: {0}")] + ParsingFailed(String), +} + +pub type CainomeResult = Result; diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs new file mode 100644 index 0000000..7522ea5 --- /dev/null +++ b/crates/parser/src/lib.rs @@ -0,0 +1,7 @@ +mod error; +pub use error::{CainomeResult, Error}; + +mod abi; +pub use crate::abi::parser::AbiParser; + +pub mod tokens; diff --git a/crates/parser/src/tokens/array.rs b/crates/parser/src/tokens/array.rs new file mode 100644 index 0000000..416ed05 --- /dev/null +++ b/crates/parser/src/tokens/array.rs @@ -0,0 +1,91 @@ +use super::constants::CAIRO_CORE_SPAN_ARRAY; +use super::genericity; +use super::Token; +use crate::{CainomeResult, Error}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Array { + pub type_path: String, + pub inner: Box, +} + +impl Array { + pub fn parse(type_path: &str) -> CainomeResult { + for a in CAIRO_CORE_SPAN_ARRAY { + if type_path.starts_with(a) { + let generic_args = genericity::extract_generics_args(type_path)?; + + if generic_args.len() != 1 { + return Err(Error::TokenInitFailed(format!( + "Array/Span are expected exactly one generic argument, found {} in `{}`.", + generic_args.len(), + type_path, + ))); + } + + let (_, generic_arg_token) = &generic_args[0]; + + return Ok(Self { + type_path: type_path.to_string(), + inner: Box::new(generic_arg_token.clone()), + }); + } + } + + Err(Error::TokenInitFailed(format!( + "Array/Span couldn't be initialized from `{}`.", + type_path, + ))) + } + + pub fn resolve_generic(&self, generic_name: &str, generic_type_path: &str) -> Token { + if self.type_path == generic_type_path { + Token::GenericArg(generic_name.to_string()) + } else { + Token::Array(Self { + type_path: self.type_path.clone(), + inner: Box::new(self.inner.resolve_generic(generic_name, generic_type_path)), + }) + } + } + + pub fn apply_alias(&mut self, type_path: &str, alias: &str) { + self.inner.apply_alias(type_path, alias); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tokens::*; + + #[test] + fn test_parse() { + assert_eq!( + Array::parse("core::array::Array::").unwrap(), + Array { + type_path: "core::array::Array::".to_string(), + inner: Box::new(Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_string() + })), + } + ); + } + + #[test] + fn test_parse_no_inner_invalid() { + assert!(Array::parse("core::array::Array").is_err()); + assert!(Array::parse("core::array::Array<>").is_err()); + } + + #[test] + fn test_parse_wrong_path_invalid() { + assert!(Array::parse("array::Array::").is_err()); + } + + #[test] + fn test_parse_invalid_path_invalid() { + assert!(Array::parse("module::module2::array::Array::").is_err()); + assert!(Array::parse("module::module2::MyStruct::").is_err()); + } +} diff --git a/crates/parser/src/tokens/basic.rs b/crates/parser/src/tokens/basic.rs new file mode 100644 index 0000000..8f84809 --- /dev/null +++ b/crates/parser/src/tokens/basic.rs @@ -0,0 +1,86 @@ +use super::constants::CAIRO_CORE_BASIC; +use crate::{CainomeResult, Error}; + +#[derive(Debug, Clone, PartialEq)] +pub struct CoreBasic { + pub type_path: String, +} + +impl CoreBasic { + pub fn parse(type_path: &str) -> CainomeResult { + // Unit type is for now included in basic type. + if type_path == "()" { + return Ok(Self { + type_path: type_path.to_string(), + }); + } + + if !CAIRO_CORE_BASIC.contains(&type_path) { + return Err(Error::TokenInitFailed(format!( + "CoreBasic token couldn't be initialized from `{}`", + type_path, + ))); + } + + Ok(Self { + type_path: type_path.to_string(), + }) + } + + pub fn type_name(&self) -> String { + // TODO: need to opti that with regex? + let f = self + .type_path + .split('<') + .nth(0) + .unwrap_or(&self.type_path) + .trim_end_matches("::") + .to_string(); + + f.split("::").last().unwrap_or(&f).to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse() { + assert_eq!( + CoreBasic::parse("core::felt252").unwrap(), + CoreBasic { + type_path: "core::felt252".to_string(), + } + ); + + assert_eq!( + CoreBasic::parse("core::integer::u64").unwrap(), + CoreBasic { + type_path: "core::integer::u64".to_string(), + } + ); + } + + #[test] + fn test_parse_unit() { + assert_eq!( + CoreBasic::parse("()").unwrap(), + CoreBasic { + type_path: "()".to_string(), + } + ); + } + + #[test] + fn test_parse_array_span_invalid() { + assert!(CoreBasic::parse("core::array::Array").is_err()); + assert!(CoreBasic::parse("core::array::Span").is_err()); + } + + #[test] + fn test_parse_composite_invalid() { + assert!(CoreBasic::parse("mymodule::MyStruct").is_err()); + assert!(CoreBasic::parse("module2::module3::MyStruct").is_err()); + } +} diff --git a/crates/parser/src/tokens/composite.rs b/crates/parser/src/tokens/composite.rs new file mode 100644 index 0000000..7286326 --- /dev/null +++ b/crates/parser/src/tokens/composite.rs @@ -0,0 +1,423 @@ +use super::genericity; +use super::Token; +use crate::CainomeResult; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CompositeType { + Struct, + Enum, + Unknown, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum CompositeInnerKind { + Key, + Data, + Nested, + Flat, + NotUsed, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct CompositeInner { + pub index: usize, + pub name: String, + pub kind: CompositeInnerKind, + pub token: Token, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Composite { + pub type_path: String, + pub inners: Vec, + pub generic_args: Vec<(String, Token)>, + pub r#type: CompositeType, + pub is_event: bool, + pub alias: Option, +} + +impl Composite { + pub fn parse(type_path: &str) -> CainomeResult { + let generic_args = genericity::extract_generics_args(type_path)?; + + Ok(Self { + // We want to keep the path with generic for the generic resolution. + type_path: type_path.to_string(), + inners: vec![], + generic_args, + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }) + } + + pub fn type_path_no_generic(&self) -> String { + genericity::type_path_no_generic(&self.type_path) + } + + pub fn is_generic(&self) -> bool { + !self.generic_args.is_empty() + } + + pub fn type_name(&self) -> String { + // TODO: need to opti that with regex? + extract_type_path_with_depth(&self.type_path_no_generic(), 0) + } + + pub fn type_name_or_alias(&self) -> String { + if let Some(a) = &self.alias { + a.clone() + } else { + self.type_name() + } + } + + pub fn apply_alias(&mut self, type_path: &str, alias: &str) { + if self.type_path.starts_with(type_path) { + self.alias = Some(alias.to_string()); + } + + for ref mut i in &mut self.inners { + if let Token::Composite(ref mut c) = i.token { + c.apply_alias(type_path, alias); + } + } + } + + pub fn resolve_generic(&self, generic_name: &str, generic_type_path: &str) -> Token { + if self.type_path == generic_type_path { + Token::GenericArg(generic_name.to_string()) + } else { + let mut inners = vec![]; + + for i in &self.inners { + inners.push(CompositeInner { + index: i.index, + name: i.name.clone(), + token: i.token.resolve_generic(generic_name, generic_type_path), + kind: i.kind, + }) + } + + Token::Composite(Self { + type_path: self.type_path.clone(), + generic_args: self.generic_args.clone(), + inners, + r#type: self.r#type, + is_event: self.is_event, + alias: None, + }) + } + } +} + +/// +pub fn snake_to_pascal_case(s: &str) -> String { + s.split('_') + .map(|word| { + let mut c = word.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } + }) + .collect() +} + +/// Extracts the `type_path` with given module `depth`. +/// The extraction also converts all everything to `snake_case`. +/// +/// # Arguments +/// +/// * `type_path` - Type path to be extracted. +/// * `depth` - The module depth to extract. +/// +/// # Examples +/// +/// `module::module2::type_name` with depth 0 -> `TypeName`. +/// `module::module2::type_name` with depth 1 -> `Module2TypeName`. +/// `module::module2::type_name` with depth 2 -> `ModuleModule2TypeName`. +pub fn extract_type_path_with_depth(type_path: &str, depth: usize) -> String { + let segments: Vec<&str> = type_path.split("::").collect(); + + let mut depth = depth; + if segments.len() < depth + 1 { + depth = segments.len() - 1; + } + + let segments = &segments[segments.len() - depth - 1..segments.len()]; + segments.iter().map(|s| snake_to_pascal_case(s)).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tokens::*; + + fn basic_felt252() -> Token { + Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_string(), + }) + } + + fn basic_u64() -> Token { + Token::CoreBasic(CoreBasic { + type_path: "core::integer::u64".to_string(), + }) + } + + fn array_felt252() -> Token { + Token::Array(Array { + type_path: "core::array::Array::".to_string(), + inner: Box::new(basic_felt252()), + }) + } + + fn composite_simple() -> Token { + Token::Composite(Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }) + } + + fn composite_with_generic() -> Token { + Token::Composite(Composite { + type_path: "module::MyStruct::".to_string(), + inners: vec![], + generic_args: vec![("A".to_string(), basic_felt252())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }) + } + + #[test] + fn test_snake_to_pascal_case() { + assert_eq!(snake_to_pascal_case("my_type"), "MyType"); + assert_eq!(snake_to_pascal_case("my_type_long"), "MyTypeLong"); + assert_eq!(snake_to_pascal_case("type"), "Type"); + assert_eq!(snake_to_pascal_case("MyType"), "MyType"); + assert_eq!(snake_to_pascal_case("MyType_hybrid"), "MyTypeHybrid"); + assert_eq!(snake_to_pascal_case(""), ""); + } + + #[test] + fn test_extract_type_with_depth() { + assert_eq!(extract_type_path_with_depth("type_name", 0), "TypeName"); + assert_eq!(extract_type_path_with_depth("type_name", 10), "TypeName"); + assert_eq!( + extract_type_path_with_depth("module::TypeName", 1), + "ModuleTypeName" + ); + assert_eq!( + extract_type_path_with_depth("module::TypeName", 8), + "ModuleTypeName" + ); + assert_eq!( + extract_type_path_with_depth("module_one::module_1::TypeName", 2), + "ModuleOneModule1TypeName" + ); + } + + #[test] + fn test_parse() { + let expected = Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + assert_eq!(Composite::parse("module::MyStruct").unwrap(), expected); + assert!(!expected.is_generic()); + } + + #[test] + fn test_parse_generic_one() { + let expected = Composite { + type_path: "module::MyStruct::".to_string(), + inners: vec![], + generic_args: vec![("A".to_string(), basic_felt252())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + assert_eq!( + Composite::parse("module::MyStruct::").unwrap(), + expected + ); + assert!(expected.is_generic()); + } + + #[test] + fn test_parse_generic_two() { + let expected = Composite { + type_path: "module::MyStruct::".to_string(), + inners: vec![], + generic_args: vec![ + ("A".to_string(), basic_felt252()), + ("B".to_string(), basic_u64()), + ], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + assert_eq!( + Composite::parse("module::MyStruct::").unwrap(), + expected + ); + assert!(expected.is_generic()); + } + + #[test] + fn test_type_name() { + let mut c = Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![], + generic_args: vec![], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + assert_eq!(c.type_name(), "MyStruct"); + + c.type_path = "module::MyStruct::".to_string(); + assert_eq!(c.type_name(), "MyStruct"); + } + + #[test] + fn test_resolve_generic() { + let c = Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![CompositeInner { + index: 0, + name: "field_1".to_string(), + kind: CompositeInnerKind::NotUsed, + token: basic_felt252(), + }], + generic_args: vec![("A".to_string(), basic_felt252())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + let t = c.resolve_generic("A", "core::felt252"); + let c_generic = t.to_composite().unwrap(); + + assert_eq!( + c_generic.inners[0].token, + Token::GenericArg("A".to_string()) + ); + } + + #[test] + fn test_resolve_generic_nested() { + let c = Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![CompositeInner { + index: 0, + name: "field_1".to_string(), + kind: CompositeInnerKind::NotUsed, + token: array_felt252(), + }], + generic_args: vec![("A".to_string(), basic_felt252())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + let t = c.resolve_generic("A", "core::felt252"); + let c_generic = t.to_composite().unwrap(); + + assert_eq!( + c_generic.inners[0].token, + Token::Array(Array { + type_path: "core::array::Array::".to_string(), + inner: Box::new(Token::GenericArg("A".to_string())), + }), + ); + } + + #[test] + fn test_resolve_generic_array_generic() { + let c = Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![CompositeInner { + index: 0, + name: "field_1".to_string(), + kind: CompositeInnerKind::NotUsed, + token: array_felt252(), + }], + generic_args: vec![("A".to_string(), array_felt252())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + let t = c.resolve_generic("A", "core::array::Array::"); + let c_generic = t.to_composite().unwrap(); + + assert_eq!( + c_generic.inners[0].token, + Token::GenericArg("A".to_string()), + ); + } + + #[test] + fn test_resolve_generic_composite() { + let c = Composite { + type_path: "module::MyStructOutter".to_string(), + inners: vec![CompositeInner { + index: 0, + name: "field_1".to_string(), + kind: CompositeInnerKind::NotUsed, + token: composite_simple(), + }], + generic_args: vec![("A".to_string(), composite_with_generic())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + let t = c.resolve_generic("A", "module::MyStruct"); + let c_generic = t.to_composite().unwrap(); + + assert_eq!( + c_generic.inners[0].token, + Token::GenericArg("A".to_string()), + ); + } + + #[test] + fn test_resolve_generic_composite_generic() { + let c = Composite { + type_path: "module::MyStruct".to_string(), + inners: vec![CompositeInner { + index: 0, + name: "field_1".to_string(), + kind: CompositeInnerKind::NotUsed, + token: composite_with_generic(), + }], + generic_args: vec![("A".to_string(), composite_with_generic())], + r#type: CompositeType::Unknown, + is_event: false, + alias: None, + }; + + let t = c.resolve_generic("A", "module::MyStruct::"); + let c_generic = t.to_composite().unwrap(); + + assert_eq!( + c_generic.inners[0].token, + Token::GenericArg("A".to_string()), + ); + } +} diff --git a/crates/parser/src/tokens/constants.rs b/crates/parser/src/tokens/constants.rs new file mode 100644 index 0000000..0d4e5f0 --- /dev/null +++ b/crates/parser/src/tokens/constants.rs @@ -0,0 +1,22 @@ +pub const CAIRO_CORE_BASIC: [&str; 16] = [ + "core::felt252", + "core::bool", + "core::integer::u8", + "core::integer::u16", + "core::integer::u32", + "core::integer::u64", + "core::integer::u128", + "core::integer::usize", + "core::integer::i8", + "core::integer::i16", + "core::integer::i32", + "core::integer::i64", + "core::integer::i128", + "core::starknet::contract_address::ContractAddress", + "core::starknet::eth_address::EthAddress", + "core::starknet::class_hash::ClassHash", +]; + +// Technically, a span is a struct. But it's here +// to match array pattern. +pub const CAIRO_CORE_SPAN_ARRAY: [&str; 2] = ["core::array::Span", "core::array::Array"]; diff --git a/crates/parser/src/tokens/function.rs b/crates/parser/src/tokens/function.rs new file mode 100644 index 0000000..5b0ee70 --- /dev/null +++ b/crates/parser/src/tokens/function.rs @@ -0,0 +1,40 @@ +use super::Token; + +#[derive(Debug, Clone, PartialEq)] +pub enum StateMutability { + External, + View, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Function { + pub name: String, + pub state_mutability: StateMutability, + pub inputs: Vec<(String, Token)>, + pub outputs: Vec, +} + +impl Function { + pub fn new(name: &str, state_mutability: StateMutability) -> Self { + Self { + name: name.to_string(), + state_mutability, + inputs: vec![], + outputs: vec![], + } + } + + pub fn apply_alias(&mut self, type_path: &str, alias: &str) { + for (_, ref mut t) in &mut self.inputs { + if let Token::Composite(ref mut c) = t { + c.apply_alias(type_path, alias); + } + } + + for ref mut t in &mut self.outputs { + if let Token::Composite(ref mut c) = t { + c.apply_alias(type_path, alias); + } + } + } +} diff --git a/crates/parser/src/tokens/genericity.rs b/crates/parser/src/tokens/genericity.rs new file mode 100644 index 0000000..e6226e4 --- /dev/null +++ b/crates/parser/src/tokens/genericity.rs @@ -0,0 +1,56 @@ +use syn::{GenericArgument, PathArguments, Type}; + +use super::Token; +use crate::CainomeResult; + +pub fn extract_generics_args(type_path: &str) -> CainomeResult> { + let t: Type = syn::parse_str(type_path)?; + + let mut generic_args = vec![]; + + if let Type::Path(p) = t { + if let Some(segment) = p.path.segments.last() { + if let PathArguments::AngleBracketed(args) = &segment.arguments { + // Starts to 'A' for generic arguments. + let ascii: u8 = 65; + let mut i = 0; + + for arg in &args.args { + if let GenericArgument::Type(ty) = arg { + let arg_name = ((ascii + i as u8) as char).to_string(); + let arg_str = quote::quote!(#ty).to_string().replace(' ', ""); + generic_args.push((arg_name, Token::parse(&arg_str)?)); + i += 1; + } + } + } + } + } + + Ok(generic_args) +} + +pub fn type_path_no_generic(type_path: &str) -> String { + let frags: Vec<&str> = type_path.split('<').collect(); + frags + .first() + .unwrap_or(&type_path) + .trim_end_matches("::") + .to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_type_with_depth() { + assert_eq!(type_path_no_generic("TypeName"), "TypeName"); + assert_eq!(type_path_no_generic("module::TypeName"), "module::TypeName"); + assert_eq!(type_path_no_generic("TypeName"), "TypeName"); + assert_eq!( + type_path_no_generic("module::TypeName"), + "module::TypeName" + ); + } +} diff --git a/crates/parser/src/tokens/mod.rs b/crates/parser/src/tokens/mod.rs new file mode 100644 index 0000000..cc38cfb --- /dev/null +++ b/crates/parser/src/tokens/mod.rs @@ -0,0 +1,124 @@ +//! Cairo ABI tokens. +//! +//! TODO. + +mod array; +mod basic; +mod composite; +mod constants; +mod function; +mod genericity; +mod tuple; + +pub use array::Array; +pub use basic::CoreBasic; +pub use composite::{Composite, CompositeInner, CompositeInnerKind, CompositeType}; +pub use function::{Function, StateMutability}; +pub use tuple::Tuple; + +use crate::{CainomeResult, Error}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Token { + CoreBasic(CoreBasic), + Array(Array), + Tuple(Tuple), + Composite(Composite), + GenericArg(String), + Function(Function), +} + +impl Token { + pub fn parse(type_path: &str) -> CainomeResult { + if let Ok(b) = CoreBasic::parse(type_path) { + return Ok(Token::CoreBasic(b)); + } + + if let Ok(a) = Array::parse(type_path) { + return Ok(Token::Array(a)); + } + + if let Ok(t) = Tuple::parse(type_path) { + return Ok(Token::Tuple(t)); + } + + if let Ok(c) = Composite::parse(type_path) { + return Ok(Token::Composite(c)); + } + + Err(Error::TokenInitFailed(format!( + "Couldn't initialize a Token from type path `{}`", + type_path, + ))) + } + + pub fn type_name(&self) -> String { + match self { + Token::CoreBasic(t) => t.type_name(), + Token::Array(_) => "array".to_string(), + Token::Tuple(_) => "tuple".to_string(), + Token::Composite(t) => t.type_name(), + Token::GenericArg(_) => "generic_arg".to_string(), + Token::Function(_) => "function".to_string(), + } + } + + pub fn type_path(&self) -> String { + match self { + Token::CoreBasic(t) => t.type_path.to_string(), + Token::Array(t) => t.type_path.to_string(), + Token::Tuple(t) => t.type_path.to_string(), + Token::Composite(t) => t.type_path_no_generic(), + Token::GenericArg(_) => "generic".to_string(), + Token::Function(t) => t.name.clone(), + } + } + + // TODO: we may remove these two functions...! And change types somewhere.. + pub fn to_composite(&self) -> CainomeResult<&Composite> { + match self { + Token::Composite(t) => Ok(t), + _ => Err(Error::ConversionFailed(format!( + "Can't convert token into composite, got {:?}", + self + ))), + } + } + + pub fn to_function(&self) -> CainomeResult<&Function> { + match self { + Token::Function(t) => Ok(t), + _ => Err(Error::ConversionFailed(format!( + "Can't convert token into function, got {:?}", + self + ))), + } + } + + pub fn resolve_generic(&self, generic_name: &str, generic_type_path: &str) -> Self { + match self { + Token::CoreBasic(t) => { + if t.type_path == generic_type_path { + Token::GenericArg(generic_name.to_string()) + } else { + self.clone() + } + } + Token::Array(t) => t.resolve_generic(generic_name, generic_type_path), + Token::Tuple(t) => t.resolve_generic(generic_name, generic_type_path), + Token::Composite(t) => t.resolve_generic(generic_name, generic_type_path), + Token::GenericArg(_) => self.clone(), + Token::Function(_) => self.clone(), + } + } + + pub fn apply_alias(&mut self, type_path: &str, alias: &str) { + match self { + Token::Array(t) => t.apply_alias(type_path, alias), + Token::Tuple(t) => t.apply_alias(type_path, alias), + Token::Composite(t) => t.apply_alias(type_path, alias), + Token::Function(t) => t.apply_alias(type_path, alias), + _ => (), + } + } +} diff --git a/crates/parser/src/tokens/tuple.rs b/crates/parser/src/tokens/tuple.rs new file mode 100644 index 0000000..91b658a --- /dev/null +++ b/crates/parser/src/tokens/tuple.rs @@ -0,0 +1,121 @@ +use syn::Type; + +use super::Token; +use crate::{CainomeResult, Error}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Tuple { + pub type_path: String, + pub inners: Vec, +} + +impl Tuple { + pub fn parse(type_path: &str) -> CainomeResult { + let t: Type = syn::parse_str(type_path)?; + + let mut inners = vec![]; + + match t { + Type::Tuple(t) => { + if t.elems.is_empty() { + return Err(Error::TokenInitFailed( + "Unit type `()` is considered as a CoreBasic, not a tuple.".to_string(), + )); + } + + for e in t.elems { + let ty = quote::quote!(#e).to_string().replace(' ', ""); + inners.push(Token::parse(&ty)?); + } + } + Type::Paren(t) => { + // Tuple with one element are under `Paren` variant. + let e = t.elem; + let ty = quote::quote!(#e).to_string().replace(' ', ""); + inners.push(Token::parse(&ty)?); + } + _ => { + return Err(Error::TokenInitFailed(format!( + "Tuple couldn't be initialized from `{}`.", + type_path, + ))); + } + } + + Ok(Self { + type_path: type_path.to_string(), + inners, + }) + } + + pub fn resolve_generic(&self, generic_name: &str, generic_type_path: &str) -> Token { + if self.type_path == generic_type_path { + Token::GenericArg(generic_name.to_string()) + } else { + let mut inners = vec![]; + + for i in &self.inners { + inners.push(i.resolve_generic(generic_name, generic_type_path)); + } + + Token::Tuple(Self { + type_path: self.type_path.clone(), + inners, + }) + } + } + + pub fn apply_alias(&mut self, type_path: &str, alias: &str) { + for i in &mut self.inners { + i.apply_alias(type_path, alias); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tokens::*; + + #[test] + fn test_parse_unit_invalid() { + assert!(Tuple::parse("()").is_err()); + } + + #[test] + fn test_parse_one_inner() { + assert_eq!( + Tuple::parse("(core::felt252)").unwrap(), + Tuple { + type_path: "(core::felt252)".to_string(), + inners: vec![Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_string() + }),], + } + ); + } + + #[test] + fn test_parse_multiple_inners() { + assert_eq!( + Tuple::parse("(core::felt252, core::integer::u64)").unwrap(), + Tuple { + type_path: "(core::felt252, core::integer::u64)".to_string(), + inners: vec![ + Token::CoreBasic(CoreBasic { + type_path: "core::felt252".to_string() + }), + Token::CoreBasic(CoreBasic { + type_path: "core::integer::u64".to_string() + }), + ], + } + ); + } + + #[test] + fn test_parse_other_type_invalid() { + assert!(Tuple::parse("module::module2::MyStuct").is_err()); + assert!(Tuple::parse("core::integer::u64").is_err()); + } +} diff --git a/crates/parser/test_data/.gitignore b/crates/parser/test_data/.gitignore new file mode 100644 index 0000000..5d43caf --- /dev/null +++ b/crates/parser/test_data/.gitignore @@ -0,0 +1 @@ +/artifacts diff --git a/crates/rs/Cargo.toml b/crates/rs/Cargo.toml new file mode 100644 index 0000000..552fb67 --- /dev/null +++ b/crates/rs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "cainome-rs" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +anyhow.workspace = true +starknet.workspace = true +cainome-parser.workspace = true +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0.15" +serde_json = "1.0.74" +thiserror.workspace = true +cainome-cairo-serde.workspace = true diff --git a/crates/rs/README.md b/crates/rs/README.md new file mode 100644 index 0000000..a473b11 --- /dev/null +++ b/crates/rs/README.md @@ -0,0 +1,189 @@ +# Cainome Rust Backend (`abigen`) + +This crates contains the compile-time rust macro `abigen` to generate rust bindings (using Cairo Serde). + +## Usage + +For examples, please refer to the [examples](../../examples) folder. + +The `abigen!` macro takes 2 or 3 inputs: + +1. The name you want to assign to the contract type being generated. +2. Path to the JSON file containing only the list of ABI entries. This can be easily generated with `jq` doing the following: + +``` +jq .abi ./target/dev/package_contract.contract_class.json > /path/contract.json +``` + +3. Optional parameters: + - `output_path`: if provided, the content will be generated in the given file instead of being expanded at the location of the macro invocation. + - `type_aliases`: to avoid type name conflicts between components / contracts, you can rename some type by providing an alias for the full type path. It is important to give the **full** type path to ensure aliases are applied correctly. + +```rust +use cainome::rs::abigen; + +// Default. +abigen!(MyContract, "/path/contract.json"); + +// Example with optional output path: +abigen!(MyContract, "/path/contract.json", output_path("/path/module.rs")); + +// Example type aliases: +abigen!( + MyContract, + "./contracts/abi/components.abi.json", + type_aliases { + package::module1::component1::MyStruct as MyStruct1; + package::module2::component2::MyStruct as MyStruct2; + }, +); + +fn main() { + // ... use the generated types here, which all of them + // implement CairoSerde trait. +} +``` + +As a known limitation of `Cargo`, the `/path/contract.json` is relative to the Cargo manifest (`Cargo.toml`). This is important when executing a specific package (`-p`) or from the workspace (`--workspace/--all`), the manifest directory is not the same! + +## What is generated + +The expansion of the macros generates the following: + +- For every type that is exposed in the ABI, a `struct` or `enum` will be generated with the `CairoSerde` trait automatically derived. The name of the type if always the last segment of the full type path, enforced to be in `PascalCase`. + + ```rust + // Take this cairo struct, in with the full path `package::my_contract::MyStruct + MyStruct { + a: felt252, + b: u256, + } + + // This will generate a rust struct with the make `MyStruct`: + MyStruct { + a: starknet::core::types::FieldElement, + a: U256, // Note the `PascalCase` here. As `u256` is a struct, it follows the common rule. + } + ``` + +- A type with the identifier of your choice (`MyContract` in the previous example). This types contains all the functions (externals and views) of your contract being exposed in the ABI. To initialize this type, you need the contract address and any type that implements `ConnectedAccount` from `starknet-rs`. Remember that `Arc` also implements `ConnectedAccount`. + ```rust + let account = SingleOwnerAccount::new(...); + let contract_address = FieldElement::from_hex_be("0x1234..."); + let contract = MyContract::new(contract_address, account); + ``` +- A type with the identifier of your choice with the suffix `Reader` (`MyContractReader`) in the previous example. The reader contains only the views of your contract. To initialize a reader, you need the contract address and a provider from `starknet-rs`. + ```rust + let provider = AnyProvider::JsonRpcHttp(...); + let contract_address = FieldElement::from_hex_be("0x1234..."); + let contract_reader = MyContractReader::new(contract_address, &provider); + ``` +- For each **view**, the contract type and the contract reader type contain a function with the exact same arguments. Calling the function returns a `cainome_cairo_serde::call::FCall` struct to allow you to customize how you want the function to be called. Currently, the only setting is the `block_id`. Finally, to actually do the RPC call, you have to use `call()` method on the `FCall` struct. + The default `block_id` value is `BlockTag::Pending`. + ```rust + let my_struct = contract + .get_my_struct() + .block_id(BlockId::Tag(BlockTag::Latest)) + .call() + .await + .expect("Call to `get_my_struct` failed"); + ``` +- For each **external**, the contract type contains a function with the same arguments. Calling the function return a `starknet::accounts::Execution` type from `starknet-rs`, which allows you to completly customize the fees, doing only a simulation etc... To actually send the transaction, you use the `send()` method on the `Execution` struct. You can find the [associated methods with this struct on starknet-rs repo](https://github.com/xJonathanLEI/starknet-rs/blob/0df9ad3417a5f10d486348737fe75659ca4bcfdc/starknet-accounts/src/account/execution.rs#L118). + + ```rust + let my_struct = MyStruct { + a: FieldElement::ONE, + b: U256 { + low: 1, + high: 0, + } + }; + + let tx_res = contract + .set_my_struct(&my_struct) + .max_fee(1000000000000000_u128.into()) + .send() + .await + .expect("Call to `set_my_struct` failed"); + ``` + + To support multicall, currently `Execution` type does not expose the `Call`s. + To circumvey this, for each of the external function an other function with `_getcall` suffix is generated: + + ```rust + // Gather the `Call`s. + let set_a_call = contract.set_a_getcall(&FieldElement::ONE); + let set_b_call = contract.set_b_getcall(&U256 { low: 0xff, high: 0 }); + + // Then use the account exposed by the `MyContract` type to realize the multicall. + let tx_res = contract + .account + .execute(vec![set_a_call, set_b_call]) + .send() + .await + .expect("Multicall failed"); + ``` + +## Known limitation + +With the current state of the parser, here are some limitations: + +1. Generic arguments: even if the library currently supports generic arguments, sometimes the simple algorithm for generic resolution is not able to re-construct the expected generic mapping. This may cause compilation errors. Take an example with: + +```rust +struct GenericTwo { + a: A, + b: B, + c: felt252, +} +``` + +If the cairo code only have one use of this struct like this: + +```rust +fn my_func(self: @ContractState) -> GenericTwo; +``` + +Then the ABI will look like this: + +```json + { + "type": "struct", + "name": "contracts::abicov::structs::GenericTwo::", + "members": [ + { + "name": "a", + "type": "core::integer::u64" + }, + { + "name": "b", + "type": "core::integer::u64" + }, + { + "name": "c", + "type": "core::felt252" + } + ] + }, +``` + +And here... how can we know that `a` is `A` and `b` is `B`? The current algorithm will generate the following: + +```rust +struct GenericTwo { + a: A, + b: A, + c: felt252, +} +``` + +Which will cause a compilation error. + +A first approach to this, is to add a `Phantom` placeholder for each of the variant. To ensure that there is always the two generic args used. But this will prevent the easy initialization of the struct with the fields. Need to check if we can use `Default`, or instead, using a `new(..)` pattern. + +## Roadmap + +1. [ ] Move the `ContractAbi` parsing to a library with `proc_macro2` to be re-used for other backends. +2. [ ] Add a simple transaction status watcher integrated to the contract type. +3. [ ] Add declare and deploy function to the contract type. +4. [ ] Custom choice of derive for generated structs/enums. diff --git a/crates/rs/src/expand/contract.rs b/crates/rs/src/expand/contract.rs new file mode 100644 index 0000000..4112e78 --- /dev/null +++ b/crates/rs/src/expand/contract.rs @@ -0,0 +1,65 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::Ident; + +use super::utils; + +pub struct CairoContract; + +impl CairoContract { + pub fn expand(contract_name: Ident) -> TokenStream2 { + let reader = utils::str_to_ident(format!("{}Reader", contract_name).as_str()); + + let snrs_types = utils::snrs_types(); + let snrs_accounts = utils::snrs_accounts(); + let snrs_providers = utils::snrs_providers(); + + let q = quote! { + + #[derive(Debug)] + pub struct #contract_name { + pub address: #snrs_types::FieldElement, + pub account: A, + } + + impl #contract_name { + pub fn new(address: #snrs_types::FieldElement, account: A) -> Self { + Self { address, account } + } + + pub fn set_contract_address(mut self, address: #snrs_types::FieldElement) { + self.address = address; + } + + pub fn provider(&self) -> &A::Provider { + self.account.provider() + } + } + + #[derive(Debug)] + pub struct #reader { + pub address: #snrs_types::FieldElement, + pub provider: P, + } + + impl #reader

{ + pub fn new( + address: #snrs_types::FieldElement, + provider: P, + ) -> Self { + Self { address, provider } + } + + pub fn set_contract_address(mut self, address: #snrs_types::FieldElement) { + self.address = address; + } + + pub fn provider(&self) -> &P { + &self.provider + } + } + }; + + q + } +} diff --git a/crates/rs/src/expand/enum.rs b/crates/rs/src/expand/enum.rs new file mode 100644 index 0000000..d5bea79 --- /dev/null +++ b/crates/rs/src/expand/enum.rs @@ -0,0 +1,170 @@ +use cainome_parser::tokens::{Composite, Token}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::Ident; + +use crate::expand::types::CairoToRust; +use crate::expand::utils; + +pub struct CairoEnum; + +impl CairoEnum { + pub fn expand_decl(composite: &Composite) -> TokenStream2 { + let enum_name = utils::str_to_ident(&composite.type_name_or_alias()); + + let mut variants: Vec = vec![]; + + for inner in &composite.inners { + let name = utils::str_to_ident(&inner.name); + let ty = utils::str_to_type(&inner.token.to_rust_type()); + + if inner.token.type_name() == "()" { + variants.push(quote!(#name)); + } else { + variants.push(quote!(#name(#ty))); + } + } + + if composite.is_generic() { + let gen_args: Vec = composite + .generic_args + .iter() + .map(|(g, _)| utils::str_to_ident(g)) + .collect(); + + // TODO: we may need Phantom fields here, in the case that + // several generic are present in the enum definition, + // but they are not all used. + // Add one phantom for each generic type. + // Those phantom fields are ignored by serde. + + quote! { + #[derive(Debug, PartialEq, Clone)] + pub enum #enum_name<#(#gen_args),*> { + #(#variants),* + } + } + } else { + quote! { + #[derive(Debug, PartialEq, Clone)] + pub enum #enum_name { + #(#variants),* + } + } + } + } + + pub fn expand_impl(composite: &Composite) -> TokenStream2 { + let name_str = &composite.type_name_or_alias(); + let enum_name = utils::str_to_ident(name_str); + + let mut serialized_sizes: Vec = vec![]; + let mut serializations: Vec = vec![]; + let mut deserializations: Vec = vec![]; + + for inner in &composite.inners { + let variant_name = utils::str_to_ident(&inner.name); + let ty = utils::str_to_type(&inner.token.to_rust_type_path()); + let variant_index = inner.index; + + // Tuples type used as rust type path must be surrounded + // by angle brackets. + let ty_punctuated = match inner.token { + Token::Tuple(_) => quote!(<#ty>), + _ => quote!(#ty), + }; + + if inner.token.type_name() == "()" { + serializations.push(quote! { + #enum_name::#variant_name => usize::cairo_serialize(&#variant_index) + }); + deserializations.push(quote! { + #variant_index => Ok(#enum_name::#variant_name) + }); + serialized_sizes.push(quote! { + #enum_name::#variant_name => 1 + }); + } else { + serializations.push(quote! { + #enum_name::#variant_name(val) => { + let mut temp = vec![]; + temp.extend(usize::cairo_serialize(&#variant_index)); + temp.extend(#ty_punctuated::cairo_serialize(val)); + temp + } + }); + deserializations.push(quote! { + #variant_index => Ok(#enum_name::#variant_name(#ty_punctuated::cairo_deserialize(__felts, __offset + 1)?)) + }); + // +1 because we have to handle the variant index also. + serialized_sizes.push(quote! { + #enum_name::#variant_name(val) => #ty_punctuated::cairo_serialized_size(val) + 1 + }) + } + } + + let ccs = utils::cainome_cairo_serde(); + + serialized_sizes.push(quote! { + _ => 0 + }); + + serializations.push(quote! { + _ => vec![] + }); + + deserializations.push(quote! { + _ => return Err(#ccs::Error::Deserialize(format!("Index not handle for enum {}", #name_str))) + }); + + let (impl_line, rust_type) = if composite.is_generic() { + let gen_args: Vec = composite + .generic_args + .iter() + .map(|(g, _)| utils::str_to_ident(g)) + .collect(); + + ( + utils::impl_with_gen_args(&enum_name, &gen_args), + utils::rust_associated_type_gen_args(&enum_name, &gen_args), + ) + } else { + ( + quote!(impl #ccs::CairoSerde for #enum_name), + quote!( + type RustType = Self; + ), + ) + }; + + quote! { + #impl_line { + + #rust_type + + const SERIALIZED_SIZE: std::option::Option = std::option::Option::None; + + #[inline] + fn cairo_serialized_size(__rust: &Self::RustType) -> usize { + match __rust { + #(#serialized_sizes),* + } + } + + fn cairo_serialize(__rust: &Self::RustType) -> Vec { + match __rust { + #(#serializations),* + } + } + + fn cairo_deserialize(__felts: &[starknet::core::types::FieldElement], __offset: usize) -> #ccs::Result { + let __index:u128 = __felts[__offset].try_into().unwrap(); + match __index as usize { + #(#deserializations),* + } + + } + } + } + } +} diff --git a/crates/rs/src/expand/event.rs b/crates/rs/src/expand/event.rs new file mode 100644 index 0000000..6c81a18 --- /dev/null +++ b/crates/rs/src/expand/event.rs @@ -0,0 +1,25 @@ +use cainome_parser::tokens::{Composite, Token}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::Ident; + +use crate::expand::utils; +use crate::expand::types::CairoToRust; + +pub struct CairoEvent; + +impl CairoEvent { + pub fn expand(enums: &[Token], structs: &[Token]) -> TokenStream2 { + // TODO: + // For each enum in enums -> check if it's an event. + // if yes -> + // 1. impl a function to retrieve the selector + string name of the event. + // 2. impl `TryFrom` EmittedEvent. Need to take in account the new flat keyword. + // - if nested => the selector is the name of the enum variant. + // - if nested and the type it points to is also an enum => first selector is + // the name of the variant of the current enum, and then we've to check + // recursively until the event type is a struct and not an enum. + // - if it's flat, we just take the name of the current variant. + quote!() + } +} diff --git a/crates/rs/src/expand/function.rs b/crates/rs/src/expand/function.rs new file mode 100644 index 0000000..2d9f018 --- /dev/null +++ b/crates/rs/src/expand/function.rs @@ -0,0 +1,158 @@ +//! # Functions types expansion +//! +//! This module contains the auto-generated types +//! for the functions of a contract for which the bindings are requested. +//! +//! Starknet has two types of functions: +//! +//! * `Views` - Which are also named `FunctionCall` that don't modifying the state. Readonly operations. +//! * `Externals` - Where a transaction is involved and can alter the state. Write operations. +//! +//! For each of these functions, there is a struct that is dedicated for each function of the contract, +//! based on it's state mutability found in the ABI itself. +//! +//! * `FCall` - Struct for readonly functions. +//! * `Execution` - Struct from starknet-rs for transaction based functions. +//! +//! ## Examples +//! +//! ```ignore (pseudo-code) +//! // TODO +//! ``` +use cainome_parser::tokens::{Function, StateMutability, Token}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::expand::types::CairoToRust; +use crate::expand::utils; + +fn get_func_inputs(inputs: &[(String, Token)]) -> Vec { + let mut out: Vec = vec![]; + + for (name, token) in inputs { + let name = utils::str_to_ident(name); + let ty = utils::str_to_type(&token.to_rust_type_path()); + out.push(quote!(#name:&#ty)); + } + + out +} + +pub struct CairoFunction; + +impl CairoFunction { + pub fn expand(func: &Function, is_for_reader: bool) -> TokenStream2 { + let func_name = &func.name; + let func_name_ident = utils::str_to_ident(func_name); + + let mut serializations: Vec = vec![]; + for (name, token) in &func.inputs { + let name = utils::str_to_ident(name); + let ty = utils::str_to_type(&token.to_rust_type_path()); + + let ser = match token { + Token::Tuple(_) => quote! { + __calldata.extend(<#ty>::cairo_serialize(#name)); + }, + _ => quote!(__calldata.extend(#ty::cairo_serialize(#name));), + }; + + serializations.push(ser); + } + + let out_type = if func.outputs.is_empty() { + quote!(()) + } else { + // We consider only one type for Cairo 1, if any. + // The outputs field is a list for historical reason from Cairo 0 + // were tuples were used as returned values. + let out_type = utils::str_to_type(&func.outputs[0].to_rust_type_path()); + quote!(#out_type) + }; + + let inputs = get_func_inputs(&func.inputs); + let func_name_call = utils::str_to_ident(&format!("{}_getcall", func_name)); + let type_param = if is_for_reader { + utils::str_to_type("P") + } else { + utils::str_to_type("A::Provider") + }; + + let ccs = utils::cainome_cairo_serde(); + + match &func.state_mutability { + StateMutability::View => quote! { + #[allow(clippy::ptr_arg)] + #[allow(clippy::too_many_arguments)] + pub fn #func_name_ident( + &self, + #(#inputs),* + ) -> #ccs::call::FCall<#type_param, #out_type> { + use #ccs::CairoSerde; + + let mut __calldata = vec![]; + #(#serializations)* + + let __call = starknet::core::types::FunctionCall { + contract_address: self.address, + entry_point_selector: starknet::macros::selector!(#func_name), + calldata: __calldata, + }; + + #ccs::call::FCall::new( + __call, + self.provider(), + ) + } + }, + StateMutability::External => { + // For now, Execution can't return the list of calls. + // This would be helpful to easily access the calls + // without having to add `_getcall()` method. + // If starknet-rs provides a way to get the calls, + // we can remove `_getcall()` method. + // + // TODO: if it's possible to do it with lifetime, + // this can be tried in an issue. + quote! { + #[allow(clippy::ptr_arg)] + #[allow(clippy::too_many_arguments)] + pub fn #func_name_call( + &self, + #(#inputs),* + ) -> starknet::accounts::Call { + use #ccs::CairoSerde; + + let mut __calldata = vec![]; + #(#serializations)* + + starknet::accounts::Call { + to: self.address, + selector: starknet::macros::selector!(#func_name), + calldata: __calldata, + } + } + + #[allow(clippy::ptr_arg)] + pub fn #func_name_ident( + &self, + #(#inputs),* + ) -> starknet::accounts::Execution { + use #ccs::CairoSerde; + + let mut __calldata = vec![]; + #(#serializations)* + + let __call = starknet::accounts::Call { + to: self.address, + selector: starknet::macros::selector!(#func_name), + calldata: __calldata, + }; + + self.account.execute(vec![__call]) + } + } + } + } + } +} diff --git a/crates/rs/src/expand/mod.rs b/crates/rs/src/expand/mod.rs new file mode 100644 index 0000000..e44d5c9 --- /dev/null +++ b/crates/rs/src/expand/mod.rs @@ -0,0 +1,12 @@ +pub(crate) mod contract; +pub(crate) mod r#enum; +// pub(crate) mod event; +pub(crate) mod function; +pub(crate) mod r#struct; +mod types; +pub(crate) mod utils; + +pub use contract::CairoContract; +pub use function::CairoFunction; +pub use r#enum::CairoEnum; +pub use r#struct::CairoStruct; diff --git a/crates/rs/src/expand/struct.rs b/crates/rs/src/expand/struct.rs new file mode 100644 index 0000000..d5933d0 --- /dev/null +++ b/crates/rs/src/expand/struct.rs @@ -0,0 +1,137 @@ +use cainome_parser::tokens::{Composite, Token}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::Ident; + +use crate::expand::types::CairoToRust; +use crate::expand::utils; + +pub struct CairoStruct; + +impl CairoStruct { + pub fn expand_decl(composite: &Composite) -> TokenStream2 { + let struct_name = utils::str_to_ident(&composite.type_name_or_alias()); + + let mut members: Vec = vec![]; + for inner in &composite.inners { + let name = utils::str_to_ident(&inner.name); + let ty = utils::str_to_type(&inner.token.to_rust_type()); + + members.push(quote!(#name: #ty)); + } + + if composite.is_generic() { + let gen_args: Vec = composite + .generic_args + .iter() + .map(|(g, _)| utils::str_to_ident(g)) + .collect(); + + // TODO: we may need Phantom fields here, in the case that + // several generic are present in the struct definition, + // but they are not all used. + // Add one phantom for each generic type. + // Those phantom fields are ignored by serde. + + quote! { + #[derive(Debug, PartialEq, Clone)] + pub struct #struct_name<#(#gen_args),*> { + #(pub #members),* + } + } + } else { + quote! { + #[derive(Debug, PartialEq, Clone)] + pub struct #struct_name { + #(pub #members),* + } + } + } + } + + pub fn expand_impl(composite: &Composite) -> TokenStream2 { + let struct_name = utils::str_to_ident(&composite.type_name_or_alias()); + + let mut sizes: Vec = vec![]; + let mut sers: Vec = vec![]; + let mut desers: Vec = vec![]; + let mut names: Vec = vec![]; + + for inner in &composite.inners { + let name = utils::str_to_ident(&inner.name); + names.push(quote!(#name)); + + let ty = utils::str_to_type(&inner.token.to_rust_type_path()); + + // Tuples type used as rust type path item path must be surrounded + // by angle brackets. + let ty_punctuated = match inner.token { + Token::Tuple(_) => quote!(<#ty>), + _ => quote!(#ty), + }; + + sizes.push(quote! { + __size += #ty_punctuated::cairo_serialized_size(&__rust.#name); + }); + + sers.push(quote!(__out.extend(#ty_punctuated::cairo_serialize(&__rust.#name));)); + + desers.push(quote! { + let #name = #ty_punctuated::cairo_deserialize(__felts, __offset)?; + __offset += #ty_punctuated::cairo_serialized_size(&#name); + }); + } + + let ccs = utils::cainome_cairo_serde(); + + let (impl_line, rust_type) = if composite.is_generic() { + let gen_args: Vec = composite + .generic_args + .iter() + .map(|(g, _)| utils::str_to_ident(g)) + .collect(); + + ( + utils::impl_with_gen_args(&struct_name, &gen_args), + utils::rust_associated_type_gen_args(&struct_name, &gen_args), + ) + } else { + ( + quote!(impl #ccs::CairoSerde for #struct_name), + quote!( + type RustType = Self; + ), + ) + }; + + quote! { + #impl_line { + + #rust_type + + const SERIALIZED_SIZE: std::option::Option = None; + + #[inline] + fn cairo_serialized_size(__rust: &Self::RustType) -> usize { + let mut __size = 0; + #(#sizes)* + __size + } + + fn cairo_serialize(__rust: &Self::RustType) -> Vec { + let mut __out: Vec = vec![]; + #(#sers)* + __out + } + + fn cairo_deserialize(__felts: &[starknet::core::types::FieldElement], __offset: usize) -> #ccs::Result { + let mut __offset = __offset; + #(#desers)* + Ok(#struct_name { + #(#names),* + }) + } + } + } + } +} diff --git a/crates/rs/src/expand/types.rs b/crates/rs/src/expand/types.rs new file mode 100644 index 0000000..eb610e3 --- /dev/null +++ b/crates/rs/src/expand/types.rs @@ -0,0 +1,84 @@ +use cainome_parser::tokens::Token; + +use super::utils; + +pub trait CairoToRust { + fn to_rust_type(&self) -> String; + + fn to_rust_type_path(&self) -> String; +} + +impl CairoToRust for Token { + fn to_rust_type(&self) -> String { + match self { + Token::CoreBasic(t) => basic_types_to_rust(&t.type_name()), + Token::Array(t) => format!("Vec<{}>", t.inner.to_rust_type()), + Token::Tuple(t) => { + let mut s = String::from("("); + + for (idx, inner) in t.inners.iter().enumerate() { + s.push_str(&inner.to_rust_type()); + + if idx < t.inners.len() - 1 { + s.push_str(", "); + } + } + s.push(')'); + + s + } + Token::Composite(c) => c.type_name_or_alias(), + Token::GenericArg(s) => s.clone(), + _ => "__FUNCTION_NOT_SUPPORTED__".to_string(), + } + } + + fn to_rust_type_path(&self) -> String { + match self { + Token::CoreBasic(t) => basic_types_to_rust(&t.type_name()), + Token::Array(t) => format!("Vec::<{}>", t.inner.to_rust_type_path()), + Token::Tuple(t) => { + let mut s = String::from("("); + for (idx, inner) in t.inners.iter().enumerate() { + s.push_str(&inner.to_rust_type_path()); + + if idx < t.inners.len() - 1 { + s.push_str(", "); + } + } + s.push(')'); + s + } + Token::Composite(c) => { + let mut s = c.type_name_or_alias(); + + if c.is_generic() { + s.push_str("::<"); + for (i, (_, token)) in c.generic_args.iter().enumerate() { + s.push_str(&token.to_rust_type_path()); + if i < c.generic_args.len() - 1 { + s.push(','); + } + } + s.push('>'); + } + + s + } + Token::GenericArg(s) => s.clone(), + _ => "__FUNCTION_NOT_SUPPORTED__".to_string(), + } + } +} + +fn basic_types_to_rust(type_name: &str) -> String { + let ccsp = utils::cainome_cairo_serde_path(); + + match type_name { + "ClassHash" => format!("{ccsp}::ClassHash"), + "ContractAddress" => format!("{ccsp}::ContractAddress"), + "EthAddress" => format!("{ccsp}::EthAddress"), + "felt252" => "starknet::core::types::FieldElement".to_string(), + _ => type_name.to_string(), + } +} diff --git a/crates/rs/src/expand/utils.rs b/crates/rs/src/expand/utils.rs new file mode 100644 index 0000000..a0c6bc0 --- /dev/null +++ b/crates/rs/src/expand/utils.rs @@ -0,0 +1,75 @@ +//! Utils function for expansion. +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Ident, Type}; + +/// +pub fn str_to_ident(str_in: &str) -> Ident { + Ident::new(str_in, proc_macro2::Span::call_site()) +} + +/// +pub fn str_to_type(str_in: &str) -> Type { + syn::parse_str(str_in).unwrap_or_else(|_| panic!("Can't convert {} to syn::Type", str_in)) +} + +/// +// pub fn str_to_litstr(str_in: &str) -> LitStr { +// LitStr::new(str_in, proc_macro2::Span::call_site()) +// } + +pub fn snrs_types() -> Type { + str_to_type("starknet::core::types") +} + +pub fn snrs_accounts() -> Type { + str_to_type("starknet::accounts") +} + +pub fn snrs_providers() -> Type { + str_to_type("starknet::providers") +} + +pub fn cainome_cairo_serde() -> Type { + str_to_type(&cainome_cairo_serde_path()) +} + +#[inline] +pub fn cainome_cairo_serde_path() -> String { + //String::from("cainome_cairo_serde") + String::from("cainome::cairo_serde") +} + +/// Expands the implementation line with generic types. +pub fn impl_with_gen_args(entity_name: &Ident, gen_args: &Vec) -> TokenStream2 { + let gen_args_rust: Vec = gen_args + .iter() + .map(|g| str_to_ident(format!("R{}", g).as_str())) + .collect(); + + let mut tokens = vec![]; + + let ccs = cainome_cairo_serde(); + + tokens.push(quote! { + impl<#(#gen_args),* , #(#gen_args_rust),*> #ccs::CairoSerde for #entity_name<#(#gen_args),*> + where + }); + + for (i, g) in gen_args.iter().enumerate() { + let gr = &gen_args_rust[i]; + tokens.push(quote!(#g: #ccs::CairoSerde,)); + } + + quote!(#(#tokens)*) +} + +/// Expands the associated types lines for generic types. +pub fn rust_associated_type_gen_args(entity_name: &Ident, gen_args: &[Ident]) -> TokenStream2 { + let gen_args_rust: Vec = gen_args + .iter() + .map(|g| str_to_ident(format!("R{}", g).as_str())) + .collect(); + + quote!(type RustType = #entity_name<#(#gen_args_rust),*>;) +} diff --git a/crates/rs/src/lib.rs b/crates/rs/src/lib.rs new file mode 100644 index 0000000..a04acb1 --- /dev/null +++ b/crates/rs/src/lib.rs @@ -0,0 +1,97 @@ +use cainome_parser::tokens::StateMutability; +use cainome_parser::AbiParser; +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +mod expand; +mod macro_inputs; +mod spanned; +mod types; + +use crate::expand::utils; +use crate::expand::{CairoContract, CairoEnum, CairoFunction, CairoStruct}; +use crate::macro_inputs::ContractAbi; + +#[proc_macro] +pub fn abigen(input: TokenStream) -> TokenStream { + abigen_internal(input) +} + +fn abigen_internal(input: TokenStream) -> TokenStream { + let contract_abi = syn::parse_macro_input!(input as ContractAbi); + + let contract_name = contract_abi.name; + let abi_entries = contract_abi.abi; + + let abi_tokens = AbiParser::collect_tokens(&abi_entries).expect("failed tokens parsing"); + let abi_tokens = AbiParser::organize_tokens(abi_tokens, &contract_abi.type_aliases); + + let mut tokens: Vec = vec![]; + + tokens.push(CairoContract::expand(contract_name.clone())); + + if let Some(structs) = abi_tokens.get("structs") { + for s in structs { + let s_composite = s.to_composite().expect("composite expected"); + tokens.push(CairoStruct::expand_decl(s_composite)); + tokens.push(CairoStruct::expand_impl(s_composite)); + } + } + + if let Some(enums) = abi_tokens.get("enums") { + for e in enums { + let e_composite = e.to_composite().expect("composite expected"); + tokens.push(CairoEnum::expand_decl(e_composite)); + tokens.push(CairoEnum::expand_impl(e_composite)); + } + } + + // TODO: events need to expand auto-deserialization based on selectors. + + let mut reader_views = vec![]; + let mut views = vec![]; + let mut externals = vec![]; + + if let Some(funcs) = abi_tokens.get("functions") { + for f in funcs { + let f = f.to_function().expect("function expected"); + match f.state_mutability { + StateMutability::View => { + reader_views.push(CairoFunction::expand(f, true)); + views.push(CairoFunction::expand(f, false)); + } + StateMutability::External => externals.push(CairoFunction::expand(f, false)), + } + } + } + + let reader = utils::str_to_ident(format!("{}Reader", contract_name).as_str()); + + tokens.push(quote! { + impl #contract_name { + #(#views)* + #(#externals)* + } + + impl #reader

{ + #(#reader_views)* + } + }); + + let expanded = quote! { + #(#tokens)* + }; + + if let Some(out_path) = contract_abi.output_path { + let content: String = expanded.to_string(); + match std::fs::write(out_path, content) { + Ok(_) => (), + Err(e) => panic!("Failed to write to file: {}", e), + } + + quote!().into() + } else { + expanded.into() + } +} diff --git a/crates/rs/src/macro_inputs.rs b/crates/rs/src/macro_inputs.rs new file mode 100644 index 0000000..925b4c1 --- /dev/null +++ b/crates/rs/src/macro_inputs.rs @@ -0,0 +1,125 @@ +//! Defines the arguments of the `abigen` macro. +//! +//! `ContractAbi` is expected to be the argument +//! passed to the macro. We should then parse the +//! token stream to ensure the arguments are correct. +//! +//! At this moment, the macro supports one fashion: +//! +//! Loading from a file with only the ABI array. +//! abigen!(ContractName, "path/to/abi.json" +//! +//! TODO: support the full artifact JSON to be able to +//! deploy contracts from abigen. +use quote::ToTokens; +use starknet::core::types::contract::AbiEntry; +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use syn::{ + braced, + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream, Result}, + Ident, LitStr, Token, Type, +}; + +use crate::spanned::Spanned; + +#[derive(Clone, Debug)] +pub(crate) struct ContractAbi { + pub name: Ident, + pub abi: Vec, + pub output_path: Option, + pub type_aliases: HashMap, +} + +impl Parse for ContractAbi { + fn parse(input: ParseStream) -> Result { + let name = input.parse::()?; + input.parse::()?; + + // Path rooted to the Cargo.toml location. + let json_path = input.parse::()?; + + let abi = + serde_json::from_reader::<_, Vec>(File::open(json_path.value()).map_err( + |e| syn::Error::new(json_path.span(), format!("JSON open file error: {}", e)), + )?) + .map_err(|e| syn::Error::new(json_path.span(), format!("JSON parse error: {}", e)))?; + + let mut output_path: Option = None; + let mut type_aliases = HashMap::new(); + + loop { + if input.parse::().is_err() { + break; + } + + let name = match Ident::parse_any(input) { + Ok(n) => n, + Err(_) => break, + }; + + match name.to_string().as_str() { + "type_aliases" => { + let content; + braced!(content in input); + let parsed = + content.parse_terminated(Spanned::::parse, Token![;])?; + + let mut abi_types = HashSet::new(); + let mut aliases = HashSet::new(); + + for type_alias in parsed { + if !abi_types.insert(type_alias.abi.clone()) { + panic!("{} duplicate abi type", type_alias.abi) + } + if !aliases.insert(type_alias.alias.clone()) { + panic!("{} duplicate alias name", type_alias.alias) + } + + let ta = type_alias.into_inner(); + type_aliases.insert(ta.abi, ta.alias); + } + } + "output_path" => { + let content; + parenthesized!(content in input); + output_path = Some(content.parse::()?.value()); + } + _ => panic!("unexpected named parameter `{}`", name), + } + } + + Ok(ContractAbi { + name, + abi, + output_path, + type_aliases, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct TypeAlias { + abi: String, + alias: String, +} + +impl Parse for TypeAlias { + fn parse(input: ParseStream) -> Result { + let abi = input + .parse::()? + .into_token_stream() + .to_string() + .replace(' ', ""); + + input.parse::()?; + + let alias = input.parse::()?.to_string(); + + Ok(TypeAlias { abi, alias }) + } +} + +// TODO: add test for argument parsing. diff --git a/crates/rs/src/spanned.rs b/crates/rs/src/spanned.rs new file mode 100644 index 0000000..4e97c99 --- /dev/null +++ b/crates/rs/src/spanned.rs @@ -0,0 +1,52 @@ +//! Provides implementation for helpers used in parsing `TokenStream`s where the +//! data ultimately does not care about its `Span` information, but it is useful +//! during intermediate processing. +//! +//! Taken from Ethers. + +use proc_macro2::Span; +use std::ops::Deref; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; + +/// Trait that abstracts functionality for inner data that can be parsed and +/// wrapped with a specific `Span`. +pub trait ParseInner: Sized { + fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)>; +} + +impl ParseInner for T { + fn spanned_parse(input: ParseStream) -> ParseResult<(Span, Self)> { + Ok((input.span(), T::parse(input)?)) + } +} + +impl Parse for Spanned { + fn parse(input: ParseStream) -> ParseResult { + let (span, value) = T::spanned_parse(input)?; + Ok(Spanned(span, value)) + } +} + +/// A struct that captures `Span` information for inner parsable data. +#[cfg_attr(test, derive(Clone, Debug))] +pub struct Spanned(Span, T); + +impl Spanned { + // /// Retrieves the captured `Span` information for the parsed data. + // pub fn span(&self) -> Span { + // self.0 + // } + + /// Retrieves the inner data. + pub fn into_inner(self) -> T { + self.1 + } +} + +impl Deref for Spanned { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.1 + } +} diff --git a/crates/rs/src/types/mod.rs b/crates/rs/src/types/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/crates/rs/src/types/mod.rs @@ -0,0 +1 @@ + diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..60ffdca --- /dev/null +++ b/examples/README.md @@ -0,0 +1,10 @@ +# Examples + +## simple_get_set + +A simple contract that showcase the use of `abigen!` macro. +Usage: + +``` +cargo run --example simple_get_set --features="abigen-rs" +``` diff --git a/examples/components_events.rs b/examples/components_events.rs new file mode 100644 index 0000000..4082b6d --- /dev/null +++ b/examples/components_events.rs @@ -0,0 +1,31 @@ +use cainome::rs::abigen; +// use starknet::{ +// accounts::{Account, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}, +// core::types::{BlockId, BlockTag, FieldElement}, +// providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient}, +// signers::{LocalWallet, SigningKey}, +// }; +// use std::sync::Arc; +// use url::Url; + +// This example uses an ABI where components introduce several enums with `Event` type name. +// This showcase how the type_aliases parameter can be leveraged to avoid conflicts. + +abigen!( + MyContract, + "./contracts/abi/components.abi.json", + type_aliases { + contracts::abicov::components::simple_component::Event as SimpleEvent; + contracts::abicov::components::simple_component::Written as SimpleWritten; + contracts::abicov::components::simple_component::MyStruct as MyStructSimple; + contracts::abicov::components::simple_component_other::Event as OtherEvent; + contracts::abicov::components::simple_component_other::Written as OtherWritten; + contracts::abicov::components::simple_component_other::MyStruct as MyStructOther; + } +); + +// All components Events are renamed, and the only one that will remain with the name `Event` +// is the enum of the contract's events itself. + +#[tokio::main] +async fn main() {} diff --git a/examples/simple_get_set.rs b/examples/simple_get_set.rs new file mode 100644 index 0000000..94edbb3 --- /dev/null +++ b/examples/simple_get_set.rs @@ -0,0 +1,179 @@ +use cainome::rs::abigen; +use starknet::{ + accounts::{Account, ConnectedAccount, ExecutionEncoding, SingleOwnerAccount}, + core::types::{BlockId, BlockTag, FieldElement}, + providers::{jsonrpc::HttpTransport, AnyProvider, JsonRpcClient}, + signers::{LocalWallet, SigningKey}, +}; +use std::sync::Arc; +use url::Url; + +// To run this example, please first run `make setup_simple_get_set` in the contracts directory with a Katana running. This will declare and deploy the testing contract. + +const CONTRACT_ADDRESS: &str = "0x001852eaa36c6e8c3729648efb376e5b622a0aa634f0b0d612b42d1bb2a8bd3e"; +const KATANA_ACCOUNT_0: &str = "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973"; +const KATANA_PRIVKEY_0: &str = "0x1800000000300000180000000000030000000000003006001800006600"; +const KATANA_CHAIN_ID: &str = "0x4b4154414e41"; + +abigen!(MyContract, "./contracts/abi/simple_get_set.abi.json"); + +#[tokio::main] +async fn main() { + let rpc_url = Url::parse("http://0.0.0.0:5050").expect("Expecting Starknet RPC URL"); + let provider = + AnyProvider::JsonRpcHttp(JsonRpcClient::new(HttpTransport::new(rpc_url.clone()))); + + let contract_address = FieldElement::from_hex_be(CONTRACT_ADDRESS).unwrap(); + + // If you only plan to call views functions, you can use the `Reader`, which + // only requires a provider along with your contract address. + let contract = MyContractReader::new(contract_address, &provider); + + // To call a view, there is no need to initialize an account. You can directly + // use the name of the method in the ABI and then use the `call()` method. + let a = contract + .get_a() + .call() + .await + .expect("Call to `get_a` failed"); + println!("a initial value: {:?}", a); + + // If you need to explicitely set the block id of the call, you can do as + // following. The default value is "Pending". + let b = contract + .get_b() + .block_id(BlockId::Tag(BlockTag::Latest)) + .call() + .await + .expect("Call to `get_b` failed"); + println!("b inital value: {:?}", b); + + // For the inputs / outputs of the ABI functions, all the types are + // defined where the abigen macro is expanded. Consider using the macro abigen + // in a separate module to avoid clashes if you have to use it multiple times. + + // If you want to do some invoke for external functions, you must use an account. + let signer = LocalWallet::from(SigningKey::from_secret_scalar( + FieldElement::from_hex_be(KATANA_PRIVKEY_0).unwrap(), + )); + let address = FieldElement::from_hex_be(KATANA_ACCOUNT_0).unwrap(); + + let account = Arc::new(SingleOwnerAccount::new( + provider, + signer, + address, + FieldElement::from_hex_be(KATANA_CHAIN_ID).unwrap(), + ExecutionEncoding::Legacy, + )); + + let contract = MyContract::new(contract_address, account); + + // The transaction is actually sent when `send()` is called. + // You can before that configure the fees, or even only run an estimation of the + // fees without actually sending the transaction. + let _tx_res = contract + .set_a(&(a + FieldElement::ONE)) + .max_fee(1000000000000000_u128.into()) + .send() + .await + .expect("Call to `set_a` failed"); + + // In production code, you want to poll the transaction status. + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let a = contract + .get_a() + .call() + .await + .expect("Call to `get_a` failed"); + println!("a after invoke: {:?}", a); + + // Now let's say we want to do multicall, and in one transaction we want to set a and b. + // You can call the same function name with `_getcall` prefix to get the + // call only, ready to be added in a multicall array. + let set_a_call = contract.set_a_getcall(&FieldElement::from_hex_be("0xee").unwrap()); + let set_b_call = contract.set_b_getcall(&U256 { low: 0xff, high: 0 }); + + // Then, we use the account exposed by the contract to execute the multicall. + // Once again, there is no abstraction on starknet-rs type, so you have + // the full control from starknet-rs library. + let _tx_res = contract + .account + .execute(vec![set_a_call, set_b_call]) + .send() + .await + .expect("Multicall failed"); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let a = contract + .get_a() + .call() + .await + .expect("Call to `get_a` failed"); + println!("a after multicall: {:?}", a); + + let b = contract + .get_b() + .call() + .await + .expect("Call to `get_b` failed"); + println!("b after multicall: {:?}", b); + + // Let's send this to an other thread. + // Remember, ConnectedAccount is implemented for Arc. + let arc_contract = Arc::new(contract); + + let handle = tokio::spawn(async move { + other_func(arc_contract.clone()).await; + }); + + handle.await.unwrap(); +} + +async fn other_func(contract: Arc>) { + // As `Arc>` is also implementing `ConnectedAccount`, + // passing a contract you also have the reader that you can retrieve anytime + // by calling `contract.reader()`. + + let set_b = contract.set_b(&U256 { low: 0xfe, high: 0 }); + + // Example of estimation of fees. + let estimated_fee = set_b + .estimate_fee() + .await + .expect("Fail to estimate") + .overall_fee; + + // Use the estimated fees as a base. + let _tx_res = set_b + .max_fee((estimated_fee * 2).into()) + .send() + .await + .expect("invoke failed"); + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + + let b = contract + .get_b() + .call() + .await + .expect("Call to `get_b` failed"); + println!("b set in task: {:?}", b); + + let arr = vec![FieldElement::THREE, FieldElement::ONE, FieldElement::ZERO]; + + let tx_res = contract + .set_array(&arr) + .send() + .await + .expect("invoke set_array failed"); + println!("tx_res in task: {:?}", tx_res); + + let a = contract + .get_a() + .call() + .await + .expect("Call to `get_a` failed"); + println!("a set in task: {:?}", a); +} diff --git a/scripts/clippy.sh b/scripts/clippy.sh new file mode 100644 index 0000000..2d4f279 --- /dev/null +++ b/scripts/clippy.sh @@ -0,0 +1 @@ +cargo clippy --all --all-targets --all-features -- -D warnings diff --git a/scripts/fmt.sh b/scripts/fmt.sh new file mode 100644 index 0000000..2a0bd2b --- /dev/null +++ b/scripts/fmt.sh @@ -0,0 +1 @@ +cargo fmt --check diff --git a/scripts/prettier.sh b/scripts/prettier.sh new file mode 100644 index 0000000..ef9adbb --- /dev/null +++ b/scripts/prettier.sh @@ -0,0 +1 @@ +prettier --check "**/*.md" diff --git a/scripts/test_all.sh b/scripts/test_all.sh new file mode 100644 index 0000000..274eecd --- /dev/null +++ b/scripts/test_all.sh @@ -0,0 +1 @@ +cargo test --workspace --all-features diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..84bdef3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,14 @@ +//! Cainome crate. + +pub mod cairo_serde { + pub use cainome_cairo_serde::*; +} + +pub mod parser { + pub use cainome_parser::*; +} + +#[cfg(feature = "abigen-rs")] +pub mod rs { + pub use cainome_rs::*; +}