diff --git a/.github/actions/setup-tools/action.yml b/.github/actions/setup-tools/action.yml new file mode 100644 index 0000000000..2d4e6d6004 --- /dev/null +++ b/.github/actions/setup-tools/action.yml @@ -0,0 +1,55 @@ +name: Setup Tools +description: Installs Rust and optionally Scarb, Universal Sierra Compiler and LLVM 19 toolchain + +inputs: + install-llvm: + description: 'Whether to install the LLVM 19 toolchain' + required: false + default: 'false' + setup-scarb: + description: 'Whether to setup scarb' + required: false + default: 'true' + setup-usc: + description: 'Whether to setup universal-sierra-compiler' + required: false + default: 'true' + +runs: + using: "composite" + steps: + - uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + + - uses: software-mansion/setup-scarb@v1 + if: ${{ inputs.setup-scarb == 'true' }} + + - uses: software-mansion/setup-universal-sierra-compiler@v1 + if: ${{ inputs.setup-usc == 'true' }} + + - name: Add LLVM APT repository + uses: myci-actions/add-deb-repo@11 + if: ${{ inputs.install-llvm == 'true' }} + with: + repo: deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-19 main + repo-name: llvm-repo + keys-asc: https://apt.llvm.org/llvm-snapshot.gpg.key + + - name: Install LLVM + shell: bash + if: ${{ inputs.install-llvm == 'true' }} + run: | + sudo apt-get update + sudo apt-get install -y \ + llvm-19 llvm-19-dev llvm-19-runtime \ + clang-19 clang-tools-19 \ + lld-19 libpolly-19-dev libmlir-19-dev mlir-19-tools + + - name: Set environment variables + if: ${{ inputs.install-llvm == 'true' }} + shell: bash + run: | + echo "MLIR_SYS_190_PREFIX=/usr/lib/llvm-19/" >> $GITHUB_ENV + echo "LLVM_SYS_191_PREFIX=/usr/lib/llvm-19/" >> $GITHUB_ENV + echo "TABLEGEN_190_PREFIX=/usr/lib/llvm-19/" >> $GITHUB_ENV diff --git a/.github/workflows/_build-binaries-native.yml b/.github/workflows/_build-binaries-native.yml new file mode 100644 index 0000000000..6ec639cfd4 --- /dev/null +++ b/.github/workflows/_build-binaries-native.yml @@ -0,0 +1,118 @@ +# TODO(#3790) this is currently unused, integrate into nightly release flow +name: Build Native binaries + +on: + workflow_call: + inputs: + # Specify the version in MAJOR.MINOR.PATCH format, without a leading 'v' + version: + required: true + type: string + ref: + required: false + type: string + +jobs: + build-binaries: + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.os }} + + env: + # Cross-compiled targets will override this to `cross`. + CARGO: cargo + + strategy: + matrix: + # Currently we have only managed to build successfuly on x86 gnu Linux + # - On MacOS, LLVM is installed with brew and the built binary requires having LLVM installed through it, + # even though it is supossed to be bundled. + # - MUSL Linux was not investigated + # - Aarch64 Linux requires LLVM to be build for Aarch64, but proc macros run against host OS, which is x86_64 + # when building through cross. With current setup, we install LLVM for only one of these arches. + # We need to investigate cross compiling LLVM for multiple architectures. + include: + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + system: linux + +# - target: x86_64-unknown-linux-musl +# os: ubuntu-latest +# system: linux +# +# - target: aarch64-unknown-linux-gnu +# os: ubuntu-latest +# system: linux +# +# - target: aarch64-unknown-linux-musl +# os: ubuntu-latest +# system: linux +# +# - target: x86_64-apple-darwin +# os: macos-14-large +# system: macos +# +# - target: aarch64-apple-darwin +# os: macos-latest +# system: macos + + steps: + - name: Checkout with ref + if: inputs.ref != '' + uses: actions/checkout@v5 + with: + ref: ${{ inputs.ref }} + + - name: Checkout default + if: inputs.ref == '' + uses: actions/checkout@v5 + + - name: Setup rust + run: | + rustup target add ${{ matrix.target }} + + - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + + - name: Setup LLVM on MacOS + if: matrix.system == 'macos' + run: | + brew install llvm@19 + brew reinstall zstd + echo "MLIR_SYS_190_PREFIX=$(brew --prefix llvm@19)" >> $GITHUB_ENV + echo "LLVM_SYS_191_PREFIX=$(brew --prefix llvm@19)" >> $GITHUB_ENV + echo "TABLEGEN_190_PREFIX=$(brew --prefix llvm@19)" >> $GITHUB_ENV + echo "LIBRARY_PATH=/opt/homebrew/lib" >> $GITHUB_ENV + + - name: Install cross + if: matrix.system == 'linux' + run: | + cargo install cross --git https://github.com/cross-rs/cross + + - name: Build MacOS + if: matrix.system == 'macos' + run: | + export MLIR_SYS_190_PREFIX=${{ env.MLIR_SYS_190_PREFIX }} + export LLVM_SYS_191_PREFIX=${{ env.LLVM_SYS_191_PREFIX }} + export TABLEGEN_190_PREFIX=${{ env.TABLEGEN_190_PREFIX }} + export LIBRARY_PATH=${{ env.LIBRARY_PATH }} + cargo build --release --locked --target ${{ matrix.target }} + + - name: Build Linux + if: matrix.system == 'linux' + run: | + # Use cross to link oldest GLIBC possible. + cross build --release --locked --target ${{ matrix.target }} + + - name: Package + shell: bash + run: | + set -euxo pipefail + PKG_FULL_NAME="starknet-foundry-v${{ inputs.version }}-${{ matrix.target }}" + echo "PKG_FULL_NAME=$PKG_FULL_NAME" >> $GITHUB_ENV + + ./scripts/package.sh "${{ matrix.target }}" "$PKG_FULL_NAME" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.target }} + path: ${{ env.PKG_FULL_NAME }}.* diff --git a/.github/workflows/_test-binaries.yml b/.github/workflows/_test-binaries.yml index 1431a448ac..afa1edd0a9 100644 --- a/.github/workflows/_test-binaries.yml +++ b/.github/workflows/_test-binaries.yml @@ -24,9 +24,13 @@ jobs: - target: x86_64-unknown-linux-gnu os: ubuntu-latest - - target: x86_64-apple-darwin + - target: aarch64-apple-darwin os: macos-latest + - target: x86_64-apple-darwin + # Target macos-latest uses Mac with ARM, macos-14-large is still on Intel + os: macos-14-large + steps: - uses: actions/checkout@v5 - uses: software-mansion/setup-scarb@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e71d1a25c5..ccad5da55d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,20 +18,17 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - run: cargo test --profile ci --lib -p forge build-test-forge-nextest-archive: runs-on: ubuntu-latest - strategy: - fail-fast: false steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + - uses: ./.github/actions/setup-tools + with: + setup-scarb: 'false' + setup-usc: 'false' - name: Install nextest uses: taiki-e/install-action@v2 with: @@ -44,9 +41,30 @@ jobs: name: nextest-archive-${{ runner.os }} path: nextest-archive-${{ runner.os }}.tar.zst + build-test-forge-nextest-archive-native: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/setup-tools + with: + install-llvm: 'true' + setup-scarb: 'false' + setup-usc: 'false' + - name: Install nextest + uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 + - name: Build and archive tests + run: cargo nextest archive --cargo-profile ci -p forge --features cairo-native --archive-file 'nextest-archive-${{ runner.os }}-native.tar.zst' + - name: Upload archive to workflow + uses: actions/upload-artifact@v4 + with: + name: nextest-archive-${{ runner.os }}-native + path: nextest-archive-${{ runner.os }}-native.tar.zst + test-forge-integration: name: Test Forge / Integration Tests - runs-on: [ubuntu-latest] + runs-on: [ ubuntu-latest ] needs: [ build-test-forge-nextest-archive ] strategy: fail-fast: false @@ -54,10 +72,7 @@ jobs: partition: [ 1, 2, 3 ] steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - uses: taiki-e/install-action@v2 with: tool: nextest@0.9.98 @@ -67,6 +82,26 @@ jobs: - name: nextest partition ${{ matrix.partition }}/3 run: cargo nextest run --no-fail-fast --partition 'count:${{ matrix.partition }}/3' --archive-file 'nextest-archive-${{ runner.os }}.tar.zst' integration + test-forge-integration-native: + name: Test Forge / Integration Tests (native) + runs-on: [ ubuntu-latest ] + needs: [ build-test-forge-nextest-archive-native ] + strategy: + fail-fast: false + matrix: + partition: [ 1, 2, 3 ] + steps: + - uses: actions/checkout@v5 + - uses: ./.github/actions/setup-tools + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 + - uses: actions/download-artifact@v4 + with: + name: nextest-archive-${{ runner.os }}-native + - name: nextest partition ${{ matrix.partition }}/3 + run: cargo nextest run --no-fail-fast --partition 'count:${{ matrix.partition }}/3' --archive-file 'nextest-archive-${{ runner.os }}-native.tar.zst' integration + test-forge-e2e: name: Test Forge / E2E Tests runs-on: ubuntu-latest @@ -104,11 +139,8 @@ jobs: curl -L https://raw.githubusercontent.com/software-mansion/cairo-coverage/main/scripts/install.sh | sh - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - uses: taiki-e/install-action@v2 with: tool: nextest@0.9.98 @@ -118,15 +150,63 @@ jobs: - name: nextest partition ${{ matrix.partition }}/3 run: cargo nextest run --no-fail-fast --partition 'count:${{ matrix.partition }}/3' --archive-file 'nextest-archive-${{ runner.os }}.tar.zst' e2e + test-forge-e2e-native: + name: Test Forge / E2E Tests (native) + runs-on: ubuntu-latest + needs: [ build-test-forge-nextest-archive-native ] + strategy: + fail-fast: false + matrix: + partition: [ 1, 2, 3 ] + steps: + - name: Extract branch name + if: github.event_name != 'pull_request' + run: echo "BRANCH_NAME=$(echo ${GITHUB_REF#refs/heads/})" >> $GITHUB_ENV + shell: bash + + - name: Extract branch name on pull request + if: github.event_name == 'pull_request' + run: echo "BRANCH_NAME=$(echo $GITHUB_HEAD_REF)" >> $GITHUB_ENV + shell: bash + + - name: Extract repo name and owner + if: github.event_name != 'pull_request' + run: echo "REPO_NAME=$(echo ${{ github.repository }}.git)" >> $GITHUB_ENV + shell: bash + + - name: Extract repo name and owner on pull request + if: github.event_name == 'pull_request' + run: echo "REPO_NAME=$(echo ${{ github.event.pull_request.head.repo.full_name }}.git)" >> $GITHUB_ENV + shell: bash + + - name: Install cairo-profiler + run: | + curl -L https://raw.githubusercontent.com/software-mansion/cairo-profiler/main/scripts/install.sh | sh + - name: Install cairo-coverage + run: | + curl -L https://raw.githubusercontent.com/software-mansion/cairo-coverage/main/scripts/install.sh | sh + + - uses: actions/checkout@v5 + - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 + - uses: ./.github/actions/setup-tools + - uses: taiki-e/install-action@v2 + with: + tool: nextest@0.9.98 + - uses: actions/download-artifact@v4 + with: + name: nextest-archive-${{ runner.os }}-native + - name: nextest partition ${{ matrix.partition }}/3 + run: cargo nextest run --no-fail-fast --partition 'count:${{ matrix.partition }}/3' --archive-file 'nextest-archive-${{ runner.os }}-native.tar.zst' e2e + test-plugin-checks: name: Test plugin across different scarb versions runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-universal-sierra-compiler@v1 - # No setup scarb action is used because we want to install multiple versions of scarb through asdf + - uses: ./.github/actions/setup-tools + # No setup scarb action is used because we want to install multiple versions of scarb through asdf + with: + setup-scarb: 'false' - uses: asdf-vm/actions/install@1902764435ca0dd2f3388eea723a4f92a4eb8302 - run: cargo test --profile ci --package forge --features test_for_multiple_scarb_versions e2e::plugin_diagnostic @@ -135,9 +215,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools + with: + setup-scarb: 'false' - run: cargo test --profile ci --package forge --features no_scarb_installed --lib compatibility_check::tests::failing_tool_not_installed @@ -152,8 +232,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + - uses: ./.github/actions/setup-tools - run: cargo test --profile ci -p forge_runner test-cheatnet: @@ -161,10 +240,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - name: Run Cheatnet tests run: cargo test --profile ci -p cheatnet @@ -173,8 +249,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + - uses: ./.github/actions/setup-tools - name: Run Data Transformer tests run: cargo test --profile ci -p data-transformer @@ -183,10 +258,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - name: Run Forge Scarb Plugin tests working-directory: crates/snforge-scarb-plugin run: cargo test --profile ci @@ -196,10 +268,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - name: Run Forge Scarb Plugin tests working-directory: crates/snforge-scarb-plugin-deprecated run: cargo test --profile ci @@ -209,13 +278,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b - with: - toolchain: stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - uses: asdf-vm/actions/install@05e0d2ed97b598bfce82fd30daf324ae0c4570e6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - name: Run tests run: cargo test --profile ci -p sncast @@ -224,10 +288,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + - uses: ./.github/actions/setup-tools with: - toolchain: stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + setup-scarb: 'false' + setup-usc: 'false' - name: Run tests run: cargo test --profile ci -p conversions @@ -236,8 +300,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + - uses: ./.github/actions/setup-tools + with: + setup-scarb: 'false' + setup-usc: 'false' - run: cargo test --profile ci -p shared test-scarb-api: @@ -245,11 +311,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: software-mansion/setup-universal-sierra-compiler@v1 - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - run: cargo test --profile ci -p scarb-api scarbfmt: @@ -296,11 +358,11 @@ jobs: RUSTFLAGS: "-Dwarnings" steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + - uses: ./.github/actions/setup-tools with: - toolchain: stable - components: clippy - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + install-llvm: 'true' + setup-scarb: 'false' + setup-usc: 'false' - run: cargo lint - name: Lint snforge-scarb-plugin @@ -317,13 +379,8 @@ jobs: env: MDBOOK_VERSION: 0.4.52 steps: - - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b - with: - toolchain: stable - - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 - uses: actions/checkout@v5 - - uses: software-mansion/setup-scarb@v1 - - uses: software-mansion/setup-universal-sierra-compiler@v1 + - uses: ./.github/actions/setup-tools - name: Install mdBook run: | cargo install --version ${MDBOOK_VERSION} mdbook diff --git a/CAIRO_NATIVE.md b/CAIRO_NATIVE.md new file mode 100644 index 0000000000..94ec2b00bd --- /dev/null +++ b/CAIRO_NATIVE.md @@ -0,0 +1,41 @@ +# Running Cairo Native + + +* [Running Cairo Native](#running-cairo-native) + * [Installing Starknet Foundry With Cairo Native Support](#installing-starknet-foundry-with-cairo-native-support) + * [LLVM](#llvm) + * [`ld`](#ld) + * [Linux](#linux) + * [MacOS](#macos) + * [Running Tests](#running-tests) + + +## Installing Starknet Foundry With Cairo Native Support + +Cairo Native introduces additional dependencies outside of the Rust ecosystem. + +### LLVM + +LLVM is linked into Starknet Foundry binary, so it doesn't have to be installed separately at the cost an incrased +binary size. + +### `ld` + +Cairo Native crate makes direct calls to `ld`, which must in turn be installed on the system. + +#### Linux + +The package `binutils` contains `ld`, install it with package manager relevant to your distribution or build it +from source. + +#### MacOS + +`ld` is part of the Xcode command line tools. Install it with `xcode-select --install`. + +## Running Tests + +To run tests run `snforge test --run-native`, without the flag, the execution will still run in the VM. + +When running native features that rely on VM trace like test backtrace, profiler, coverage will not work. +Running native enforces the test to run with `sierra-gas` as tracked resource. Tracking vm resources is not possible +with the native execution. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 22187c54d7..8de033f447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,6 +135,24 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "apollo_compilation_utils" +version = "0.15.0-rc.4" +source = "git+https://github.com/software-mansion-labs/sequencer.git?branch=main-v0.14.0#57447e3e8897d4e7ce7f3ec8d23af58d5b6bf1a7" +dependencies = [ + "apollo_infra_utils", + "cairo-lang-sierra", + "cairo-lang-starknet-classes", + "cairo-lang-utils", + "rlimit", + "serde", + "serde_json", + "starknet-types-core", + "starknet_api", + "tempfile", + "thiserror 1.0.69", +] + [[package]] name = "apollo_compile_to_native_types" version = "0.15.0-rc.4" @@ -170,7 +188,7 @@ source = "git+https://github.com/software-mansion-labs/sequencer.git?branch=main dependencies = [ "apollo_proc_macros", "assert-json-diff", - "colored", + "colored 3.0.0", "num_enum", "serde", "serde_json", @@ -204,6 +222,20 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "aquamarine" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f50776554130342de4836ba542aa85a4ddb361690d7e8df13774d7284c3d5c2" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "arbitrary" version = "1.4.2" @@ -238,7 +270,7 @@ dependencies = [ "ark-poly 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", - "educe", + "educe 0.6.0", "fnv", "hashbrown 0.15.5", "itertools 0.13.0", @@ -280,7 +312,7 @@ dependencies = [ "ark-std 0.5.0", "arrayvec", "digest", - "educe", + "educe 0.6.0", "itertools 0.13.0", "num-bigint", "num-traits", @@ -357,7 +389,7 @@ dependencies = [ "ark-ff 0.5.0", "ark-serialize 0.5.0", "ark-std 0.5.0", - "educe", + "educe 0.6.0", "fnv", "hashbrown 0.15.5", ] @@ -695,6 +727,26 @@ dependencies = [ "virtue", ] +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn 2.0.106", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -761,6 +813,7 @@ version = "0.15.0-rc.4" source = "git+https://github.com/software-mansion-labs/sequencer.git?branch=main-v0.14.0#57447e3e8897d4e7ce7f3ec8d23af58d5b6bf1a7" dependencies = [ "anyhow", + "apollo_compilation_utils", "apollo_compile_to_native_types", "apollo_config", "apollo_infra_utils", @@ -774,6 +827,7 @@ dependencies = [ "cairo-lang-casm", "cairo-lang-runner", "cairo-lang-starknet-classes", + "cairo-native", "cairo-vm", "dashmap", "derive_more 0.99.20", @@ -1090,7 +1144,7 @@ dependencies = [ "cairo-lang-syntax", "cairo-lang-syntax-codegen", "cairo-lang-utils", - "colored", + "colored 3.0.0", "itertools 0.14.0", "num-bigint", "num-traits", @@ -1456,7 +1510,7 @@ checksum = "ebbd4ebcd82ab07fba3d376a6aa992aa552fcb7f051736f6b5a2122381754bdb" dependencies = [ "cairo-lang-formatter", "cairo-lang-utils", - "colored", + "colored 3.0.0", "log", "pretty_assertions", ] @@ -1478,6 +1532,61 @@ dependencies = [ "smol_str", ] +[[package]] +name = "cairo-native" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a1b73479b3b3676bf81d2e3586f7e7d234227d7bdb6d8635b0256af7a41592" +dependencies = [ + "anyhow", + "aquamarine", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-secp256k1 0.5.0", + "ark-secp256r1 0.5.0", + "bumpalo", + "cairo-lang-compiler", + "cairo-lang-defs", + "cairo-lang-filesystem", + "cairo-lang-runner", + "cairo-lang-semantic", + "cairo-lang-sierra", + "cairo-lang-sierra-ap-change", + "cairo-lang-sierra-gas", + "cairo-lang-sierra-to-casm", + "cairo-lang-starknet", + "cairo-lang-starknet-classes", + "cairo-lang-test-plugin", + "cairo-lang-utils", + "cc", + "clap", + "colored 2.2.0", + "educe 0.5.11", + "itertools 0.14.0", + "keccak", + "lazy_static 1.5.0", + "libc", + "libloading", + "llvm-sys", + "melior", + "mlir-sys", + "num-bigint", + "num-integer", + "num-traits", + "rand 0.9.2", + "serde", + "serde_json", + "sha2", + "starknet-curve", + "starknet-types-core", + "stats_alloc", + "tempfile", + "thiserror 2.0.17", + "tracing", + "tracing-subscriber", + "utf8_iter", +] + [[package]] name = "cairo-serde-macros" version = "1.0.0" @@ -1606,6 +1715,15 @@ dependencies = [ "winx", ] +[[package]] +name = "caseless" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + [[package]] name = "cc" version = "1.2.35" @@ -1618,6 +1736,15 @@ dependencies = [ "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -1641,6 +1768,7 @@ dependencies = [ "cairo-lang-casm", "cairo-lang-starknet-classes", "cairo-lang-utils", + "cairo-native", "cairo-vm", "camino", "conversions", @@ -1701,6 +1829,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.48" @@ -1776,6 +1915,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static 1.5.0", + "windows-sys 0.59.0", +] + [[package]] name = "colored" version = "3.0.0" @@ -1785,6 +1934,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "comrak" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39bff2cbb80102771ca62bd2375bc6f6611dc1493373440b23aa08a155538708" +dependencies = [ + "caseless", + "entities", + "memchr", + "slug", + "typed-arena", + "unicode_categories", +] + [[package]] name = "config" version = "0.14.1" @@ -1928,6 +2091,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "convert_case" version = "0.8.0" @@ -2477,6 +2649,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "dialoguer" version = "0.11.0" @@ -2681,6 +2859,18 @@ dependencies = [ "spki", ] +[[package]] +name = "educe" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "educe" version = "0.6.0" @@ -2762,6 +2952,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" + [[package]] name = "enum-ordinalize" version = "4.3.0" @@ -4222,6 +4418,16 @@ version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.0", +] + [[package]] name = "libm" version = "0.2.15" @@ -4256,6 +4462,20 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "llvm-sys" +version = "191.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "893cddf1adf0354b93411e413553dd4daf5c43195d73f1acfa1e394bdd371456" +dependencies = [ + "anyhow", + "cc", + "lazy_static 1.5.0", + "libc", + "regex-lite", + "semver", +] + [[package]] name = "lock_api" version = "0.4.13" @@ -4333,6 +4553,32 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" +[[package]] +name = "melior" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2af6454b7bcd7edc8c2060a3726a18ceaed60e25c34d9f8de9c6b44e82eb647" +dependencies = [ + "melior-macro", + "mlir-sys", +] + +[[package]] +name = "melior-macro" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99671327250df8e24e56d8304474a970e7a2c6bb8f6dc71382d188136fe4d1b" +dependencies = [ + "comrak", + "convert_case 0.7.1", + "proc-macro2", + "quote", + "regex", + "syn 2.0.106", + "tblgen", + "unindent", +] + [[package]] name = "memchr" version = "2.7.5" @@ -4409,6 +4655,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "mlir-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee4047ffefa7e9853412025a98b38a66968584543918cf084a6e4df9144b71b" +dependencies = [ + "bindgen", +] + [[package]] name = "mockall" version = "0.12.1" @@ -4442,6 +4697,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b52c1b33ff98142aecea13138bd399b68aa7ab5d9546c300988c345004001eea" +[[package]] +name = "native-api" +version = "0.50.0" +dependencies = [ + "cairo-lang-starknet-classes", + "cairo-native", + "starknet_api", + "thiserror 2.0.17", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -5148,6 +5413,16 @@ dependencies = [ "yansi", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -5216,6 +5491,27 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -5562,6 +5858,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" + [[package]] name = "regex-syntax" version = "0.8.6" @@ -5642,6 +5944,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + [[package]] name = "rlp" version = "0.5.2" @@ -5965,10 +6276,14 @@ version = "1.0.0" dependencies = [ "anyhow", "assert_fs", + "cairo-lang-starknet-classes", + "cairo-native", "camino", "foundry-ui", "indoc", "itertools 0.14.0", + "native-api", + "num-bigint", "rayon", "regex", "scarb-metadata", @@ -5978,6 +6293,7 @@ dependencies = [ "serde_json", "shared", "thiserror 2.0.17", + "tracing", "universal-sierra-compiler-api", "which", ] @@ -6348,6 +6664,7 @@ dependencies = [ "starknet", "starknet-types-core", "starknet_api", + "thiserror 2.0.17", "url", ] @@ -6409,6 +6726,16 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -6792,6 +7119,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stats_alloc" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e04424e733e69714ca1bbb9204c1a57f09f5493439520f9f68c132ad25eec" + [[package]] name = "str-buf" version = "1.0.6" @@ -6982,6 +7315,18 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +[[package]] +name = "tblgen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c155c9310c9e11e6f642b4c8a30ae572ea0cad013d5c9e28bb264b52fa8163bb" +dependencies = [ + "bindgen", + "cc", + "paste", + "thiserror 2.0.17", +] + [[package]] name = "tempfile" version = "3.23.0" @@ -7404,6 +7749,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.20" @@ -7414,12 +7769,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] @@ -7459,6 +7817,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typeid" version = "1.0.3" @@ -7585,18 +7949,34 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + [[package]] name = "universal-sierra-compiler-api" version = "1.0.0" dependencies = [ "anyhow", "cairo-lang-casm", + "cairo-lang-starknet-classes", "camino", "num-bigint", "serde", "serde_json", "shared", + "strum_macros 0.27.2", "tempfile", + "thiserror 2.0.17", + "tracing", "which", ] diff --git a/Cargo.toml b/Cargo.toml index b7a080a82c..b5d87a4166 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "crates/debugging", "crates/testing/packages_validation", "crates/foundry-ui", + "crates/native-api", ] exclude = ["crates/snforge-scarb-plugin", "crates/snforge-scarb-plugin-deprecated"] @@ -38,6 +39,7 @@ blockifier = { git = "https://github.com/software-mansion-labs/sequencer.git", b bigdecimal = "0.4.8" # TODO(#3770) Use starknet_api directly starknet_api = { git = "https://github.com/software-mansion-labs/sequencer.git", branch = "main-v0.14.0" } +cairo-native = "0.6.2" cairo-lang-casm = { version = "2.12.3", features = ["serde"] } cairo-lang-sierra = "2.12.3" cairo-lang-utils = "2.12.3" diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000000..21aafe8289 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,20 @@ +# TODO(#3790) Setup cross for native +#[build] +#pre-build = [ +# "dpkg --add-architecture $CROSS_DEB_ARCH", +# "apt-get update", +# "apt-get install -y --no-install-recommends ca-certificates gnupg wget software-properties-common lsb-release", +# "apt-get install -y --no-install-recommends zlib1g-dev libzstd-dev zlib1g-dev:$CROSS_DEB_ARCH libzstd-dev:$CROSS_DEB_ARCH", +# "echo \"deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main\" > /etc/apt/sources.list.d/llvm-toolchain-focal.list", +# "echo \"deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main\" >> /etc/apt/sources.list.d/llvm-toolchain-focal.list", +# "wget -qO - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -", +# "apt-get update", +# "apt-get install -y --no-install-recommends llvm-19 llvm-19-dev llvm-19-runtime clang-19 clang-tools-19 lld-19 libpolly-19-dev libmlir-19-dev mlir-19-tools", +#] +# +#[target.x86_64-unknown-linux-gnu.env] +#passthrough = [ +# "MLIR_SYS_190_PREFIX=/usr/lib/llvm-19", +# "LLVM_SYS_191_PREFIX=/usr/lib/llvm-19", +# "TABLEGEN_190_PREFIX=/usr/lib/llvm-19", +#] diff --git a/crates/cheatnet/Cargo.toml b/crates/cheatnet/Cargo.toml index b999b5d710..42ff069953 100644 --- a/crates/cheatnet/Cargo.toml +++ b/crates/cheatnet/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [features] testing = [] +cairo-native = ["dep:cairo-native", "blockifier/cairo_native"] [dependencies] anyhow.workspace = true @@ -43,7 +44,8 @@ p256.workspace = true shared.workspace = true rand.workspace = true foundry-ui = { path = "../foundry-ui" } -scarb-oracle-hint-service.workspace = true +scarb-oracle-hint-service.workspace = true +cairo-native = { workspace = true, optional = true } [dev-dependencies] ctor.workspace = true diff --git a/crates/cheatnet/src/forking/state.rs b/crates/cheatnet/src/forking/state.rs index b676b3069a..6b53e53ba1 100644 --- a/crates/cheatnet/src/forking/state.rs +++ b/crates/cheatnet/src/forking/state.rs @@ -28,7 +28,7 @@ use std::cell::{Ref, RefCell}; use std::collections::HashMap; use std::io::Read; use std::sync::Arc; -use universal_sierra_compiler_api::{SierraType, compile_sierra}; +use universal_sierra_compiler_api::compile_contract_sierra; use url::Url; #[derive(Debug)] @@ -239,13 +239,10 @@ impl StateReader for ForkStateReader { SierraVersion::extract_from_program(&flattened_class.sierra_program) .expect("Unable to extract Sierra version from Sierra program"); - match compile_sierra::(&sierra_contract_class, &SierraType::Contract) { + match compile_contract_sierra(&sierra_contract_class) { Ok(casm_contract_class_raw) => Ok(RunnableCompiledClass::V1( - CompiledClassV1::try_from_json_string( - &casm_contract_class_raw, - sierra_version, - ) - .expect("Unable to create RunnableCompiledClass::V1"), + CompiledClassV1::try_from((casm_contract_class_raw, sierra_version)) + .expect("Unable to create RunnableCompiledClass::V1"), )), Err(err) => Err(StateReadError(err.to_string())), } diff --git a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs index ce6eb4f7c3..3b36f9d577 100644 --- a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs +++ b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/cheated_syscalls.rs @@ -82,15 +82,14 @@ pub fn deploy_syscall( request.constructor_calldata.0.len(), ); - // region: Modified blockifier code - let deployer_address = syscall_handler.storage_address(); - // endregion + let deployer_address = syscall_handler.base.call.storage_address; let deployer_address_for_calculation = if request.deploy_from_zero { ContractAddress::default() } else { deployer_address }; + // region: Modified blockifier code let deployed_contract_address = if let Some(contract_address) = cheatnet_state.next_address_for_deployment() { contract_address @@ -102,6 +101,7 @@ pub fn deploy_syscall( deployer_address_for_calculation, )? }; + // endregion let ctor_context = ConstructorContext { class_hash: request.class_hash, diff --git a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/entry_point.rs b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/entry_point.rs index 623c7b1a15..55ba5f6749 100644 --- a/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/entry_point.rs +++ b/crates/cheatnet/src/runtime_extensions/call_to_blockifier_runtime_extension/execution/entry_point.rs @@ -5,6 +5,8 @@ use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution:: use crate::runtime_extensions::call_to_blockifier_runtime_extension::CheatnetState; use crate::runtime_extensions::common::{get_relocated_vm_trace}; use crate::state::{CheatStatus}; +#[cfg(feature = "cairo-native")] +use crate::runtime_extensions::native::execution::execute_entry_point_call_native; use blockifier::execution::call_info::{CallExecution, Retdata, StorageAccessTracker}; use blockifier::execution::contract_class::{RunnableCompiledClass, TrackedResource}; use blockifier::execution::entry_point::EntryPointRevertInfo; @@ -173,6 +175,26 @@ pub fn execute_call_entry_point( cheatnet_state, context, ), + #[cfg(feature = "cairo-native")] + RunnableCompiledClass::V1Native(native_compiled_class_v1) => { + if context.tracked_resource_stack.last() == Some(&TrackedResource::CairoSteps) { + execute_entry_point_call_cairo1( + entry_point.clone(), + &native_compiled_class_v1.casm(), + state, + cheatnet_state, + context, + ) + } else { + execute_entry_point_call_native( + &entry_point, + &native_compiled_class_v1, + state, + cheatnet_state, + context, + ) + } + } }; context .tracked_resource_stack diff --git a/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/cheat_block_hash.rs b/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/cheat_block_hash.rs index 2d497cd16f..990ae9672e 100644 --- a/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/cheat_block_hash.rs +++ b/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/cheat_block_hash.rs @@ -78,13 +78,11 @@ impl CheatnetState { self.cheat_block_hash(block_number, Operation::StopGlobal); } - #[expect(clippy::result_large_err)] - pub fn get_block_hash_for_contract( + pub fn get_cheated_block_hash_for_contract( &mut self, contract_address: ContractAddress, block_number: u64, - syscall_handler: &mut SyscallHintProcessor, - ) -> SyscallResult { + ) -> Option { if let Some((cheat_span, block_hash)) = self .block_hash_contracts .get(&(contract_address, block_number)) @@ -110,17 +108,33 @@ impl CheatnetState { } CheatSpan::Indefinite => {} } - return Ok(BlockHash(StarkHash::from(block_hash))); + return Some(BlockHash(StarkHash::from(block_hash))); } if let Some((block_hash, except)) = self.global_block_hash.get(&block_number) && !except.contains(&contract_address) { - return Ok(BlockHash(StarkHash::from(*block_hash))); + return Some(BlockHash(StarkHash::from(*block_hash))); } - Ok(BlockHash( - syscall_handler.base.get_block_hash(block_number)?, - )) + None + } + + #[expect(clippy::result_large_err)] + pub fn get_block_hash_for_contract( + &mut self, + contract_address: ContractAddress, + block_number: u64, + syscall_handler: &mut SyscallHintProcessor, + ) -> SyscallResult { + let cheated_block_hash = + self.get_cheated_block_hash_for_contract(contract_address, block_number); + if let Some(cheated_block_hash) = cheated_block_hash { + Ok(cheated_block_hash) + } else { + Ok(BlockHash( + syscall_handler.base.get_block_hash(block_number)?, + )) + } } } diff --git a/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/declare.rs b/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/declare.rs index 1a229e3113..de7c41dd0b 100644 --- a/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/declare.rs +++ b/crates/cheatnet/src/runtime_extensions/forge_runtime_extension/cheatcodes/declare.rs @@ -5,9 +5,12 @@ use crate::runtime_extensions::forge_runtime_extension::{ }; use anyhow::{Context, Result}; use blockifier::execution::contract_class::{CompiledClassV1, RunnableCompiledClass}; +#[cfg(feature = "cairo-native")] +use blockifier::execution::native::contract_class::NativeCompiledClassV1; use blockifier::state::{errors::StateError, state_api::State}; use conversions::IntoConv; use conversions::serde::serialize::CairoSerialize; +use scarb_api::StarknetContractArtifacts; use starknet::core::types::contract::SierraClass; use starknet_api::core::{ClassHash, CompiledClassHash}; @@ -27,12 +30,7 @@ pub fn declare( .with_context(|| format!("Failed to get contract artifact for name = {contract_name}.")) .map_err(EnhancedHintError::from)?; - let contract_class = CompiledClassV1::try_from_json_string( - &contract_artifact.casm, - get_current_sierra_version(), - ) - .expect("Failed to read contract class from json"); - let contract_class = RunnableCompiledClass::V1(contract_class); + let contract_class = get_contract_class(contract_artifact); let class_hash = *contracts_data .get_class_hash(contract_name) @@ -68,3 +66,20 @@ pub fn declare( pub fn get_class_hash(sierra_class: &SierraClass) -> Result { Ok(sierra_class.class_hash()?.into_()) } + +fn get_contract_class(contract_artifact: &StarknetContractArtifacts) -> RunnableCompiledClass { + let contract_class = + CompiledClassV1::try_from((contract_artifact.casm.clone(), get_current_sierra_version())) + .expect("Failed to read contract class from json"); + + #[cfg(feature = "cairo-native")] + return match &contract_artifact.executor { + None => RunnableCompiledClass::V1(contract_class), + Some(executor) => RunnableCompiledClass::V1Native(NativeCompiledClassV1::new( + executor.clone(), + contract_class, + )), + }; + #[cfg(not(feature = "cairo-native"))] + RunnableCompiledClass::V1(contract_class) +} diff --git a/crates/cheatnet/src/runtime_extensions/mod.rs b/crates/cheatnet/src/runtime_extensions/mod.rs index 69a31b68be..ac32c9bdf1 100644 --- a/crates/cheatnet/src/runtime_extensions/mod.rs +++ b/crates/cheatnet/src/runtime_extensions/mod.rs @@ -4,3 +4,5 @@ pub mod common; pub mod deprecated_cheatable_starknet_extension; pub mod forge_config_extension; pub mod forge_runtime_extension; +#[cfg(feature = "cairo-native")] +mod native; diff --git a/crates/cheatnet/src/runtime_extensions/native/call.rs b/crates/cheatnet/src/runtime_extensions/native/call.rs new file mode 100644 index 0000000000..24270c8605 --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/call.rs @@ -0,0 +1,72 @@ +use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::entry_point::{ + ExecuteCallEntryPointExtraOptions, execute_call_entry_point, +}; +use crate::runtime_extensions::native::native_syscall_handler::BaseSyscallResult; +use crate::state::CheatnetState; +use blockifier::execution::call_info::CallInfo; +use blockifier::execution::entry_point::CallEntryPoint; +use blockifier::execution::syscalls::hint_processor::{ + ENTRYPOINT_FAILED_ERROR, SyscallExecutionError, +}; +use blockifier::execution::syscalls::syscall_base::SyscallHandlerBase; +use starknet_types_core::felt::Felt; + +// Based on https://github.com/software-mansion-labs/sequencer/blob/57447e3e8897d4e7ce7f3ec8d23af58d5b6bf1a7/crates/blockifier/src/execution/syscalls/syscall_base.rs#L435 +#[expect(clippy::mut_mut)] +#[expect(clippy::result_large_err)] +pub fn execute_inner_call( + // region: Modified blockifier code + syscall_handler_base: &mut SyscallHandlerBase, + cheatnet_state: &mut CheatnetState, + call: &mut CallEntryPoint, + remaining_gas: &mut u64, +) -> BaseSyscallResult> { + // endregion + let revert_idx = syscall_handler_base.context.revert_infos.0.len(); + + // region: Modified blockifier code + let call_info = execute_call_entry_point( + call, + syscall_handler_base.state, + cheatnet_state, + syscall_handler_base.context, + remaining_gas, + &ExecuteCallEntryPointExtraOptions { + trace_data_handled_by_revert_call: false, + }, + )?; + // endregion + + let mut raw_retdata = call_info.execution.retdata.0.clone(); + let failed = call_info.execution.failed; + syscall_handler_base.inner_calls.push(call_info); + if failed { + syscall_handler_base + .context + .revert(revert_idx, syscall_handler_base.state)?; + + // Delete events and l2_to_l1_messages from the reverted call. + let reverted_call = &mut syscall_handler_base.inner_calls.last_mut().unwrap(); + let mut stack: Vec<&mut CallInfo> = vec![reverted_call]; + while let Some(call_info) = stack.pop() { + call_info.execution.events.clear(); + call_info.execution.l2_to_l1_messages.clear(); + // Add inner calls that did not fail to the stack. + // The events and l2_to_l1_messages of the failed calls were already cleared. + stack.extend( + call_info + .inner_calls + .iter_mut() + .filter(|call_info| !call_info.execution.failed), + ); + } + + raw_retdata + .push(Felt::from_hex(ENTRYPOINT_FAILED_ERROR).map_err(SyscallExecutionError::from)?); + return Err(SyscallExecutionError::Revert { + error_data: raw_retdata, + }); + } + + Ok(raw_retdata) +} diff --git a/crates/cheatnet/src/runtime_extensions/native/deploy.rs b/crates/cheatnet/src/runtime_extensions/native/deploy.rs new file mode 100644 index 0000000000..6016c34856 --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/deploy.rs @@ -0,0 +1,67 @@ +use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::cheated_syscalls::execute_deployment; +use crate::runtime_extensions::native::native_syscall_handler::BaseSyscallResult; +use crate::state::CheatnetState; +use blockifier::execution::call_info::CallInfo; +use blockifier::execution::entry_point::ConstructorContext; +use blockifier::execution::syscalls::syscall_base::SyscallHandlerBase; +use blockifier::execution::syscalls::vm_syscall_utils::SyscallSelector; +use starknet_api::core::{ClassHash, ContractAddress, calculate_contract_address}; +use starknet_api::transaction::fields::{Calldata, ContractAddressSalt}; + +#[expect(clippy::match_bool, clippy::result_large_err)] +// Copied from blockifer/src/execution/syscalls/syscall_base.rs +pub fn deploy( + syscall_handler_base: &mut SyscallHandlerBase, + cheatnet_state: &mut CheatnetState, + class_hash: ClassHash, + contract_address_salt: ContractAddressSalt, + constructor_calldata: Calldata, + deploy_from_zero: bool, + remaining_gas: &mut u64, +) -> BaseSyscallResult<(ContractAddress, CallInfo)> { + syscall_handler_base + .increment_syscall_linear_factor_by(&SyscallSelector::Deploy, constructor_calldata.0.len()); + + // TODO(#3790) support for reject + // let versioned_constants = &syscall_handler_base + // .context + // .tx_context + // .block_context + // .versioned_constants; + // if should_reject_deploy( + // versioned_constants.disable_deploy_in_validation_mode, + // syscall_handler_base.context.execution_mode, + // ) { + // syscall_handler_base.reject_syscall_in_validate_mode("deploy")?; + // } + + let deployer_address = syscall_handler_base.call.storage_address; + let deployer_address_for_calculation = match deploy_from_zero { + true => ContractAddress::default(), + false => deployer_address, + }; + let deployed_contract_address = calculate_contract_address( + contract_address_salt, + class_hash, + &constructor_calldata, + deployer_address_for_calculation, + )?; + + let ctor_context = ConstructorContext { + class_hash, + code_address: Some(deployed_contract_address), + storage_address: deployed_contract_address, + caller_address: deployer_address, + }; + // region: Modified blockifer code + let call_info = execute_deployment( + syscall_handler_base.state, + cheatnet_state, + syscall_handler_base.context, + &ctor_context, + constructor_calldata, + remaining_gas, + )?; + // endregion + Ok((deployed_contract_address, call_info)) +} diff --git a/crates/cheatnet/src/runtime_extensions/native/execution.rs b/crates/cheatnet/src/runtime_extensions/native/execution.rs new file mode 100644 index 0000000000..d85570d82e --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/execution.rs @@ -0,0 +1,216 @@ +use crate::runtime_extensions::call_to_blockifier_runtime_extension::execution::entry_point::{ + CallInfoWithExecutionData, ContractClassEntryPointExecutionResult, +}; +use crate::runtime_extensions::native::native_syscall_handler::CheatableNativeSyscallHandler; +use crate::state::CheatnetState; +use blockifier::execution::call_info::{BuiltinCounterMap, CallExecution, CallInfo, Retdata}; +use blockifier::execution::contract_class::TrackedResource; +use blockifier::execution::entry_point::{ + EntryPointExecutionContext, EntryPointExecutionResult, ExecutableCallEntryPoint, +}; +use blockifier::execution::errors::{ + EntryPointExecutionError, PostExecutionError, PreExecutionError, +}; +use blockifier::execution::native::contract_class::NativeCompiledClassV1; +use blockifier::execution::native::syscall_handler::NativeSyscallHandler; +use blockifier::state::state_api::State; +use blockifier::transaction::objects::ExecutionResourcesTraits; +use blockifier::utils::add_maps; +use cairo_native::execution_result::{BuiltinStats, ContractExecutionResult}; +use cairo_native::utils::BuiltinCosts; +use cairo_vm::types::builtin_name::BuiltinName; +use std::collections::HashMap; +use std::default::Default; + +#[expect(clippy::result_large_err)] +pub(crate) fn execute_entry_point_call_native( + call: &ExecutableCallEntryPoint, + native_compiled_class_v1: &NativeCompiledClassV1, + state: &mut dyn State, + cheatnet_state: &mut CheatnetState, // Added parameter + context: &mut EntryPointExecutionContext, +) -> ContractClassEntryPointExecutionResult { + let mut syscall_handler = CheatableNativeSyscallHandler { + cheatnet_state, + native_syscall_handler: &mut NativeSyscallHandler::new(call.clone(), state, context), + }; + + let call_info = execute_entry_point_call(call, native_compiled_class_v1, &mut syscall_handler)?; + + let syscall_usage = &syscall_handler.native_syscall_handler.base.syscalls_usage; + + Ok(CallInfoWithExecutionData { + call_info, + // Native execution doesn't support VM resources. + // If we got to this point, it means tracked resources are SierraGas. + syscall_usage_vm_resources: HashMap::default(), + syscall_usage_sierra_gas: syscall_usage.clone(), + }) +} + +// Based on https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/entry_point_execution.rs#L20 +#[allow(clippy::result_large_err)] +fn execute_entry_point_call( + call: &ExecutableCallEntryPoint, + compiled_class: &NativeCompiledClassV1, + // region: Modified blockifier code + syscall_handler: &mut CheatableNativeSyscallHandler, + // endregion +) -> EntryPointExecutionResult { + let entry_point = compiled_class.get_entry_point(&call.type_and_selector())?; + + let gas_costs = &syscall_handler + .native_syscall_handler + .base + .context + .gas_costs(); + let builtin_costs = BuiltinCosts { + r#const: 1, + pedersen: gas_costs.builtins.pedersen, + bitwise: gas_costs.builtins.bitwise, + ecop: gas_costs.builtins.ecop, + poseidon: gas_costs.builtins.poseidon, + add_mod: gas_costs.builtins.add_mod, + mul_mod: gas_costs.builtins.mul_mod, + }; + + // Pre-charge entry point's initial budget to ensure sufficient gas for executing a minimal + // entry point code. When redepositing is used, the entry point is aware of this pre-charge + // and adjusts the gas counter accordingly if a smaller amount of gas is required. + let initial_budget = syscall_handler + .native_syscall_handler + .base + .context + .gas_costs() + .base + .entry_point_initial_budget; + let call_initial_gas = syscall_handler + .native_syscall_handler + .base + .call + .initial_gas + .checked_sub(initial_budget) + .ok_or(PreExecutionError::InsufficientEntryPointGas)?; + + let execution_result = compiled_class.executor.run( + entry_point.selector.0, + &syscall_handler + .native_syscall_handler + .base + .call + .calldata + .0 + .clone(), + call_initial_gas, + Some(builtin_costs), + &mut *syscall_handler, + ); + + syscall_handler.native_syscall_handler.finalize(); + + let call_result = execution_result.map_err(EntryPointExecutionError::NativeUnexpectedError)?; + + // TODO(#3790) consider modifying this so it doesn't use take internally + if let Some(error) = syscall_handler.unrecoverable_error() { + return Err(EntryPointExecutionError::NativeUnrecoverableError( + Box::new(error), + )); + } + + create_callinfo(call_result, syscall_handler) +} + +// Copied from https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/entry_point_execution.rs#L73 +#[allow(clippy::result_large_err)] +fn create_callinfo( + call_result: ContractExecutionResult, + syscall_handler: &mut CheatableNativeSyscallHandler<'_>, +) -> Result { + let remaining_gas = call_result.remaining_gas; + + if remaining_gas > syscall_handler.native_syscall_handler.base.call.initial_gas { + return Err(PostExecutionError::MalformedReturnData { + error_message: format!( + "Unexpected remaining gas. Used gas is greater than initial gas: {} > {}", + remaining_gas, syscall_handler.native_syscall_handler.base.call.initial_gas + ), + } + .into()); + } + + let gas_consumed = syscall_handler.native_syscall_handler.base.call.initial_gas - remaining_gas; + let vm_resources = CallInfo::summarize_vm_resources( + syscall_handler + .native_syscall_handler + .base + .inner_calls + .iter(), + ); + + // Retrieve the builtin counts from the syscall handler + let version_constants = syscall_handler + .native_syscall_handler + .base + .context + .versioned_constants(); + let syscall_builtin_counts = version_constants + .get_additional_os_syscall_resources( + &syscall_handler.native_syscall_handler.base.syscalls_usage, + ) + .filter_unused_builtins() + .prover_builtins(); + let entry_point_builtins = builtin_stats_to_builtin_counter_map(call_result.builtin_stats); + let mut builtin_counters = syscall_builtin_counts; + add_maps(&mut builtin_counters, &entry_point_builtins); + + Ok(CallInfo { + call: syscall_handler + .native_syscall_handler + .base + .call + .clone() + .into(), + execution: CallExecution { + retdata: Retdata(call_result.return_values), + events: syscall_handler.native_syscall_handler.base.events.clone(), + cairo_native: true, + l2_to_l1_messages: syscall_handler + .native_syscall_handler + .base + .l2_to_l1_messages + .clone(), + failed: call_result.failure_flag, + gas_consumed, + }, + resources: vm_resources, + inner_calls: syscall_handler + .native_syscall_handler + .base + .inner_calls + .clone(), + storage_access_tracker: syscall_handler + .native_syscall_handler + .base + .storage_access_tracker + .clone(), + tracked_resource: TrackedResource::SierraGas, + builtin_counters, + }) +} + +fn builtin_stats_to_builtin_counter_map(builtin_stats: BuiltinStats) -> BuiltinCounterMap { + let builtins = [ + (BuiltinName::range_check, builtin_stats.range_check), + (BuiltinName::pedersen, builtin_stats.pedersen), + (BuiltinName::bitwise, builtin_stats.bitwise), + (BuiltinName::ec_op, builtin_stats.ec_op), + (BuiltinName::poseidon, builtin_stats.poseidon), + (BuiltinName::range_check96, builtin_stats.range_check96), + (BuiltinName::add_mod, builtin_stats.add_mod), + (BuiltinName::mul_mod, builtin_stats.mul_mod), + ]; + builtins + .into_iter() + .filter(|(_, count)| *count > 0) + .collect() +} diff --git a/crates/cheatnet/src/runtime_extensions/native/mod.rs b/crates/cheatnet/src/runtime_extensions/native/mod.rs new file mode 100644 index 0000000000..be5ecd4266 --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/mod.rs @@ -0,0 +1,4 @@ +mod call; +mod deploy; +pub mod execution; +pub mod native_syscall_handler; diff --git a/crates/cheatnet/src/runtime_extensions/native/native_syscall_handler.rs b/crates/cheatnet/src/runtime_extensions/native/native_syscall_handler.rs new file mode 100644 index 0000000000..e069a260f6 --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/native_syscall_handler.rs @@ -0,0 +1,737 @@ +use crate::runtime_extensions::forge_runtime_extension::cheatcodes::spy_events::Event; +use crate::runtime_extensions::forge_runtime_extension::cheatcodes::spy_messages_to_l1::MessageToL1; +use crate::runtime_extensions::native::call::execute_inner_call; +use crate::runtime_extensions::native::deploy::deploy; +use crate::state::CheatnetState; +use blockifier::execution::call_info::Retdata; +use blockifier::execution::common_hints::ExecutionMode; +use blockifier::execution::entry_point::{CallEntryPoint, CallType}; +use blockifier::execution::errors::EntryPointExecutionError; +use blockifier::execution::native::syscall_handler::NativeSyscallHandler; +use blockifier::execution::syscalls::hint_processor::{OUT_OF_GAS_ERROR, SyscallExecutionError}; +use blockifier::execution::syscalls::vm_syscall_utils::{ + SelfOrRevert, SyscallExecutorBaseError, SyscallSelector, TryExtractRevert, +}; +use blockifier::utils::u64_from_usize; +use cairo_native::starknet::{ + BlockInfo, ExecutionInfo, ExecutionInfoV2, ResourceBounds, Secp256k1Point, Secp256r1Point, + StarknetSyscallHandler, SyscallResult, TxV2Info, U256, +}; +use num_traits::ToPrimitive; +use starknet_api::contract_class::EntryPointType; +use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; +use starknet_api::execution_resources::GasAmount; +use starknet_api::transaction::fields::{Calldata, ContractAddressSalt}; +use starknet_types_core::felt::Felt; +use std::sync::Arc; + +pub struct CheatableNativeSyscallHandler<'a> { + pub native_syscall_handler: &'a mut NativeSyscallHandler<'a>, + pub cheatnet_state: &'a mut CheatnetState, +} + +pub type BaseSyscallResult = Result; + +impl CheatableNativeSyscallHandler<'_> { + // TODO(#3790) consider modifying this so it doesn't use take + pub fn unrecoverable_error(&mut self) -> Option { + self.native_syscall_handler.unrecoverable_error.take() + } + + // Copied from https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/syscall_handler.rs#L80 + /// Handles all gas-related logics, syscall usage counting and perform additional checks. In + /// native, we need to explicitly call this method at the beginning of each syscall. + #[allow(clippy::result_large_err)] + fn pre_execute_syscall( + &mut self, + remaining_gas: &mut u64, + total_gas_cost: u64, + selector: SyscallSelector, + ) -> SyscallResult<()> { + if self.native_syscall_handler.unrecoverable_error.is_some() { + // An unrecoverable error was found in a previous syscall, we return immediately to + // accelerate the end of the execution. The returned data is not important + return Err(vec![]); + } + + // Keccak syscall usages' increments are handled inside its implementation. + if !matches!(selector, SyscallSelector::Keccak) { + self.native_syscall_handler + .base + .increment_syscall_count_by(selector, 1); + } + + // Refund `SYSCALL_BASE_GAS_COST` as it was pre-charged. + let required_gas = total_gas_cost + - self + .native_syscall_handler + .gas_costs() + .base + .syscall_base_gas_cost; + + if *remaining_gas < required_gas { + // Out of gas failure. + return Err(vec![ + Felt::from_hex(OUT_OF_GAS_ERROR) + .expect("Failed to parse OUT_OF_GAS_ERROR hex string"), + ]); + } + + *remaining_gas -= required_gas; + + // To support sierra gas charge for blockifier revert flow, we track the remaining gas left + // before executing a syscall if the current tracked resource is gas. + // 1. If the syscall does not run Cairo code (i.e. not library call, not call contract, and + // not a deploy), any failure will not run in the OS, so no need to charge - the value + // before entering the callback is good enough to charge. + // 2. If the syscall runs Cairo code, but the tracked resource is steps (and not gas), the + // additional charge of reverted cairo steps will cover the inner cost, and the outer + // cost we track here will be the additional reverted gas. + // 3. If the syscall runs Cairo code and the tracked resource is gas, either the inner + // failure will be a Cairo1 revert (and the gas consumed on the call info will override + // the current tracked value), or we will pass through another syscall before failing - + // and by induction (we will reach this point again), the gas will be charged correctly. + self.native_syscall_handler + .base + .context + .update_revert_gas_with_next_remaining_gas(GasAmount(*remaining_gas)); + + Ok(()) + } + + // Based on https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/syscall_handler.rs#L153 + #[allow(clippy::result_large_err)] + fn execute_inner_call( + &mut self, + entry_point: &mut CallEntryPoint, + remaining_gas: &mut u64, + class_hash: ClassHash, + error_wrapper_fn: impl Fn( + SyscallExecutionError, + ClassHash, + ContractAddress, + EntryPointSelector, + ) -> SyscallExecutionError, + ) -> SyscallResult { + let entry_point_clone = entry_point.clone(); + let raw_data = execute_inner_call( + &mut self.native_syscall_handler.base, + self.cheatnet_state, + entry_point, + remaining_gas, + ) + .map_err(|e| { + self.handle_error( + remaining_gas, + SyscallExecutionError::from_self_or_revert(e.try_extract_revert().map_original( + |error| { + error_wrapper_fn( + error, + class_hash, + entry_point_clone.storage_address, + entry_point_clone.entry_point_selector, + ) + }, + )), + ) + })?; + Ok(Retdata(raw_data)) + } + + // Copied from https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/syscall_handler.rs#L124 + fn handle_error(&mut self, remaining_gas: &mut u64, error: SyscallExecutionError) -> Vec { + // In case of more than one inner call and because each inner call has their own + // syscall handler, if there is an unrecoverable error at call `n` it will create a + // `NativeExecutionError`. When rolling back, each call from `n-1` to `1` will also + // store the result of a previous `NativeExecutionError` in a `NativeExecutionError` + // creating multiple wraps around the same error. This function is meant to prevent that. + fn unwrap_native_error(error: SyscallExecutionError) -> SyscallExecutionError { + match error { + SyscallExecutionError::EntryPointExecutionError( + EntryPointExecutionError::NativeUnrecoverableError(e), + ) => *e, + _ => error, + } + } + + match error.try_extract_revert() { + SelfOrRevert::Revert(revert_error) => revert_error.error_data, + SelfOrRevert::Original(error) => { + assert!( + self.native_syscall_handler.unrecoverable_error.is_none(), + "Trying to set an unrecoverable error twice in Native Syscall Handler" + ); + self.native_syscall_handler.unrecoverable_error = Some(unwrap_native_error(error)); + *remaining_gas = 0; + vec![] + } + } + } +} + +impl StarknetSyscallHandler for &mut CheatableNativeSyscallHandler<'_> { + fn get_block_hash( + &mut self, + block_number: u64, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.pre_execute_syscall( + remaining_gas, + self.native_syscall_handler + .gas_costs() + .syscalls + .get_block_hash + .base_syscall_cost(), + SyscallSelector::GetBlockHash, + )?; + + let block_hash = self.cheatnet_state.get_cheated_block_hash_for_contract( + self.native_syscall_handler.base.call.storage_address, + block_number, + ); + + if let Some(block_hash) = block_hash { + Ok(block_hash.0) + } else { + match self + .native_syscall_handler + .base + .get_block_hash(block_number) + { + Ok(value) => Ok(value), + Err(e) => Err(self.handle_error(remaining_gas, e)), + } + } + } + + fn get_execution_info(&mut self, remaining_gas: &mut u64) -> SyscallResult { + self.native_syscall_handler + .get_execution_info(remaining_gas) + } + + #[expect(clippy::too_many_lines)] + fn get_execution_info_v2(&mut self, remaining_gas: &mut u64) -> SyscallResult { + // We don't need to call pre_execute_syscall here because the call to `get_execution_info_v2` + // on the native syscall handler is does that internally, and we don't want to do it twice. + + let original_data = self + .native_syscall_handler + .get_execution_info_v2(remaining_gas)?; + + let cheated_data = self + .cheatnet_state + .get_cheated_data(self.native_syscall_handler.base.call.storage_address); + + let block_number = cheated_data + .block_number + .unwrap_or(original_data.block_info.block_number); + let block_timestamp = cheated_data + .block_timestamp + .unwrap_or(original_data.block_info.block_timestamp); + let sequencer_address = cheated_data.sequencer_address.map_or( + original_data.block_info.sequencer_address, + std::convert::Into::into, + ); + + let version = cheated_data + .tx_info + .version + .unwrap_or(original_data.tx_info.version); + let account_contract_address = cheated_data + .tx_info + .account_contract_address + .unwrap_or(original_data.tx_info.account_contract_address); + let max_fee = cheated_data + .tx_info + .max_fee + .map_or(original_data.tx_info.max_fee, |max_fee| { + max_fee.to_u128().unwrap() + }); + let signature = cheated_data + .tx_info + .signature + .unwrap_or(original_data.tx_info.signature); + let transaction_hash = cheated_data + .tx_info + .transaction_hash + .unwrap_or(original_data.tx_info.transaction_hash); + let chain_id = cheated_data + .tx_info + .chain_id + .unwrap_or(original_data.tx_info.chain_id); + let nonce = cheated_data + .tx_info + .nonce + .unwrap_or(original_data.tx_info.nonce); + // TODO(#3790) implement conversions + let resource_bounds = cheated_data.tx_info.resource_bounds.map_or( + original_data.tx_info.resource_bounds, + |rb| { + rb.iter() + .map(|item| ResourceBounds { + resource: item.resource, + max_amount: item.max_amount, + max_price_per_unit: item.max_price_per_unit, + }) + .collect() + }, + ); + let tip = cheated_data + .tx_info + .tip + .map_or(original_data.tx_info.tip, |tip| tip.to_u128().unwrap()); + let paymaster_data = cheated_data + .tx_info + .paymaster_data + .unwrap_or(original_data.tx_info.paymaster_data); + let nonce_data_availability_mode = cheated_data + .tx_info + .nonce_data_availability_mode + .map_or(original_data.tx_info.nonce_data_availability_mode, |mode| { + mode.to_u32().unwrap() + }); + let fee_data_availability_mode = cheated_data + .tx_info + .fee_data_availability_mode + .map_or(original_data.tx_info.fee_data_availability_mode, |mode| { + mode.to_u32().unwrap() + }); + let account_deployment_data = cheated_data + .tx_info + .account_deployment_data + .unwrap_or(original_data.tx_info.account_deployment_data); + + let caller_address = cheated_data + .caller_address + .map_or(original_data.caller_address, std::convert::Into::into); + let contract_address = cheated_data + .contract_address + .map_or(original_data.contract_address, std::convert::Into::into); + let entry_point_selector = original_data.entry_point_selector; + + Ok(ExecutionInfoV2 { + block_info: BlockInfo { + block_number, + block_timestamp, + sequencer_address, + }, + tx_info: TxV2Info { + version, + account_contract_address, + max_fee, + signature, + transaction_hash, + chain_id, + nonce, + resource_bounds, + tip, + paymaster_data, + nonce_data_availability_mode, + fee_data_availability_mode, + account_deployment_data, + }, + caller_address, + contract_address, + entry_point_selector, + }) + } + + // Based on https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/syscall_handler.rs#L322 + fn deploy( + &mut self, + class_hash: Felt, + contract_address_salt: Felt, + calldata: &[Felt], + deploy_from_zero: bool, + remaining_gas: &mut u64, + ) -> SyscallResult<(Felt, Vec)> { + // The cost of deploying a contract is the base cost plus the linear cost of the calldata + // len. + let total_gas_cost = self + .native_syscall_handler + .gas_costs() + .syscalls + .deploy + .get_syscall_cost(u64_from_usize(calldata.len())); + + self.pre_execute_syscall(remaining_gas, total_gas_cost, SyscallSelector::Deploy)?; + + // region: Modified blockifier code + let (deployed_contract_address, call_info) = deploy( + &mut self.native_syscall_handler.base, + self.cheatnet_state, + ClassHash(class_hash), + ContractAddressSalt(contract_address_salt), + Calldata(Arc::new(calldata.to_vec())), + deploy_from_zero, + remaining_gas, + ) + // endregion + .map_err(|err| self.handle_error(remaining_gas, err))?; + + let constructor_retdata = call_info.execution.retdata.0[..].to_vec(); + self.native_syscall_handler.base.inner_calls.push(call_info); + + Ok((Felt::from(deployed_contract_address), constructor_retdata)) + } + + fn replace_class(&mut self, class_hash: Felt, remaining_gas: &mut u64) -> SyscallResult<()> { + self.native_syscall_handler + .replace_class(class_hash, remaining_gas) + } + + // Based on from https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/syscall_handler.rs#L399 + fn library_call( + &mut self, + class_hash: Felt, + function_selector: Felt, + calldata: &[Felt], + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + self.native_syscall_handler + .gas_costs() + .syscalls + .library_call + .base_syscall_cost(), + SyscallSelector::LibraryCall, + )?; + + let class_hash = ClassHash(class_hash); + + let wrapper_calldata = Calldata(Arc::new(calldata.to_vec())); + + let selector = EntryPointSelector(function_selector); + + let mut entry_point = CallEntryPoint { + class_hash: Some(class_hash), + code_address: None, + entry_point_type: EntryPointType::External, + entry_point_selector: selector, + calldata: wrapper_calldata, + // The call context remains the same in a library call. + storage_address: self.native_syscall_handler.base.call.storage_address, + caller_address: self.native_syscall_handler.base.call.caller_address, + call_type: CallType::Delegate, + initial_gas: *remaining_gas, + }; + + let error_wrapper_function = + |e: SyscallExecutionError, + class_hash: ClassHash, + storage_address: ContractAddress, + selector: EntryPointSelector| { + e.as_lib_call_execution_error(class_hash, storage_address, selector) + }; + + Ok(self + .execute_inner_call( + &mut entry_point, + remaining_gas, + class_hash, + error_wrapper_function, + )? + .0) + } + + // Based on https://github.com/software-mansion-labs/sequencer/blob/b6d1c0b354d84225ab9c47f8ff28663d22e84d19/crates/blockifier/src/execution/native/syscall_handler.rs#L444 + fn call_contract( + &mut self, + address: Felt, + entry_point_selector: Felt, + calldata: &[Felt], + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.pre_execute_syscall( + remaining_gas, + self.native_syscall_handler + .gas_costs() + .syscalls + .call_contract + .base_syscall_cost(), + SyscallSelector::CallContract, + )?; + + let contract_address = ContractAddress::try_from(address) + .map_err(|error| self.handle_error(remaining_gas, error.into()))?; + + let class_hash = self + .native_syscall_handler + .base + .state + .get_class_hash_at(contract_address) + .map_err(|e| self.handle_error(remaining_gas, e.into()))?; + if self.native_syscall_handler.base.context.execution_mode == ExecutionMode::Validate + && self.native_syscall_handler.base.call.storage_address != contract_address + { + let err = SyscallExecutorBaseError::InvalidSyscallInExecutionMode { + syscall_name: "call_contract".to_string(), + execution_mode: self.native_syscall_handler.base.context.execution_mode, + }; + return Err(self.handle_error(remaining_gas, err.into())); + } + let selector = EntryPointSelector(entry_point_selector); + // TODO(#3790) restore blocking + // self + // .native_syscall_handler + // .base + // .maybe_block_direct_execute_call(selector) + // .map_err(|e| self.handle_error(remaining_gas, e))?; + + let wrapper_calldata = Calldata(Arc::new(calldata.to_vec())); + + let mut entry_point = CallEntryPoint { + class_hash: None, + code_address: Some(contract_address), + entry_point_type: EntryPointType::External, + entry_point_selector: selector, + calldata: wrapper_calldata, + storage_address: contract_address, + caller_address: self.native_syscall_handler.base.call.storage_address, + call_type: CallType::Call, + initial_gas: *remaining_gas, + }; + + let error_wrapper_function = + |e: SyscallExecutionError, + class_hash: ClassHash, + storage_address: ContractAddress, + selector: EntryPointSelector| { + e.as_call_contract_execution_error(class_hash, storage_address, selector) + }; + + Ok(self + .execute_inner_call( + &mut entry_point, + remaining_gas, + class_hash, + error_wrapper_function, + )? + .0) + } + + fn storage_read( + &mut self, + address_domain: u32, + address: Felt, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.native_syscall_handler + .storage_read(address_domain, address, remaining_gas) + } + + fn storage_write( + &mut self, + address_domain: u32, + address: Felt, + value: Felt, + remaining_gas: &mut u64, + ) -> SyscallResult<()> { + self.native_syscall_handler + .storage_write(address_domain, address, value, remaining_gas) + } + + fn emit_event( + &mut self, + keys: &[Felt], + data: &[Felt], + remaining_gas: &mut u64, + ) -> SyscallResult<()> { + let syscall_result = self + .native_syscall_handler + .emit_event(keys, data, remaining_gas); + + if syscall_result.is_ok() { + let contract_address = self + .native_syscall_handler + .base + .call + // TODO(#3790) why we default to code_address?? + .code_address + .unwrap_or(self.native_syscall_handler.base.call.storage_address); + let event = self + .native_syscall_handler + .base + .events + .last() + .expect("Event must have been emitted"); + self.cheatnet_state + .detected_events + .push(Event::from_ordered_event(event, contract_address)); + } + + syscall_result + } + + fn send_message_to_l1( + &mut self, + to_address: Felt, + payload: &[Felt], + remaining_gas: &mut u64, + ) -> SyscallResult<()> { + let syscall_result = + self.native_syscall_handler + .send_message_to_l1(to_address, payload, remaining_gas); + + if syscall_result.is_ok() { + let contract_address = self + .native_syscall_handler + .base + .call + // TODO(#3790) why we default to code_address?? + .code_address + .unwrap_or(self.native_syscall_handler.base.call.storage_address); + let message = self + .native_syscall_handler + .base + .l2_to_l1_messages + .last() + .expect("Message must have been sent"); + self.cheatnet_state + .detected_messages_to_l1 + .push(MessageToL1::from_ordered_message(message, contract_address)); + } + + syscall_result + } + + fn keccak(&mut self, input: &[u64], remaining_gas: &mut u64) -> SyscallResult { + self.native_syscall_handler.keccak(input, remaining_gas) + } + + fn secp256k1_new( + &mut self, + x: U256, + y: U256, + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.native_syscall_handler + .secp256k1_new(x, y, remaining_gas) + } + + fn secp256k1_add( + &mut self, + p0: Secp256k1Point, + p1: Secp256k1Point, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.native_syscall_handler + .secp256k1_add(p0, p1, remaining_gas) + } + + fn secp256k1_mul( + &mut self, + p: Secp256k1Point, + m: U256, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.native_syscall_handler + .secp256k1_mul(p, m, remaining_gas) + } + + fn secp256k1_get_point_from_x( + &mut self, + x: U256, + y_parity: bool, + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.native_syscall_handler + .secp256k1_get_point_from_x(x, y_parity, remaining_gas) + } + + fn secp256k1_get_xy( + &mut self, + p: Secp256k1Point, + remaining_gas: &mut u64, + ) -> SyscallResult<(U256, U256)> { + self.native_syscall_handler + .secp256k1_get_xy(p, remaining_gas) + } + + fn secp256r1_new( + &mut self, + x: U256, + y: U256, + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.native_syscall_handler + .secp256r1_new(x, y, remaining_gas) + } + + fn secp256r1_add( + &mut self, + p0: Secp256r1Point, + p1: Secp256r1Point, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.native_syscall_handler + .secp256r1_add(p0, p1, remaining_gas) + } + + fn secp256r1_mul( + &mut self, + p: Secp256r1Point, + m: U256, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.native_syscall_handler + .secp256r1_mul(p, m, remaining_gas) + } + + fn secp256r1_get_point_from_x( + &mut self, + x: U256, + y_parity: bool, + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.native_syscall_handler + .secp256r1_get_point_from_x(x, y_parity, remaining_gas) + } + + fn secp256r1_get_xy( + &mut self, + p: Secp256r1Point, + remaining_gas: &mut u64, + ) -> SyscallResult<(U256, U256)> { + self.native_syscall_handler + .secp256r1_get_xy(p, remaining_gas) + } + + fn sha256_process_block( + &mut self, + state: &mut [u32; 8], + block: &[u32; 16], + remaining_gas: &mut u64, + ) -> SyscallResult<()> { + self.native_syscall_handler + .sha256_process_block(state, block, remaining_gas) + } + + fn get_class_hash_at( + &mut self, + contract_address: Felt, + remaining_gas: &mut u64, + ) -> SyscallResult { + self.native_syscall_handler + .get_class_hash_at(contract_address, remaining_gas) + } + + // TODO(#3790) Support cheating meta_tx_v0 + fn meta_tx_v0( + &mut self, + address: Felt, + entry_point_selector: Felt, + calldata: &[Felt], + signature: &[Felt], + remaining_gas: &mut u64, + ) -> SyscallResult> { + self.native_syscall_handler.meta_tx_v0( + address, + entry_point_selector, + calldata, + signature, + remaining_gas, + ) + } +} diff --git a/crates/cheatnet/tests/common/mod.rs b/crates/cheatnet/tests/common/mod.rs index fc1d434375..5ddb9638f2 100644 --- a/crates/cheatnet/tests/common/mod.rs +++ b/crates/cheatnet/tests/common/mod.rs @@ -27,9 +27,9 @@ use conversions::string::TryFromHexStr; use foundry_ui::UI; use runtime::starknet::constants::TEST_ADDRESS; use runtime::starknet::context::build_context; -use scarb_api::metadata::MetadataCommandExt; +use scarb_api::metadata::metadata_for_dir; use scarb_api::{ - ScarbCommand, get_contracts_artifacts_and_source_sierra_paths, target_dir_for_workspace, + CompilationOpts, get_contracts_artifacts_and_source_sierra_paths, target_dir_for_workspace, }; use starknet::core::utils::get_selector_from_name; use starknet_api::contract_class::EntryPointType; @@ -75,18 +75,23 @@ pub fn recover_data(output: CallResult) -> Vec { } pub fn get_contracts() -> ContractsData { - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .manifest_path("tests/contracts/Scarb.toml") - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir("tests/contracts").unwrap(); let target_dir = target_dir_for_workspace(&scarb_metadata).join("dev"); let package = scarb_metadata.packages.first().unwrap(); let ui = UI::default(); - let contracts = - get_contracts_artifacts_and_source_sierra_paths(&target_dir, package, false, &ui).unwrap(); + let contracts = get_contracts_artifacts_and_source_sierra_paths( + &target_dir, + package, + &ui, + CompilationOpts { + use_test_target_contracts: false, + #[cfg(feature = "cairo-native")] + run_native: true, + }, + ) + .unwrap(); ContractsData::try_from(contracts).unwrap() } diff --git a/crates/docs/src/snippet.rs b/crates/docs/src/snippet.rs index 9cd1de9df1..96bd1a54ba 100644 --- a/crates/docs/src/snippet.rs +++ b/crates/docs/src/snippet.rs @@ -1,7 +1,7 @@ use std::sync::LazyLock; use regex::Regex; -use scarb_api::ScarbCommand; +use scarb_api::version::scarb_version; use semver::VersionReq; use serde::{Deserialize, Serialize}; @@ -101,10 +101,7 @@ impl Default for SnippetConfig { impl SnippetConfig { fn check_scarb_compatibility(&mut self) { if let Some(ref scarb_version_req) = self.scarb_version { - let current_scarb_version = ScarbCommand::version() - .run() - .expect("Failed to get scarb version") - .scarb; + let current_scarb_version = scarb_version().expect("Failed to get scarb version").scarb; if !scarb_version_req.matches(¤t_scarb_version) { self.ignored = true; diff --git a/crates/forge-runner/src/lib.rs b/crates/forge-runner/src/lib.rs index 55a6d83af8..57a0ef6e84 100644 --- a/crates/forge-runner/src/lib.rs +++ b/crates/forge-runner/src/lib.rs @@ -23,7 +23,7 @@ use std::sync::{Arc, Mutex}; use test_case_summary::{AnyTestCaseSummary, Fuzzing}; use tokio::sync::mpsc::{Sender, channel}; use tokio::task::JoinHandle; -use universal_sierra_compiler_api::AssembledProgramWithDebugInfo; +use universal_sierra_compiler_api::representation::RawCasmProgram; pub mod build_trace_data; pub mod coverage_api; @@ -114,7 +114,7 @@ pub fn maybe_generate_coverage( #[tracing::instrument(skip_all, level = "debug")] pub fn run_for_test_case( case: Arc, - casm_program: Arc, + casm_program: Arc, forge_config: Arc, versioned_program_path: Arc, send: Sender<()>, @@ -149,7 +149,7 @@ pub fn run_for_test_case( #[tracing::instrument(skip_all, level = "debug")] fn run_with_fuzzing( case: Arc, - casm_program: Arc, + casm_program: Arc, forge_config: Arc, versioned_program_path: Arc, send: Sender<()>, diff --git a/crates/forge-runner/src/package_tests.rs b/crates/forge-runner/src/package_tests.rs index 32a981f7e8..42fea5110d 100644 --- a/crates/forge-runner/src/package_tests.rs +++ b/crates/forge-runner/src/package_tests.rs @@ -19,7 +19,7 @@ use serde::Serialize; use starknet_types_core::felt::Felt; use std::collections::HashMap; use std::sync::Arc; -use universal_sierra_compiler_api::AssembledProgramWithDebugInfo; +use universal_sierra_compiler_api::representation::RawCasmProgram; pub mod raw; pub mod with_config; @@ -66,10 +66,7 @@ impl TestDetails { builtins } - pub fn try_into_program( - &self, - casm_program: &AssembledProgramWithDebugInfo, - ) -> Result { + pub fn try_into_program(&self, casm_program: &RawCasmProgram) -> Result { let builtins = self.builtins(); let assembled_program = &casm_program.assembled_cairo_program; @@ -102,7 +99,7 @@ pub struct TestTarget { pub tests_location: TestTargetLocation, pub sierra_program: ProgramArtifact, pub sierra_program_path: Arc, - pub casm_program: Arc, + pub casm_program: Arc, pub test_cases: Vec>, } diff --git a/crates/forge-runner/src/package_tests/with_config_resolved.rs b/crates/forge-runner/src/package_tests/with_config_resolved.rs index 11597441ff..0423a75559 100644 --- a/crates/forge-runner/src/package_tests/with_config_resolved.rs +++ b/crates/forge-runner/src/package_tests/with_config_resolved.rs @@ -6,7 +6,7 @@ use cheatnet::runtime_extensions::forge_config_extension::config::{ RawAvailableResourceBoundsConfig, RawFuzzerConfig, }; use starknet_api::block::BlockNumber; -use universal_sierra_compiler_api::AssembledProgramWithDebugInfo; +use universal_sierra_compiler_api::representation::RawCasmProgram; use url::Url; pub type TestTargetWithResolvedConfig = TestTarget; @@ -29,10 +29,7 @@ impl TestCaseWithResolvedConfig { } } - pub fn try_into_program( - &self, - casm_program: &AssembledProgramWithDebugInfo, - ) -> Result { + pub fn try_into_program(&self, casm_program: &RawCasmProgram) -> Result { self.test_details.try_into_program(casm_program) } } diff --git a/crates/forge-runner/src/running.rs b/crates/forge-runner/src/running.rs index aba45beea2..4ac5189a9d 100644 --- a/crates/forge-runner/src/running.rs +++ b/crates/forge-runner/src/running.rs @@ -44,7 +44,7 @@ use std::rc::Rc; use std::sync::{Arc, Mutex}; use tokio::sync::mpsc::Sender; use tokio::task::JoinHandle; -use universal_sierra_compiler_api::AssembledProgramWithDebugInfo; +use universal_sierra_compiler_api::representation::RawCasmProgram; pub mod config_run; mod execution; @@ -63,7 +63,7 @@ pub use syscall_handler::syscall_handler_offset; #[tracing::instrument(skip_all, level = "debug")] pub fn run_test( case: Arc, - casm_program: Arc, + casm_program: Arc, forge_config: Arc, versioned_program_path: Arc, send: Sender<()>, @@ -100,7 +100,7 @@ pub fn run_test( pub(crate) fn run_fuzz_test( case: Arc, program: Program, - casm_program: Arc, + casm_program: Arc, forge_config: Arc, versioned_program_path: Arc, send: Sender<()>, @@ -167,7 +167,7 @@ pub enum RunResult { pub fn run_test_case( case: &TestCaseWithResolvedConfig, program: &Program, - casm_program: &AssembledProgramWithDebugInfo, + casm_program: &RawCasmProgram, runtime_config: &RuntimeConfig, fuzzer_rng: Option>>, versioned_program_path: &Utf8Path, diff --git a/crates/forge-runner/src/running/config_run.rs b/crates/forge-runner/src/running/config_run.rs index e9ee61cd1f..b0d78e7183 100644 --- a/crates/forge-runner/src/running/config_run.rs +++ b/crates/forge-runner/src/running/config_run.rs @@ -18,7 +18,7 @@ use starknet_api::block::{ }; use starknet_types_core::felt::Felt; use std::default::Default; -use universal_sierra_compiler_api::AssembledProgramWithDebugInfo; +use universal_sierra_compiler_api::representation::RawCasmProgram; struct PhantomStateReader; @@ -58,9 +58,10 @@ impl StateReader for PhantomStateReader { } } +#[tracing::instrument(skip_all, level = "debug")] pub fn run_config_pass( test_details: &TestDetails, - casm_program: &AssembledProgramWithDebugInfo, + casm_program: &RawCasmProgram, tracked_resource: &ForgeTrackedResource, ) -> Result { let program = test_details.try_into_program(casm_program)?; diff --git a/crates/forge-runner/src/running/hints.rs b/crates/forge-runner/src/running/hints.rs index 236f00b660..c78ccbee81 100644 --- a/crates/forge-runner/src/running/hints.rs +++ b/crates/forge-runner/src/running/hints.rs @@ -1,7 +1,7 @@ use cairo_lang_casm::hints::Hint; use cairo_vm::serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams}; use std::collections::HashMap; -use universal_sierra_compiler_api::AssembledCairoProgramWithSerde; +use universal_sierra_compiler_api::representation::AssembledCairoProgramWithSerde; pub fn hints_by_representation( assembled_program: &AssembledCairoProgramWithSerde, diff --git a/crates/forge-runner/src/running/setup.rs b/crates/forge-runner/src/running/setup.rs index 159f478171..0a421d4c9d 100644 --- a/crates/forge-runner/src/running/setup.rs +++ b/crates/forge-runner/src/running/setup.rs @@ -15,7 +15,7 @@ use cairo_vm::vm::runners::cairo_runner::CairoRunner; use cheatnet::constants::build_test_entry_point; use starknet_api::deprecated_contract_class::EntryPointOffset; use std::collections::HashMap; -use universal_sierra_compiler_api::AssembledProgramWithDebugInfo; +use universal_sierra_compiler_api::representation::RawCasmProgram; // Based on structure from https://github.com/starkware-libs/sequencer/blob/e417a9e7d50cbd78065d357763df2fbc2ad41f7c/crates/blockifier/src/execution/entry_point_execution.rs#L39 // Logic of `initialize_execution_context` had to be modified so this struct ended up modified as well. @@ -96,7 +96,7 @@ pub fn entry_point_initial_budget(syscall_hint_processor: &SyscallHintProcessor) pub fn build_test_call_and_entry_point( test_details: &TestDetails, - casm_program: &AssembledProgramWithDebugInfo, + casm_program: &RawCasmProgram, program: &Program, ) -> (ExecutableCallEntryPoint, EntryPointV1) { let sierra_instruction_idx = test_details.sierra_entry_point_statement_idx; diff --git a/crates/forge-runner/src/running/with_config.rs b/crates/forge-runner/src/running/with_config.rs index 2789fd8588..88df31ec6a 100644 --- a/crates/forge-runner/src/running/with_config.rs +++ b/crates/forge-runner/src/running/with_config.rs @@ -19,7 +19,7 @@ use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; use std::{collections::HashMap, sync::Arc}; -use universal_sierra_compiler_api::{SierraType, compile_sierra_at_path}; +use universal_sierra_compiler_api::compile_raw_sierra_at_path; #[tracing::instrument(skip_all, level = "debug")] pub fn test_target_with_config( @@ -42,9 +42,8 @@ pub fn test_target_with_config( let funcs = by_id!(funcs); let type_declarations = by_id!(type_declarations); - let casm_program = Arc::new(compile_sierra_at_path( + let casm_program = Arc::new(compile_raw_sierra_at_path( &test_target_raw.sierra_program_path, - &SierraType::Raw, )?); let sierra_program_registry = @@ -88,6 +87,7 @@ pub fn test_target_with_config( }) } +#[tracing::instrument(skip_all, level = "debug")] fn build_test_details( func: &GenFunction, type_declarations: &HashMap, diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index e193c80734..80c38f80ad 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -12,6 +12,7 @@ non_exact_gas_assertions = ["test_utils/non_exact_gas_assertions"] skip_test_for_only_latest_scarb = [] skip_test_for_scarb_since_2_11 = [] test_for_multiple_scarb_versions = [] +cairo-native = ["test_utils/cairo-native", "cheatnet/cairo-native", "scarb-api/cairo-native"] [dependencies] anyhow.workspace = true @@ -71,4 +72,4 @@ walkdir.workspace = true test-case.workspace = true itertools.workspace = true docs = { workspace = true, features = ["testing"] } -packages_validation = { path = "../testing/packages_validation"} +packages_validation = { path = "../testing/packages_validation" } diff --git a/crates/forge/src/clean.rs b/crates/forge/src/clean.rs index cd06324bb2..fe11347a4f 100644 --- a/crates/forge/src/clean.rs +++ b/crates/forge/src/clean.rs @@ -2,7 +2,7 @@ use crate::{CleanArgs, CleanComponent}; use anyhow::{Context, Result, ensure}; use camino::Utf8PathBuf; use foundry_ui::UI; -use scarb_api::{ScarbCommand, metadata::MetadataCommandExt}; +use scarb_api::metadata::{MetadataOpts, metadata_with_opts}; use std::fs; const COVERAGE_DIR: &str = "coverage"; @@ -26,7 +26,10 @@ pub fn clean(args: CleanArgs, ui: &UI) -> Result<()> { args.clean_components }; - let scarb_metadata = ScarbCommand::metadata().inherit_stderr().no_deps().run()?; + let scarb_metadata = metadata_with_opts(MetadataOpts { + no_deps: true, + ..MetadataOpts::default() + })?; let workspace_root = scarb_metadata.workspace.root; let packages_root: Vec = scarb_metadata diff --git a/crates/forge/src/compatibility_check.rs b/crates/forge/src/compatibility_check.rs index f1f020f236..c509e37cae 100644 --- a/crates/forge/src/compatibility_check.rs +++ b/crates/forge/src/compatibility_check.rs @@ -143,7 +143,6 @@ mod tests { fixture::{FileWriteStr, PathChild}, }; use scarb_api::ScarbCommand; - use universal_sierra_compiler_api::UniversalSierraCompilerCommand; #[test] fn happy_case() { @@ -178,7 +177,7 @@ mod tests { }); requirements_checker.add_requirement(Requirement { name: "Universal Sierra Compiler".to_string(), - command: RefCell::new(UniversalSierraCompilerCommand::new().arg("--version").command()), + command: RefCell::new(universal_sierra_compiler_api::version_command().unwrap()), minimal_version: Version::new(2, 0, 0), minimal_recommended_version: None, helper_text: "Reinstall `snforge` using the same installation method or follow instructions from https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html#universal-sierra-compiler-update".to_string(), @@ -300,7 +299,7 @@ mod tests { }); requirements_checker.add_requirement(Requirement { name: "Universal Sierra Compiler".to_string(), - command: RefCell::new(UniversalSierraCompilerCommand::new().arg("--version").command()), + command: RefCell::new(universal_sierra_compiler_api::version_command().unwrap()), minimal_version: Version::new(2, 4, 0), minimal_recommended_version: None, helper_text: "Reinstall `snforge` using the same installation method or follow instructions from https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html#universal-sierra-compiler-update".to_string(), diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 1a8ffffaca..a856c1ee8d 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -9,7 +9,8 @@ use forge_runner::forge_config::ForgeTrackedResource; use foundry_ui::components::warning::WarningMessage; use foundry_ui::{Message, UI}; use run_tests::workspace::run_for_workspace; -use scarb_api::{ScarbCommand, metadata::MetadataCommandExt}; +use scarb_api::ScarbCommand; +use scarb_api::metadata::metadata; use scarb_ui::args::{FeaturesSpec, PackagesFilter, ProfileSpec}; use semver::Version; use shared::auto_completions::{Completions, generate_completions}; @@ -18,7 +19,6 @@ use std::ffi::OsString; use std::sync::Arc; use std::{fs, num::NonZeroU32, thread::available_parallelism}; use tokio::runtime::Builder; -use universal_sierra_compiler_api::UniversalSierraCompilerCommand; pub mod block_number_map; mod clean; @@ -141,6 +141,13 @@ pub struct TestArgs { #[command(flatten)] trace_args: TraceArgs, + /// Run contracts on `cairo-native` instead of the default `cairo-vm`. This will set `tracked-resource` to `sierra-gas`. + /// + /// Note: Only contracts execution through native is supported, test code itself will still run on `cairo-vm`. + #[arg(long)] + #[cfg(feature = "cairo-native")] + run_native: bool, + /// Use exact matches for `test_filter` #[arg(short, long)] exact: bool, @@ -181,14 +188,17 @@ pub struct TestArgs { /// Save execution traces of all test which have passed and are not fuzz tests #[arg(long)] + #[cfg_attr(feature = "cairo-native", arg(conflicts_with = "run_native"))] save_trace_data: bool, /// Build profiles of all tests which have passed and are not fuzz tests using the cairo-profiler - #[arg(long, conflicts_with = "coverage")] + #[arg(long, conflicts_with_all = ["coverage"])] + #[cfg_attr(feature = "cairo-native", arg(conflicts_with_all = ["run_native", "coverage"]))] build_profile: bool, /// Generate a coverage report for the executed tests which have passed and are not fuzz tests using the cairo-coverage - #[arg(long, conflicts_with = "build_profile")] + #[arg(long, conflicts_with_all = ["build_profile"])] + #[cfg_attr(feature = "cairo-native", arg(conflicts_with_all = ["run_native", "build_profile"]))] coverage: bool, /// Number of maximum steps during a single test. For fuzz tests this value is applied to each subtest separately. @@ -211,6 +221,21 @@ pub struct TestArgs { scarb_args: ScarbArgs, } +impl TestArgs { + /// Adjust dependent arguments based on related flags. + /// + /// This function mutates the `TestArgs` instance to enforce logical coherence + /// between fields. + pub fn normalize(&mut self) { + // Force using `SierraGas` as tracked resource when running with `cairo-native`, + // as otherwise it would run on vm. + #[cfg(feature = "cairo-native")] + if self.run_native { + self.tracked_resource = ForgeTrackedResource::SierraGas; + } + } +} + #[derive(Parser, Debug)] pub struct ScarbArgs { #[command(flatten)] @@ -274,7 +299,7 @@ pub fn main_execution(ui: Arc) -> Result { } ForgeSubcommand::CleanCache {} => { ui.println(&WarningMessage::new("`snforge clean-cache` is deprecated and will be removed in the future. Use `snforge clean cache` instead")); - let scarb_metadata = ScarbCommand::metadata().inherit_stderr().run()?; + let scarb_metadata = metadata()?; let cache_dir = scarb_metadata.workspace.root.join(CACHE_DIR); if cache_dir.exists() { @@ -283,7 +308,8 @@ pub fn main_execution(ui: Arc) -> Result { Ok(ExitStatus::Success) } - ForgeSubcommand::Test { args } => { + ForgeSubcommand::Test { mut args } => { + args.normalize(); check_requirements(false, &ui)?; let cores = if let Ok(available_cores) = available_parallelism() { available_cores.get() @@ -336,7 +362,7 @@ fn check_requirements(output_on_success: bool, ui: &UI) -> Result<()> { requirements_checker.add_requirement(Requirement { name: "Universal Sierra Compiler".to_string(), - command: RefCell::new(UniversalSierraCompilerCommand::new().arg("--version").command()), + command: RefCell::new(universal_sierra_compiler_api::version_command()?), minimal_version: MINIMAL_USC_VERSION, minimal_recommended_version: None, helper_text: "Reinstall `snforge` using the same installation method or follow instructions from https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html#universal-sierra-compiler-update".to_string(), diff --git a/crates/forge/src/new.rs b/crates/forge/src/new.rs index b42997ea85..d261452441 100644 --- a/crates/forge/src/new.rs +++ b/crates/forge/src/new.rs @@ -4,7 +4,8 @@ use anyhow::{Context, Ok, Result, anyhow, bail, ensure}; use camino::Utf8PathBuf; use include_dir::{Dir, DirEntry, include_dir}; use indoc::formatdoc; -use scarb_api::ScarbCommand; +use scarb_api::version::scarb_version; +use scarb_api::{ScarbCommand, ensure_scarb_available}; use semver::Version; use shared::consts::FREE_RPC_PROVIDER_URL; use std::env; @@ -108,7 +109,7 @@ impl TryFrom<&Template> for TemplateManifestConfig { type Error = anyhow::Error; fn try_from(template: &Template) -> Result { - let cairo_version = ScarbCommand::version().run()?.cairo; + let cairo_version = scarb_version()?.cairo; match template { Template::CairoProgram => Ok(TemplateManifestConfig { dependencies: vec![], @@ -256,7 +257,7 @@ fn set_cairo_edition(document: &mut DocumentMut, cairo_edition: &str) { } fn add_assert_macros(document: &mut DocumentMut) -> Result<()> { - let versions = ScarbCommand::version().run()?; + let versions = scarb_version()?; let version = if versions.scarb < MINIMAL_SCARB_FOR_CORRESPONDING_ASSERT_MACROS { DEFAULT_ASSERT_MACROS } else { @@ -351,7 +352,7 @@ pub fn new( template, }: NewArgs, ) -> Result<()> { - ScarbCommand::new().ensure_available()?; + ensure_scarb_available()?; if !overwrite { ensure!( !path.exists() || path.read_dir().is_ok_and(|mut i| i.next().is_none()), @@ -361,7 +362,7 @@ pub fn new( ); } let name = infer_name(name, &path)?; - let scarb_version = ScarbCommand::version().run()?.scarb; + let scarb_version = scarb_version()?.scarb; fs::create_dir_all(&path)?; let project_path = path.canonicalize()?; @@ -455,7 +456,7 @@ fn get_template_dir(template: &Template) -> Result> { } fn get_oz_version() -> Result { - let scarb_version = ScarbCommand::version().run()?.scarb; + let scarb_version = scarb_version()?.scarb; let oz_version = match scarb_version { ver if ver >= Version::new(2, 9, 4) => Version::new(1, 0, 0), diff --git a/crates/forge/src/profile_validation/backtrace.rs b/crates/forge/src/profile_validation/backtrace.rs index 35caccd6ec..80c37e3c37 100644 --- a/crates/forge/src/profile_validation/backtrace.rs +++ b/crates/forge/src/profile_validation/backtrace.rs @@ -1,16 +1,33 @@ +use crate::TestArgs; use crate::profile_validation::{check_cairo_profile_entries, get_manifest}; use anyhow::ensure; use indoc::formatdoc; use scarb_metadata::Metadata; use semver::Version; -/// Checks if backtrace can be generated based on scarb version and profile settings extracted from the provided [`Metadata`]. -pub fn check_backtrace_compatibility(scarb_metadata: &Metadata) -> anyhow::Result<()> { +/// Checks if backtrace can be generated based on scarb version, profile settings extracted from +/// the provided [`Metadata`] and if native execution is disabled in the provided [`TestArgs`]. +#[allow(unused_variables)] +pub fn check_backtrace_compatibility( + test_args: &TestArgs, + scarb_metadata: &Metadata, +) -> anyhow::Result<()> { + #[cfg(feature = "cairo-native")] + check_if_native_disabled(test_args)?; check_scarb_version(scarb_metadata)?; check_profile(scarb_metadata)?; Ok(()) } +/// Checks if native execution is disabled in the provided [`TestArgs`]. +#[cfg(feature = "cairo-native")] +fn check_if_native_disabled(test_args: &TestArgs) -> anyhow::Result<()> { + ensure!( + !test_args.run_native, + "Backtrace generation is not supported with `cairo-native` execution", + ); + Ok(()) +} /// Checks if the scarb version from the provided [`Metadata`] is greater than or equal to the minimal required version. fn check_scarb_version(scarb_metadata: &Metadata) -> anyhow::Result<()> { const MINIMAL_SCARB_VERSION: Version = Version::new(2, 8, 0); diff --git a/crates/forge/src/profile_validation/mod.rs b/crates/forge/src/profile_validation/mod.rs index bd31218165..ce7780bcf5 100644 --- a/crates/forge/src/profile_validation/mod.rs +++ b/crates/forge/src/profile_validation/mod.rs @@ -18,7 +18,7 @@ pub fn check_profile_compatibility( check_coverage_compatibility(scarb_metadata)?; } if is_backtrace_enabled() { - check_backtrace_compatibility(scarb_metadata)?; + check_backtrace_compatibility(test_args, scarb_metadata)?; } Ok(()) } diff --git a/crates/forge/src/run_tests/package.rs b/crates/forge/src/run_tests/package.rs index 519d5290dd..3cac2a28b9 100644 --- a/crates/forge/src/run_tests/package.rs +++ b/crates/forge/src/run_tests/package.rs @@ -34,7 +34,7 @@ use forge_runner::{ test_target_summary::TestTargetSummary, }; use foundry_ui::{UI, components::labeled::LabeledMessage}; -use scarb_api::get_contracts_artifacts_and_source_sierra_paths; +use scarb_api::{CompilationOpts, get_contracts_artifacts_and_source_sierra_paths}; use scarb_metadata::{Metadata, PackageMetadata}; use std::sync::Arc; @@ -86,11 +86,15 @@ impl RunForPackageArgs { let contracts = get_contracts_artifacts_and_source_sierra_paths( artifacts_dir, &package, - !should_compile_starknet_contract_target( - &scarb_metadata.app_version_info.version, - args.no_optimization, - ), ui, + CompilationOpts { + use_test_target_contracts: !should_compile_starknet_contract_target( + &scarb_metadata.app_version_info.version, + args.no_optimization, + ), + #[cfg(feature = "cairo-native")] + run_native: args.run_native, + }, )?; let contracts_data = ContractsData::try_from(contracts)?; diff --git a/crates/forge/src/run_tests/resolve_config.rs b/crates/forge/src/run_tests/resolve_config.rs index 6e2ab09c2f..f011413419 100644 --- a/crates/forge/src/run_tests/resolve_config.rs +++ b/crates/forge/src/run_tests/resolve_config.rs @@ -140,7 +140,7 @@ mod tests { use forge_runner::package_tests::with_config::{TestCaseConfig, TestCaseWithConfig}; use forge_runner::{expected_result::ExpectedTestResult, package_tests::TestDetails}; use std::sync::Arc; - use universal_sierra_compiler_api::{SierraType, compile_sierra}; + use universal_sierra_compiler_api::compile_raw_sierra; use url::Url; fn program_for_testing() -> ProgramArtifact { @@ -190,11 +190,8 @@ mod tests { sierra_program: program_for_testing(), sierra_program_path: Arc::default(), casm_program: Arc::new( - compile_sierra( - &serde_json::to_value(&program_for_testing().program).unwrap(), - &SierraType::Raw, - ) - .unwrap(), + compile_raw_sierra(&serde_json::to_value(&program_for_testing().program).unwrap()) + .unwrap(), ), test_cases, tests_location: TestTargetLocation::Lib, diff --git a/crates/forge/src/run_tests/workspace.rs b/crates/forge/src/run_tests/workspace.rs index 2442deec0c..f0fd02bb2b 100644 --- a/crates/forge/src/run_tests/workspace.rs +++ b/crates/forge/src/run_tests/workspace.rs @@ -18,9 +18,10 @@ use anyhow::{Context, Result}; use forge_runner::test_case_summary::AnyTestCaseSummary; use forge_runner::{CACHE_DIR, test_target_summary::TestTargetSummary}; use foundry_ui::UI; +use scarb_api::metadata::{MetadataOpts, metadata_with_opts}; +use scarb_api::version::scarb_version; use scarb_api::{ - ScarbCommand, - metadata::{Metadata, MetadataCommandExt, PackageMetadata}, + metadata::{Metadata, PackageMetadata}, target_dir_for_workspace, }; use scarb_ui::args::PackagesFilter; @@ -38,15 +39,14 @@ pub async fn run_for_workspace(args: TestArgs, ui: Arc) -> Result (), } - let mut metadata_command = ScarbCommand::metadata(); - if let Some(profile) = &args.scarb_args.profile.specified() { - metadata_command.profile(profile.clone()); - } - let scarb_metadata = metadata_command.inherit_stderr().run()?; + let scarb_metadata = metadata_with_opts(MetadataOpts { + profile: args.scarb_args.profile.specified(), + ..MetadataOpts::default() + })?; check_profile_compatibility(&args, &scarb_metadata)?; - let scarb_version = ScarbCommand::version().run()?.scarb; + let scarb_version = scarb_version()?.scarb; if scarb_version >= MINIMAL_SCARB_VERSION_FOR_V2_MACROS_REQUIREMENT { error_if_snforge_std_not_compatible(&scarb_metadata)?; warn_if_snforge_std_does_not_match_package_version(&scarb_metadata, &ui)?; diff --git a/crates/forge/src/scarb.rs b/crates/forge/src/scarb.rs index 2723d3060d..255183f72d 100644 --- a/crates/forge/src/scarb.rs +++ b/crates/forge/src/scarb.rs @@ -137,7 +137,7 @@ mod tests { use configuration::load_package_config; use forge_runner::forge_config::ForgeTrackedResource; use indoc::{formatdoc, indoc}; - use scarb_api::metadata::MetadataCommandExt; + use scarb_api::metadata::metadata_for_dir; use scarb_metadata::PackageId; use std::env; use test_utils::{get_snforge_std_entry, tempdir_with_tool_versions}; @@ -195,11 +195,7 @@ mod tests { #[test] fn get_forge_config_for_package() { let temp = setup_package("simple_package"); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let config = load_package_config::( &scarb_metadata, @@ -248,11 +244,7 @@ mod tests { #[test] fn get_forge_config_for_package_err_on_invalid_package() { let temp = setup_package("simple_package"); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let result = load_package_config::( &scarb_metadata, @@ -278,11 +270,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let config = load_package_config::( &scarb_metadata, @@ -315,11 +303,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let err = load_package_config::( &scarb_metadata, &scarb_metadata.workspace.members[0], @@ -346,11 +330,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let err = load_package_config::( &scarb_metadata, &scarb_metadata.workspace.members[0], @@ -379,11 +359,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let err = load_package_config::( &scarb_metadata, @@ -413,11 +389,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let err = load_package_config::( &scarb_metadata, @@ -444,11 +416,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); let forge_config = load_package_config::( &scarb_metadata, @@ -475,11 +443,7 @@ mod tests { ); temp.child("Scarb.toml").write_str(content).unwrap(); - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(temp.path()) - .run() - .unwrap(); + let scarb_metadata = metadata_for_dir(temp.path()).unwrap(); // SAFETY: This value is only read here and is not modified by other tests. unsafe { diff --git a/crates/forge/src/test_filter.rs b/crates/forge/src/test_filter.rs index 06a3efdb29..f9ba77285d 100644 --- a/crates/forge/src/test_filter.rs +++ b/crates/forge/src/test_filter.rs @@ -145,7 +145,7 @@ mod tests { }; use forge_runner::package_tests::{TestDetails, TestTargetLocation}; use std::sync::Arc; - use universal_sierra_compiler_api::{SierraType, compile_sierra}; + use universal_sierra_compiler_api::compile_raw_sierra; fn program_for_testing() -> ProgramArtifact { ProgramArtifact { @@ -194,11 +194,8 @@ mod tests { sierra_program: program_for_testing(), sierra_program_path: Arc::default(), casm_program: Arc::new( - compile_sierra( - &serde_json::to_value(&program_for_testing().program).unwrap(), - &SierraType::Raw, - ) - .unwrap(), + compile_raw_sierra(&serde_json::to_value(&program_for_testing().program).unwrap()) + .unwrap(), ), test_cases: vec![ TestCaseWithResolvedConfig { @@ -482,11 +479,8 @@ mod tests { sierra_program: program_for_testing(), sierra_program_path: Arc::default(), casm_program: Arc::new( - compile_sierra( - &serde_json::to_value(&program_for_testing().program).unwrap(), - &SierraType::Raw, - ) - .unwrap(), + compile_raw_sierra(&serde_json::to_value(&program_for_testing().program).unwrap()) + .unwrap(), ), test_cases: vec![], tests_location: TestTargetLocation::Lib, @@ -530,11 +524,8 @@ mod tests { sierra_program: program_for_testing(), sierra_program_path: Arc::default(), casm_program: Arc::new( - compile_sierra( - &serde_json::to_value(&program_for_testing().program).unwrap(), - &SierraType::Raw, - ) - .unwrap(), + compile_raw_sierra(&serde_json::to_value(&program_for_testing().program).unwrap()) + .unwrap(), ), test_cases: vec![ TestCaseWithResolvedConfig { @@ -730,17 +721,13 @@ mod tests { } #[test] - #[expect(clippy::too_many_lines)] fn filtering_with_only_ignored() { let mocked_tests = TestTargetWithResolvedConfig { sierra_program: program_for_testing(), sierra_program_path: Arc::default(), casm_program: Arc::new( - compile_sierra( - &serde_json::to_value(&program_for_testing().program).unwrap(), - &SierraType::Raw, - ) - .unwrap(), + compile_raw_sierra(&serde_json::to_value(&program_for_testing().program).unwrap()) + .unwrap(), ), test_cases: vec![ TestCaseWithResolvedConfig { @@ -851,11 +838,8 @@ mod tests { sierra_program: program_for_testing(), sierra_program_path: Arc::default(), casm_program: Arc::new( - compile_sierra( - &serde_json::to_value(&program_for_testing().program).unwrap(), - &SierraType::Raw, - ) - .unwrap(), + compile_raw_sierra(&serde_json::to_value(&program_for_testing().program).unwrap()) + .unwrap(), ), test_cases: vec![ TestCaseWithResolvedConfig { diff --git a/crates/forge/src/warn.rs b/crates/forge/src/warn.rs index 2b6d7ba4ca..0d619611c4 100644 --- a/crates/forge/src/warn.rs +++ b/crates/forge/src/warn.rs @@ -5,7 +5,8 @@ use forge_runner::package_tests::with_config_resolved::TestTargetWithResolvedCon use foundry_ui::UI; use foundry_ui::components::warning::WarningMessage; use indoc::formatdoc; -use scarb_api::{ScarbCommand, package_matches_version_requirement}; +use scarb_api::package_matches_version_requirement; +use scarb_api::version::scarb_version; use scarb_metadata::Metadata; use semver::{Comparator, Op, Version, VersionReq}; use shared::rpc::create_rpc_client; @@ -25,7 +26,7 @@ pub(crate) fn warn_if_available_gas_used_with_incompatible_scarb_version( .config .available_gas .as_ref().is_some_and(cheatnet::runtime_extensions::forge_config_extension::config::RawAvailableResourceBoundsConfig::is_zero) - && ScarbCommand::version().run()?.scarb <= Version::new(2, 4, 3) + && scarb_version()?.scarb <= Version::new(2, 4, 3) { ui.println(&WarningMessage::new("`available_gas` attribute was probably specified when using Scarb ~2.4.3 \ Make sure to use Scarb >=2.4.4" diff --git a/crates/forge/test_utils/Cargo.toml b/crates/forge/test_utils/Cargo.toml index 9aecfff186..27987b39ed 100644 --- a/crates/forge/test_utils/Cargo.toml +++ b/crates/forge/test_utils/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true [features] non_exact_gas_assertions = [] +cairo-native = ["scarb-api/cairo-native", "cheatnet/cairo-native"] [dependencies] walkdir.workspace = true diff --git a/crates/forge/test_utils/src/lib.rs b/crates/forge/test_utils/src/lib.rs index 613f584870..f3847197fc 100644 --- a/crates/forge/test_utils/src/lib.rs +++ b/crates/forge/test_utils/src/lib.rs @@ -6,7 +6,7 @@ use assert_fs::fixture::PathCopy; use camino::Utf8PathBuf; use forge::MINIMAL_SCARB_VERSION_FOR_V2_MACROS_REQUIREMENT; use project_root::get_project_root; -use scarb_api::ScarbCommand; +use scarb_api::version::scarb_version; use semver::Version; use std::str::FromStr; @@ -21,7 +21,7 @@ pub fn tempdir_with_tool_versions() -> Result { } pub fn get_assert_macros_version() -> Result { - let scarb_version_output = ScarbCommand::version().run()?; + let scarb_version_output = scarb_version()?; let assert_macros_version = if scarb_version_output.scarb < MINIMAL_SCARB_FOR_CORRESPONDING_ASSERT_MACROS { DEFAULT_ASSERT_MACROS @@ -56,8 +56,6 @@ pub fn get_snforge_std_entry() -> Result { #[must_use] pub fn use_snforge_std_deprecated() -> bool { - let scarb_version_output = ScarbCommand::version() - .run() - .expect("Failed to get scarb version"); + let scarb_version_output = scarb_version().expect("Failed to get scarb version"); scarb_version_output.scarb < MINIMAL_SCARB_VERSION_FOR_V2_MACROS_REQUIREMENT } diff --git a/crates/forge/test_utils/src/runner.rs b/crates/forge/test_utils/src/runner.rs index afa323338f..6891831a4b 100644 --- a/crates/forge/test_utils/src/runner.rs +++ b/crates/forge/test_utils/src/runner.rs @@ -16,9 +16,10 @@ use forge_runner::{ }; use foundry_ui::UI; use indoc::formatdoc; +use scarb_api::metadata::metadata_for_dir; use scarb_api::{ - ScarbCommand, StarknetContractArtifacts, get_contracts_artifacts_and_source_sierra_paths, - metadata::MetadataCommandExt, target_dir_for_workspace, + CompilationOpts, StarknetContractArtifacts, get_contracts_artifacts_and_source_sierra_paths, + target_dir_for_workspace, }; use shared::command::CommandExt; use starknet_api::execution_resources::{GasAmount, GasVector}; @@ -61,7 +62,7 @@ impl Contract { }) } - fn generate_sierra_and_casm(self, ui: &UI) -> Result<(String, String)> { + fn generate_contract_artifacts(self, ui: &UI) -> Result { let dir = tempdir_with_tool_versions()?; let contract_path = dir.child("src/lib.cairo"); @@ -93,10 +94,7 @@ impl Contract { .output_checked() .context("Failed to build contracts with Scarb")?; - let scarb_metadata = ScarbCommand::metadata() - .current_dir(dir.path()) - .inherit_stderr() - .run()?; + let scarb_metadata = metadata_for_dir(dir.path())?; let package = scarb_metadata .packages .iter() @@ -104,14 +102,22 @@ impl Contract { .unwrap(); let artifacts_dir = target_dir_for_workspace(&scarb_metadata).join("dev"); - let contract = - get_contracts_artifacts_and_source_sierra_paths(&artifacts_dir, package, false, ui) - .unwrap() - .remove(&self.name) - .ok_or(anyhow!("there is no contract with name {}", self.name))? - .0; + let artifacts = get_contracts_artifacts_and_source_sierra_paths( + &artifacts_dir, + package, + ui, + CompilationOpts { + use_test_target_contracts: false, + #[cfg(feature = "cairo-native")] + run_native: true, + }, + ) + .unwrap() + .remove(&self.name) + .ok_or(anyhow!("there is no contract with name {}", self.name))? + .0; - Ok((contract.sierra, contract.casm)) + Ok(artifacts) } } @@ -216,15 +222,9 @@ impl<'a> TestCase { .into_iter() .map(|contract| { let name = contract.name.clone(); - let (sierra, casm) = contract.generate_sierra_and_casm(ui)?; - - Ok(( - name, - ( - StarknetContractArtifacts { sierra, casm }, - Utf8PathBuf::default(), - ), - )) + let artifacts = contract.generate_contract_artifacts(ui)?; + + Ok((name, (artifacts, Utf8PathBuf::default()))) }) .collect() } diff --git a/crates/forge/test_utils/src/running_tests.rs b/crates/forge/test_utils/src/running_tests.rs index 43ba00dfbb..839b367dfd 100644 --- a/crates/forge/test_utils/src/running_tests.rs +++ b/crates/forge/test_utils/src/running_tests.rs @@ -15,7 +15,8 @@ use forge_runner::forge_config::{ }; use forge_runner::test_target_summary::TestTargetSummary; use foundry_ui::UI; -use scarb_api::{ScarbCommand, metadata::MetadataCommandExt}; +use scarb_api::ScarbCommand; +use scarb_api::metadata::metadata_for_dir; use std::num::NonZeroU32; use std::sync::Arc; use tempfile::tempdir; @@ -33,10 +34,7 @@ pub fn run_test_case( .run() .unwrap(); - let metadata = ScarbCommand::metadata() - .current_dir(test.path().unwrap()) - .run() - .unwrap(); + let metadata = metadata_for_dir(test.path().unwrap()).unwrap(); let package = metadata .packages @@ -45,11 +43,8 @@ pub fn run_test_case( .unwrap(); let rt = Runtime::new().expect("Could not instantiate Runtime"); - let raw_test_targets = if false { - load_test_artifacts(&test.path().unwrap().join("target/release"), package).unwrap() - } else { - load_test_artifacts(&test.path().unwrap().join("target/dev"), package).unwrap() - }; + let raw_test_targets = + load_test_artifacts(&test.path().unwrap().join("target/dev"), package).unwrap(); let ui = Arc::new(UI::default()); rt.block_on(run_for_package( diff --git a/crates/forge/tests/data/simple_package_with_cheats/Scarb.toml b/crates/forge/tests/data/simple_package_with_cheats/Scarb.toml new file mode 100644 index 0000000000..2ab9f60c85 --- /dev/null +++ b/crates/forge/tests/data/simple_package_with_cheats/Scarb.toml @@ -0,0 +1,15 @@ +[package] +name = "simple_package_with_cheats" +version = "0.1.0" +edition = "2024_07" + +[dependencies] +starknet = "2.12.0" + +[dev-dependencies] +snforge_std = { path = "../../../../../snforge_std" } + +[[target.starknet-contract]] + +[tool.snforge] +exit_first = false diff --git a/crates/forge/tests/data/simple_package_with_cheats/src/lib.cairo b/crates/forge/tests/data/simple_package_with_cheats/src/lib.cairo new file mode 100644 index 0000000000..47f2454d28 --- /dev/null +++ b/crates/forge/tests/data/simple_package_with_cheats/src/lib.cairo @@ -0,0 +1,124 @@ +use starknet::{ClassHash, ContractAddress}; + +#[starknet::interface] +pub trait IHelloStarknet { + fn increase_balance(ref self: TContractState, amount: felt252); + fn get_balance(self: @TContractState) -> felt252; + fn get_block_number(self: @TContractState) -> u64; + fn get_block_hash(self: @TContractState) -> felt252; +} + +#[starknet::interface] +pub trait ICheatedConstructor { + fn get_stored_block_number(self: @TContractState) -> u64; +} + +#[starknet::contract] +pub mod CheatedConstructor { + use starknet::get_block_number; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + #[storage] + struct Storage { + block_number: u64, + } + + #[constructor] + fn constructor(ref self: ContractState) { + let block_number = get_block_number(); + self.block_number.write(block_number); + } + + #[abi(embed_v0)] + impl ICheatedConstructorImpl of super::ICheatedConstructor { + fn get_stored_block_number(self: @ContractState) -> u64 { + self.block_number.read() + } + } +} + +#[starknet::interface] +pub trait IHelloStarknetProxy { + fn get_block_number(self: @TContractState) -> u64; + fn get_block_number_library_call(self: @TContractState) -> u64; + fn deploy_cheated_constructor_contract( + ref self: TContractState, class_hash: ClassHash, salt: felt252, + ) -> ContractAddress; +} + +#[starknet::contract] +pub mod HelloStarknetProxy { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::syscalls::{deploy_syscall, get_class_hash_at_syscall}; + use starknet::{ClassHash, ContractAddress}; + use crate::{ + IHelloStarknetDispatcher, IHelloStarknetDispatcherTrait, IHelloStarknetLibraryDispatcher, + }; + + #[storage] + struct Storage { + address: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, address: ContractAddress) { + self.address.write(address); + } + + #[abi(embed_v0)] + impl IHelloStarknetProxyImpl of super::IHelloStarknetProxy { + fn get_block_number(self: @ContractState) -> u64 { + let address = self.address.read(); + let proxied = IHelloStarknetDispatcher { contract_address: address }; + proxied.get_block_number() + } + + fn get_block_number_library_call(self: @ContractState) -> u64 { + let address = self.address.read(); + let class_hash = get_class_hash_at_syscall(address).unwrap(); + let library_dispatcher = IHelloStarknetLibraryDispatcher { class_hash }; + library_dispatcher.get_block_number() + } + + fn deploy_cheated_constructor_contract( + ref self: ContractState, class_hash: ClassHash, salt: felt252, + ) -> ContractAddress { + let (address, _) = deploy_syscall(class_hash, salt, array![].span(), false).unwrap(); + address + } + } +} + +#[starknet::contract] +pub mod HelloStarknet { + use core::array::ArrayTrait; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::syscalls::get_block_hash_syscall; + use starknet::{SyscallResultTrait, get_block_number}; + + #[storage] + struct Storage { + balance: felt252, + } + + #[abi(embed_v0)] + impl IHelloStarknetImpl of super::IHelloStarknet { + // Increases the balance by the given amount + fn increase_balance(ref self: ContractState, amount: felt252) { + self.balance.write(self.balance.read() + amount); + } + + // Returns the current balance + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + fn get_block_number(self: @ContractState) -> u64 { + get_block_number() + } + + fn get_block_hash(self: @ContractState) -> felt252 { + get_block_hash_syscall(100).unwrap_syscall() + } + } +} diff --git a/crates/forge/tests/data/simple_package_with_cheats/tests/contract.cairo b/crates/forge/tests/data/simple_package_with_cheats/tests/contract.cairo new file mode 100644 index 0000000000..c06ac40a6c --- /dev/null +++ b/crates/forge/tests/data/simple_package_with_cheats/tests/contract.cairo @@ -0,0 +1,115 @@ +use core::array::ArrayTrait; +use core::result::ResultTrait; +use simple_package_with_cheats::{ + ICheatedConstructorDispatcher, ICheatedConstructorDispatcherTrait, IHelloStarknetDispatcher, + IHelloStarknetDispatcherTrait, IHelloStarknetProxyDispatcher, + IHelloStarknetProxyDispatcherTrait, +}; +use snforge_std::cheatcodes::contract_class::DeclareResultTrait; +use snforge_std::{ + ContractClassTrait, declare, start_cheat_block_hash_global, start_cheat_block_number_global, +}; +use starknet::contract_address; + +#[test] +fn call_and_invoke() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let block_number = dispatcher.get_block_number(); + println!("block number {}", block_number); + // TODO investigate why the default is 2000 + assert(block_number == 2000, 'block_info == 2000'); + + start_cheat_block_number_global(123); + + let block_number = dispatcher.get_block_number(); + assert(block_number == 123, 'block_info == 123'); +} + +#[test] +fn call_and_invoke_proxy() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + + let proxy_contract = declare("HelloStarknetProxy").unwrap().contract_class(); + let mut constructor_calldata = ArrayTrait::new(); + contract_address.serialize(ref constructor_calldata); + let (proxy_contract_address, _) = proxy_contract.deploy(@constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetProxyDispatcher { contract_address: proxy_contract_address }; + + let block_number = dispatcher.get_block_number(); + assert(block_number == 2000, 'block_number == 2000'); + + start_cheat_block_number_global(123); + + let block_number = dispatcher.get_block_number(); + assert(block_number == 123, 'block_number == 123'); +} + +#[test] +fn call_and_invoke_library_call() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + + let proxy_contract = declare("HelloStarknetProxy").unwrap().contract_class(); + let mut constructor_calldata = ArrayTrait::new(); + contract_address.serialize(ref constructor_calldata); + let (proxy_contract_address, _) = proxy_contract.deploy(@constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetProxyDispatcher { contract_address: proxy_contract_address }; + + let block_number = dispatcher.get_block_number_library_call(); + assert(block_number == 2000, 'block_number == 2000'); + + start_cheat_block_number_global(123); + + let block_number = dispatcher.get_block_number_library_call(); + assert(block_number == 123, 'block_number == 123'); +} + +#[test] +fn deploy_syscall() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + + let proxy_contract = declare("HelloStarknetProxy").unwrap().contract_class(); + let mut constructor_calldata = ArrayTrait::new(); + contract_address.serialize(ref constructor_calldata); + let (proxy_contract_address, _) = proxy_contract.deploy(@constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetProxyDispatcher { contract_address: proxy_contract_address }; + + let class_hash = declare("CheatedConstructor").unwrap().contract_class().class_hash; + + let contract_address = dispatcher.deploy_cheated_constructor_contract(*class_hash, 111); + let cheated_constructor_dispatcher = ICheatedConstructorDispatcher { contract_address }; + let block_number = cheated_constructor_dispatcher.get_stored_block_number(); + assert(block_number == 2000, 'block_number == 2000'); + + start_cheat_block_number_global(123); + + let contract_address = dispatcher.deploy_cheated_constructor_contract(*class_hash, 222); + let cheated_constructor_dispatcher = ICheatedConstructorDispatcher { contract_address }; + let block_number = cheated_constructor_dispatcher.get_stored_block_number(); + assert(block_number == 123, 'block_number == 123'); +} + +#[test] +fn block_hash() { + let contract = declare("HelloStarknet").unwrap().contract_class(); + let constructor_calldata = @ArrayTrait::new(); + let (contract_address, _) = contract.deploy(constructor_calldata).unwrap(); + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let block_hash = dispatcher.get_block_hash(); + assert(block_hash == 0, 'bloch_hash == 0'); + + start_cheat_block_hash_global(100, 111); + + let block_hash = dispatcher.get_block_hash(); + assert(block_hash == 111, 'bloch_hash == 111'); +} diff --git a/crates/forge/tests/e2e/backtrace.rs b/crates/forge/tests/e2e/backtrace.rs index 349d221aaa..9cf2797898 100644 --- a/crates/forge/tests/e2e/backtrace.rs +++ b/crates/forge/tests/e2e/backtrace.rs @@ -23,6 +23,23 @@ fn test_backtrace_missing_env() { ); } +#[cfg_attr(not(feature = "cairo-native"), ignore)] +#[test] +fn test_backtrace_native_execution() { + let temp = setup_package("backtrace_vm_error"); + + let output = test_runner(&temp) + .arg("--run-native") + .env("SNFORGE_BACKTRACE", "1") + .assert() + .code(2); + + assert_stdout_contains( + output, + "[ERROR] Backtrace generation is not supported with `cairo-native` execution\n", + ); +} + #[test] fn test_backtrace() { let temp = setup_package("backtrace_vm_error"); diff --git a/crates/forge/tests/e2e/clean.rs b/crates/forge/tests/e2e/clean.rs index d57ea1c062..3fae4f2dca 100644 --- a/crates/forge/tests/e2e/clean.rs +++ b/crates/forge/tests/e2e/clean.rs @@ -1,8 +1,7 @@ use super::common::runner::{runner, setup_package, test_runner}; use assert_fs::TempDir; use camino::Utf8PathBuf; -use scarb_api::ScarbCommand; -use scarb_api::metadata::MetadataCommandExt; +use scarb_api::metadata::{MetadataOpts, metadata_with_opts}; use shared::test_utils::output_assert::assert_stdout_contains; use std::path::Path; @@ -21,6 +20,10 @@ struct CleanComponentsState { } #[test] +#[cfg_attr( + feature = "cairo-native", + ignore = "Native doesn't support coverage yet" +)] fn test_clean_coverage() { let temp_dir = setup_package("coverage_project"); @@ -54,6 +57,10 @@ fn test_clean_coverage() { } #[test] +#[cfg_attr( + feature = "cairo-native", + ignore = "Native doesn't support profiler yet" +)] fn test_clean_profile() { let temp_dir = setup_package("coverage_project"); @@ -118,6 +125,10 @@ fn test_clean_cache() { } #[test] +#[cfg_attr( + feature = "cairo-native", + ignore = "Native doesn't support trace, coverage and profiler yet" +)] fn test_clean_all() { let temp_dir = setup_package("coverage_project"); @@ -152,7 +163,7 @@ fn test_clean_all_and_component() { let clean_components_state = CleanComponentsState { coverage: false, cache: true, - trace: true, + trace: false, profile: false, }; generate_clean_components(clean_components_state, &temp_dir); @@ -219,12 +230,12 @@ fn generate_clean_components(state: CleanComponentsState, temp_dir: &TempDir) { } fn check_clean_components_state(path: &Path) -> CleanComponentsState { - let scarb_metadata = ScarbCommand::metadata() - .inherit_stderr() - .current_dir(path) - .no_deps() - .run() - .unwrap(); + let scarb_metadata = metadata_with_opts(MetadataOpts { + no_deps: true, + current_dir: Some(path.into()), + ..MetadataOpts::default() + }) + .unwrap(); let workspace_root = scarb_metadata.workspace.root; diff --git a/crates/forge/tests/e2e/common/mod.rs b/crates/forge/tests/e2e/common/mod.rs index 0f6b4f3849..875c4bb11b 100644 --- a/crates/forge/tests/e2e/common/mod.rs +++ b/crates/forge/tests/e2e/common/mod.rs @@ -1,9 +1,11 @@ +#[cfg(not(feature = "cairo-native"))] use cairo_annotations::trace_data::{ CallTraceNode as ProfilerCallTraceNode, CallTraceV1 as ProfilerCallTrace, }; pub mod runner; +#[cfg(not(feature = "cairo-native"))] pub fn get_trace_from_trace_node(trace_node: &ProfilerCallTraceNode) -> &ProfilerCallTrace { if let ProfilerCallTraceNode::EntryPointCall(trace) = trace_node { trace diff --git a/crates/forge/tests/e2e/common/runner.rs b/crates/forge/tests/e2e/common/runner.rs index 6e086d0039..febf2ee51e 100644 --- a/crates/forge/tests/e2e/common/runner.rs +++ b/crates/forge/tests/e2e/common/runner.rs @@ -31,7 +31,29 @@ pub fn snforge_test_bin_path() -> PathBuf { cargo_bin!("snforge").to_path_buf() } +/// Returns a command that runs `snforge test` in the given temporary directory. +/// If the `cairo-native` feature is enabled, it adds the `--run-native` flag. pub(crate) fn test_runner>(temp_dir: T) -> SnapboxCommand { + if cfg!(feature = "cairo-native") { + test_runner_native(temp_dir) + } else { + test_runner_vm(temp_dir) + } +} + +/// Returns a command that runs `snforge test --run-native` in the given temporary directory. +/// +/// This is useful for testing behavior that occurs only when the `--run-native` flag is passed. +/// If the behavior is not specific to native execution, use `test_runner` instead. +pub(crate) fn test_runner_native>(temp_dir: T) -> SnapboxCommand { + runner(temp_dir).arg("test").arg("--run-native") +} + +/// Returns a command that runs `snforge test` in the given temporary directory. +/// +/// This is useful for testing behavior that occurs only in the VM execution. +/// If the behavior is not specific to VM execution, use `test_runner` instead. +pub(crate) fn test_runner_vm>(temp_dir: T) -> SnapboxCommand { runner(temp_dir).arg("test") } diff --git a/crates/forge/tests/e2e/docs_snippets_validation.rs b/crates/forge/tests/e2e/docs_snippets_validation.rs index ac16e555a1..1d5f19a3f2 100644 --- a/crates/forge/tests/e2e/docs_snippets_validation.rs +++ b/crates/forge/tests/e2e/docs_snippets_validation.rs @@ -11,6 +11,10 @@ use shared::test_utils::output_assert::assert_stdout_contains; use super::common::runner::{runner, setup_package}; #[test] +#[cfg_attr( + feature = "cairo-native", + ignore = "TODO(#3790): Many snippets show vm resources witch cairo native doesn't support" +)] fn test_docs_snippets() { let root_dir = get_nth_ancestor(2); let docs_dir = root_dir.join("docs/src"); diff --git a/crates/forge/tests/e2e/mod.rs b/crates/forge/tests/e2e/mod.rs index bc5f573b99..cfa90120fe 100644 --- a/crates/forge/tests/e2e/mod.rs +++ b/crates/forge/tests/e2e/mod.rs @@ -1,15 +1,19 @@ -pub(crate) mod common; - +#[cfg(not(feature = "cairo-native"))] mod backtrace; +#[cfg(not(feature = "cairo-native"))] mod build_profile; +#[cfg(not(feature = "cairo-native"))] mod build_trace_data; mod clean; mod collection; mod color; +pub(crate) mod common; mod completions; mod components; mod contract_artifacts; +#[cfg(not(feature = "cairo-native"))] mod coverage; +#[cfg(not(feature = "cairo-native"))] mod debugging; mod docs_snippets_validation; mod env; @@ -29,5 +33,6 @@ mod steps; mod templates; mod test_case; mod trace_print; +#[cfg(not(feature = "cairo-native"))] mod trace_resources; mod workspaces; diff --git a/crates/forge/tests/e2e/plugin_versions.rs b/crates/forge/tests/e2e/plugin_versions.rs index 9c59a95c7a..d3875eb345 100644 --- a/crates/forge/tests/e2e/plugin_versions.rs +++ b/crates/forge/tests/e2e/plugin_versions.rs @@ -2,6 +2,7 @@ use crate::e2e::common::runner::{runner, test_runner}; use camino::Utf8PathBuf; use indoc::{formatdoc, indoc}; use scarb_api::ScarbCommand; +use scarb_api::version::scarb_version_for_dir; use shared::test_utils::output_assert::assert_stdout_contains; use snapbox::cmd::Command; use test_utils::tempdir_with_tool_versions; @@ -146,9 +147,7 @@ fn new_scarb_deprecated_macros() { .command() .output() .unwrap(); - let scarb_version = ScarbCommand::version() - .current_dir(temp.path()) - .run() + let scarb_version = scarb_version_for_dir(temp.path()) .unwrap() .scarb .to_string(); diff --git a/crates/forge/tests/e2e/requirements.rs b/crates/forge/tests/e2e/requirements.rs index d1b3873ce3..791170263a 100644 --- a/crates/forge/tests/e2e/requirements.rs +++ b/crates/forge/tests/e2e/requirements.rs @@ -1,6 +1,6 @@ use crate::e2e::common::runner::{runner, setup_package}; use indoc::{formatdoc, indoc}; -use scarb_api::ScarbCommand; +use scarb_api::version::scarb_version; use semver::Version; use shared::test_utils::output_assert::assert_stdout_contains; @@ -9,7 +9,7 @@ fn happy_path() { let temp = setup_package("simple_package"); let output = runner(&temp).arg("check-requirements").assert(); - let scarb_version = ScarbCommand::version().run().unwrap().scarb; + let scarb_version = scarb_version().unwrap().scarb; let rust_check = if scarb_version < Version::new(2, 10, 0) { indoc! {" diff --git a/crates/forge/tests/e2e/running.rs b/crates/forge/tests/e2e/running.rs index 21e45c691d..3f9ba6b88b 100644 --- a/crates/forge/tests/e2e/running.rs +++ b/crates/forge/tests/e2e/running.rs @@ -1,4 +1,6 @@ -use super::common::runner::{get_current_branch, get_remote_url, setup_package, test_runner}; +use super::common::runner::{ + get_current_branch, get_remote_url, setup_package, test_runner, test_runner_native, +}; use assert_fs::fixture::{FileWriteStr, PathChild}; use indoc::{formatdoc, indoc}; use shared::test_utils::output_assert::{AsOutput, assert_stdout, assert_stdout_contains}; @@ -51,6 +53,80 @@ fn simple_package() { ); } +#[cfg_attr( + not(feature = "cairo-native"), + ignore = "Requires cairo-native feature" +)] +#[test] +fn simple_package_native() { + let temp = setup_package("simple_package"); + let output = test_runner_native(&temp).assert().code(1); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 13 test(s) from simple_package package + Running 2 test(s) from src/ + [PASS] simple_package::tests::test_fib [..] + [IGNORE] simple_package::tests::ignored_test + Running 11 test(s) from tests/ + [PASS] simple_package_integrationtest::contract::call_and_invoke [..] + [PASS] simple_package_integrationtest::ext_function_test::test_my_test [..] + [IGNORE] simple_package_integrationtest::ext_function_test::ignored_test + [PASS] simple_package_integrationtest::ext_function_test::test_simple [..] + [PASS] simple_package_integrationtest::test_simple::test_simple [..] + [PASS] simple_package_integrationtest::test_simple::test_simple2 [..] + [PASS] simple_package_integrationtest::test_simple::test_two [..] + [PASS] simple_package_integrationtest::test_simple::test_two_and_two [..] + [FAIL] simple_package_integrationtest::test_simple::test_failing + + Failure data: + 0x6661696c696e6720636865636b ('failing check') + + [FAIL] simple_package_integrationtest::test_simple::test_another_failing + + Failure data: + 0x6661696c696e6720636865636b ('failing check') + + [PASS] simple_package_integrationtest::without_prefix::five [..] + Tests: 9 passed, 2 failed, 2 ignored, 0 filtered out + + Failures: + simple_package_integrationtest::test_simple::test_failing + simple_package_integrationtest::test_simple::test_another_failing + "}, + ); +} + +#[test] +fn simple_package_with_cheats() { + let temp = setup_package("simple_package_with_cheats"); + let output = test_runner(&temp).assert().code(0); + + assert_stdout_contains( + output, + indoc! {r" + [..]Compiling[..] + [..]Finished[..] + + + Collected 5 test(s) from simple_package_with_cheats package + Running 0 test(s) from src/ + Running 5 test(s) from tests/ + [PASS] simple_package_with_cheats_integrationtest::contract::call_and_invoke [..] + [PASS] simple_package_with_cheats_integrationtest::contract::call_and_invoke_proxy [..] + [PASS] simple_package_with_cheats_integrationtest::contract::call_and_invoke_library_call [..] + [PASS] simple_package_with_cheats_integrationtest::contract::deploy_syscall [..] + [PASS] simple_package_with_cheats_integrationtest::contract::block_hash [..] + Tests: 5 passed, 0 failed, 0 ignored, 0 filtered out + "}, + ); +} + #[test] fn simple_package_with_git_dependency() { let temp = setup_package("simple_package"); @@ -986,6 +1062,10 @@ fn incompatible_snforge_std_version_error() { } #[test] +#[cfg_attr( + feature = "cairo-native", + ignore = "Native runner does not support vm resources tracking" +)] fn detailed_resources_flag_cairo_steps() { let temp = setup_package("erc20_package"); let output = test_runner(&temp) @@ -1181,6 +1261,10 @@ fn exact_printing_mixed() { } #[test] +#[cfg_attr( + feature = "cairo-native", + ignore = "Native runner does not support panic backtrace yet" +)] fn dispatchers() { let temp = setup_package("dispatchers"); diff --git a/crates/forge/tests/integration/meta_tx_v0.rs b/crates/forge/tests/integration/meta_tx_v0.rs index a2bd499e21..a6bcafb88a 100644 --- a/crates/forge/tests/integration/meta_tx_v0.rs +++ b/crates/forge/tests/integration/meta_tx_v0.rs @@ -1,6 +1,6 @@ use forge_runner::forge_config::ForgeTrackedResource; use indoc::indoc; -use scarb_api::ScarbCommand; +use scarb_api::version::scarb_version; use semver::Version; use std::path::Path; use test_utils::runner::{Contract, assert_passed}; @@ -9,9 +9,7 @@ use test_utils::test_case; // TODO(#3704) Remove scarb version check fn skip_scarb_lt_2_11_0() -> bool { - let version_info = ScarbCommand::version() - .run() - .expect("Failed to get Scarb version"); + let version_info = scarb_version().expect("Failed to get Scarb version"); if version_info.scarb < Version::new(2, 11, 0) { eprintln!("[IGNORED] `meta_tx_v0` syscall is not supported in Scarb < 2.11.0"); diff --git a/crates/forge/tests/integration/setup_fork.rs b/crates/forge/tests/integration/setup_fork.rs index 6e3c95c742..467084b8ad 100644 --- a/crates/forge/tests/integration/setup_fork.rs +++ b/crates/forge/tests/integration/setup_fork.rs @@ -24,7 +24,7 @@ use forge_runner::forge_config::{ ExecutionDataToSave, ForgeConfig, OutputConfig, TestRunnerConfig, }; use scarb_api::ScarbCommand; -use scarb_api::metadata::MetadataCommandExt; +use scarb_api::metadata::metadata_for_dir; use shared::test_utils::node_url::node_rpc_url; use test_utils::runner::{Contract, assert_case_output_contains, assert_failed, assert_passed}; use test_utils::running_tests::run_test_case; @@ -117,10 +117,7 @@ fn fork_aliased_decorator() { .run() .unwrap(); - let metadata = ScarbCommand::metadata() - .current_dir(test.path().unwrap()) - .run() - .unwrap(); + let metadata = metadata_for_dir(test.path().unwrap()).unwrap(); let package = metadata .packages @@ -209,10 +206,7 @@ fn fork_aliased_decorator_overrding() { .run() .unwrap(); - let metadata = ScarbCommand::metadata() - .current_dir(test.path().unwrap()) - .run() - .unwrap(); + let metadata = metadata_for_dir(test.path().unwrap()).unwrap(); let package = metadata .packages diff --git a/crates/forge/tests/integration/too_many_events.rs b/crates/forge/tests/integration/too_many_events.rs index a349b0ca33..590a64d2ae 100644 --- a/crates/forge/tests/integration/too_many_events.rs +++ b/crates/forge/tests/integration/too_many_events.rs @@ -65,7 +65,7 @@ fn ok_events() { .unwrap() ); - let result = run_test_case(&test, ForgeTrackedResource::CairoSteps); + let result = run_test_case(&test, ForgeTrackedResource::SierraGas); assert_passed(&result); } @@ -133,28 +133,28 @@ fn too_many_events() { .unwrap() ); - let result = run_test_case(&test, ForgeTrackedResource::CairoSteps); + let result = run_test_case(&test, ForgeTrackedResource::SierraGas); assert_failed(&result); assert_case_output_contains( &result, "emit_too_many_events", &format!( - "Got an exception while executing a hint: Exceeded the maximum number of events, number events: {emit_too_many_events}, max number events: {max_n_emitted_events}." + "Exceeded the maximum number of events, number events: {emit_too_many_events}, max number events: {max_n_emitted_events}." ), ); assert_case_output_contains( &result, "emit_too_many_data", &format!( - "Got an exception while executing a hint: Exceeded the maximum data length, data length: {emit_too_many_data}, max data length: {max_data_length}." + "Exceeded the maximum data length, data length: {emit_too_many_data}, max data length: {max_data_length}." ), ); assert_case_output_contains( &result, "emit_too_many_keys", &format!( - "Got an exception while executing a hint: Exceeded the maximum keys length, keys length: {emit_too_many_keys}, max keys length: {max_keys_length}." + "Exceeded the maximum keys length, keys length: {emit_too_many_keys}, max keys length: {max_keys_length}." ), ); } diff --git a/crates/native-api/Cargo.toml b/crates/native-api/Cargo.toml new file mode 100644 index 0000000000..47bf7f53ac --- /dev/null +++ b/crates/native-api/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "native-api" +version.workspace = true +edition.workspace = true + +[dependencies] +thiserror.workspace = true +starknet_api.workspace = true +cairo-lang-starknet-classes.workspace = true +cairo-native.workspace = true diff --git a/crates/native-api/src/lib.rs b/crates/native-api/src/lib.rs new file mode 100644 index 0000000000..ae9688a46f --- /dev/null +++ b/crates/native-api/src/lib.rs @@ -0,0 +1,60 @@ +//! This module provides functionality related to `cairo-native`. +//! +//! Currently, it includes: +//! - Compiling Sierra contract classes into `cairo-native` executors. + +use cairo_lang_starknet_classes::contract_class::ContractClass; +use cairo_native::executor::AotContractExecutor; +use starknet_api::contract_class::SierraVersion; + +#[derive(Debug, thiserror::Error)] +pub enum NativeCompilationError { + #[error("Unsupported Sierra version {0}. cairo-native requires version 1.7.0 or later.")] + UnsupportedSierraVersion(String), +} + +/// Compiles a given Sierra [`ContractClass`] into an [`AotContractExecutor`] for `cairo-native` execution. +pub fn compile_contract_class( + contract_class: &ContractClass, +) -> Result { + let sierra_version = extract_sierra_version(contract_class); + check_sierra_version(&sierra_version)?; + + let sierra_program = contract_class + .extract_sierra_program() + .expect("extraction should succeed"); + + Ok(AotContractExecutor::new( + &sierra_program, + &contract_class.entry_points_by_type, + sierra_version.clone().into(), + cairo_native::OptLevel::Default, + None, + ) + .expect("compilation should succeed")) +} + +/// Extracts the Sierra version from the given [`ContractClass`]. +fn extract_sierra_version(contract_class: &ContractClass) -> SierraVersion { + let sierra_version_values = contract_class + .sierra_program + .iter() + .take(3) + .map(|x| x.value.clone()) + .collect::>(); + + SierraVersion::extract_from_program(&sierra_version_values) + .expect("version extraction should succeed") +} + +/// Checks if the given Sierra version is supported by `cairo-native`. +fn check_sierra_version(sierra_version: &SierraVersion) -> Result<(), NativeCompilationError> { + let minimal_supported_version = SierraVersion::new(1, 7, 0); + if sierra_version < &minimal_supported_version { + Err(NativeCompilationError::UnsupportedSierraVersion( + sierra_version.to_string(), + )) + } else { + Ok(()) + } +} diff --git a/crates/scarb-api/Cargo.toml b/crates/scarb-api/Cargo.toml index b1df523b19..9b63fc80df 100644 --- a/crates/scarb-api/Cargo.toml +++ b/crates/scarb-api/Cargo.toml @@ -3,6 +3,9 @@ name = "scarb-api" version = "1.0.0" edition.workspace = true +[features] +cairo-native = ["dep:cairo-native", "dep:native-api"] + [dependencies] anyhow.workspace = true shared.workspace = true @@ -19,7 +22,12 @@ rayon.workspace = true itertools.workspace = true universal-sierra-compiler-api = { path = "../universal-sierra-compiler-api" } foundry-ui = { path = "../foundry-ui" } +cairo-native = { workspace = true, optional = true } +cairo-lang-starknet-classes.workspace = true +native-api = { path = "../native-api", optional = true } +tracing.workspace = true [dev-dependencies] assert_fs.workspace = true indoc.workspace = true +num-bigint.workspace = true diff --git a/crates/scarb-api/src/artifacts.rs b/crates/scarb-api/src/artifacts.rs index e20b5ba32d..33144e845f 100644 --- a/crates/scarb-api/src/artifacts.rs +++ b/crates/scarb-api/src/artifacts.rs @@ -1,29 +1,49 @@ use anyhow::Result; use crate::artifacts::representation::StarknetArtifactsRepresentation; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +#[cfg(feature = "cairo-native")] +use cairo_native::executor::AotContractExecutor; use camino::{Utf8Path, Utf8PathBuf}; use itertools::Itertools; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::collections::HashMap; use std::fs; -use universal_sierra_compiler_api::{SierraType, compile_sierra_at_path}; +use universal_sierra_compiler_api::compile_contract_sierra_at_path; mod deserialized; mod representation; /// Contains compiled Starknet artifacts -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone)] pub struct StarknetContractArtifacts { /// Compiled sierra code pub sierra: String, /// Compiled casm code - pub casm: String, + pub casm: CasmContractClass, + + #[cfg(feature = "cairo-native")] + /// Optional AOT compiled native executor + pub executor: Option, +} + +impl PartialEq for StarknetContractArtifacts { + fn eq(&self, other: &Self) -> bool { + let eq = self.sierra == other.sierra && self.casm == other.casm; + + #[cfg(feature = "cairo-native")] + let eq = eq && self.executor.is_some() == other.executor.is_some(); + + eq + } } #[derive(PartialEq, Debug)] pub(crate) struct StarknetArtifactsFiles { base: Utf8PathBuf, other: Vec, + #[cfg(feature = "cairo-native")] + compile_native: bool, } impl StarknetArtifactsFiles { @@ -31,15 +51,24 @@ impl StarknetArtifactsFiles { Self { base: base_file, other: other_files, + #[cfg(feature = "cairo-native")] + compile_native: false, } } + #[cfg(feature = "cairo-native")] + pub(crate) fn compile_native(mut self, compile_native: bool) -> Self { + self.compile_native = compile_native; + self + } + + #[tracing::instrument(skip_all, level = "debug")] pub(crate) fn load_contracts_artifacts( self, ) -> Result> { // TODO(#2626) handle duplicates - let mut base_artifacts: HashMap = - compile_artifacts( + let mut base_artifacts: HashMap = self + .compile_artifacts( StarknetArtifactsRepresentation::try_from_path(self.base.as_path())?.artifacts(), )?; @@ -52,14 +81,59 @@ impl StarknetArtifactsFiles { let other_artifacts: Vec<(String, Utf8PathBuf)> = unique_artifacts(other_artifact_representations, &base_artifacts); - let compiled_artifacts = compile_artifacts(other_artifacts)?; + let compiled_artifacts = self.compile_artifacts(other_artifacts)?; base_artifacts.extend(compiled_artifacts); Ok(base_artifacts) } + + #[tracing::instrument(skip_all, level = "debug")] + fn compile_artifacts( + &self, + artifacts: Vec<(String, Utf8PathBuf)>, + ) -> Result> { + artifacts + .into_par_iter() + .map(|(name, path)| { + self.compile_artifact_at_path(&path) + .map(|artifact| (name.to_string(), (artifact, path))) + }) + .collect::>() + } + + #[tracing::instrument(skip_all, level = "debug")] + #[cfg_attr(not(feature = "cairo-native"), expect(clippy::unused_self))] + fn compile_artifact_at_path(&self, path: &Utf8Path) -> Result { + let sierra = fs::read_to_string(path)?; + + let casm = compile_contract_sierra_at_path(path)?; + + #[cfg(feature = "cairo-native")] + let executor = self.compile_to_native(&sierra)?; + + Ok(StarknetContractArtifacts { + sierra, + casm, + #[cfg(feature = "cairo-native")] + executor, + }) + } + + #[cfg(feature = "cairo-native")] + #[tracing::instrument(skip_all, level = "debug")] + fn compile_to_native(&self, sierra: &str) -> Result> { + Ok(if self.compile_native { + Some(native_api::compile_contract_class(&serde_json::from_str( + sierra, + )?)?) + } else { + None + }) + } } +#[tracing::instrument(skip_all, level = "debug")] fn unique_artifacts( artifact_representations: Vec, current_artifacts: &HashMap, @@ -72,33 +146,19 @@ fn unique_artifacts( .collect() } -fn compile_artifacts( - artifacts: Vec<(String, Utf8PathBuf)>, -) -> Result> { - artifacts - .into_par_iter() - .map(|(name, path)| { - compile_artifact_at_path(&path).map(|artifact| (name.to_string(), (artifact, path))) - }) - .collect::>() -} - -fn compile_artifact_at_path(path: &Utf8Path) -> Result { - let sierra = fs::read_to_string(path)?; - - let casm = compile_sierra_at_path(path, &SierraType::Contract)?; - - Ok(StarknetContractArtifacts { sierra, casm }) -} - #[cfg(test)] mod tests { use super::*; + use crate::ScarbCommand; + use crate::tests::setup_package; + use assert_fs::TempDir; use assert_fs::fixture::{FileWriteStr, PathChild}; + use cairo_lang_starknet_classes::casm_contract_class::CasmContractEntryPoints; use camino::Utf8PathBuf; use deserialized::{StarknetArtifacts, StarknetContract, StarknetContractArtifactPaths}; use indoc::indoc; + use num_bigint::BigUint; #[test] fn test_unique_artifacts() { @@ -108,7 +168,17 @@ mod tests { ( StarknetContractArtifacts { sierra: "sierra1".to_string(), - casm: "casm1".to_string(), + casm: CasmContractClass { + prime: BigUint::default(), + compiler_version: String::default(), + bytecode: vec![], + bytecode_segment_lengths: None, + hints: vec![], + pythonic_hints: None, + entry_points_by_type: CasmContractEntryPoints::default(), + }, + #[cfg(feature = "cairo-native")] + executor: None, }, Utf8PathBuf::from("path1"), ), @@ -152,9 +222,8 @@ mod tests { assert_eq!(result[0].0, "contract2"); } - #[test] - fn test_load_contracts_artifacts() { - let temp = crate::tests::setup_package("basic_package"); + fn setup() -> (TempDir, StarknetArtifactsFiles) { + let temp = setup_package("basic_package"); let tests_dir = temp.join("tests"); fs::create_dir(&tests_dir).unwrap(); @@ -196,11 +265,34 @@ mod tests { // Create `StarknetArtifactsFiles` let artifacts_files = StarknetArtifactsFiles::new(base_file, other_files); + (temp, artifacts_files) + } + + #[test] + fn test_load_contracts_artifacts() { + let (_temp, artifacts_files) = setup(); + + // Load the contracts + let result = artifacts_files.load_contracts_artifacts().unwrap(); + + // Assert the Contract Artifacts are loaded. + assert!(result.contains_key("ERC20")); + assert!(result.contains_key("HelloStarknet")); + } + + #[test] + #[cfg(feature = "cairo-native")] + fn test_load_contracts_artifacts_native() { + let (_temp, artifacts_files) = setup(); + + let artifacts_files = artifacts_files.compile_native(true); + // Load the contracts let result = artifacts_files.load_contracts_artifacts().unwrap(); // Assert the Contract Artifacts are loaded. assert!(result.contains_key("ERC20")); assert!(result.contains_key("HelloStarknet")); + assert!(result.get("ERC20").unwrap().0.executor.is_some()); } } diff --git a/crates/scarb-api/src/command.rs b/crates/scarb-api/src/command.rs index 8a4f4363a7..8744f46902 100644 --- a/crates/scarb-api/src/command.rs +++ b/crates/scarb-api/src/command.rs @@ -1,6 +1,3 @@ -use crate::metadata::MetadataCommand; -use crate::version::VersionCommand; -use anyhow::Context; use scarb_ui::args::{FeaturesSpec, PackagesFilter, ProfileSpec, ToEnvVars}; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; @@ -33,7 +30,6 @@ pub struct ScarbCommand { json: bool, offline: bool, manifest_path: Option, - scarb_path: Option, } impl ScarbCommand { @@ -53,34 +49,6 @@ impl ScarbCommand { cmd } - /// Creates [`MetadataCommand`] command - #[must_use] - pub fn metadata() -> MetadataCommand { - MetadataCommand::new() - } - - /// Creates [`VersionCommand`] command - #[must_use] - pub fn version() -> VersionCommand { - VersionCommand::new() - } - - /// Ensures that `scarb` binary is available in the system. - pub fn ensure_available(&self) -> anyhow::Result<()> { - which::which(self.binary_path()) - .context("Cannot find `scarb` binary. Make sure you have Scarb installed https://github.com/software-mansion/scarb")?; - Ok(()) - } - - /// Path to `scarb` executable. - /// - /// If not set, this will use the `$SCARB` environment variable, and if that is not set, it - /// will simply be `scarb` and the system will look it up in `$PATH`. - pub fn scarb_path(&mut self, path: impl Into) -> &mut Self { - self.scarb_path = Some(path.into()); - self - } - /// Path to `Scarb.toml`. /// /// If not set, this will look for `Scarb.toml` in the current directory or its ancestors. @@ -186,7 +154,7 @@ impl ScarbCommand { /// Build executable `scarb` command. #[must_use] pub fn command(&self) -> Command { - let scarb = self.binary_path(); + let scarb = binary_path(); let mut cmd = Command::new(scarb); @@ -227,13 +195,6 @@ impl ScarbCommand { cmd } - fn binary_path(&self) -> PathBuf { - self.scarb_path - .clone() - .or_else(|| env::var("SCARB").map(PathBuf::from).ok()) - .unwrap_or_else(|| PathBuf::from("scarb")) - } - /// Runs configured `scarb` command. pub fn run(&self) -> Result<(), ScarbCommandError> { let mut cmd = self.command(); @@ -244,3 +205,24 @@ impl ScarbCommand { } } } + +#[derive(Error, Debug)] +pub enum ScarbUnavailableError { + #[error( + "Cannot find `scarb` binary. Make sure you have Scarb installed https://github.com/software-mansion/scarb" + )] + NotFound(which::Error), +} + +pub fn ensure_scarb_available() -> anyhow::Result<(), ScarbUnavailableError> { + which::which(binary_path()) + .map(|_| ()) + .map_err(ScarbUnavailableError::NotFound) +} + +fn binary_path() -> PathBuf { + env::var("SCARB") + .map(PathBuf::from) + .ok() + .unwrap_or_else(|| PathBuf::from("scarb")) +} diff --git a/crates/scarb-api/src/lib.rs b/crates/scarb-api/src/lib.rs index 0cf9f7f627..9d97047c96 100644 --- a/crates/scarb-api/src/lib.rs +++ b/crates/scarb-api/src/lib.rs @@ -23,6 +23,7 @@ const INTEGRATION_TEST_TYPE: &str = "integration"; /// If artifacts with `test_type` of `INTEGRATION_TEST_TYPE` are present, we use them base path /// and extend with paths to other artifacts. /// If `INTEGRATION_TEST_TYPE` is not present, we take first artifacts found. +#[tracing::instrument(skip_all, level = "debug")] fn get_starknet_artifacts_paths_from_test_targets( target_dir: &Utf8Path, test_targets: &HashMap, @@ -72,6 +73,7 @@ fn get_starknet_artifacts_paths_from_test_targets( /// Try getting the path to `starknet_artifacts.json` related to `starknet-contract` target. This file that is generated by `scarb build` command. /// If the file is not present, `None` is returned. +#[tracing::instrument(skip_all, level = "debug")] fn get_starknet_artifacts_path( target_dir: &Utf8Path, target_name: &str, @@ -93,12 +95,24 @@ fn get_starknet_artifacts_path( path.map(|path| StarknetArtifactsFiles::new(path, vec![])) } +#[derive(Default)] +pub struct CompilationOpts { + pub use_test_target_contracts: bool, + #[cfg(feature = "cairo-native")] + pub run_native: bool, +} + /// Get the map with `StarknetContractArtifacts` for the given package +#[tracing::instrument(skip_all, level = "debug")] pub fn get_contracts_artifacts_and_source_sierra_paths( artifacts_dir: &Utf8Path, package: &PackageMetadata, - use_test_target_contracts: bool, ui: &UI, + CompilationOpts { + use_test_target_contracts, + #[cfg(feature = "cairo-native")] + run_native, + }: CompilationOpts, ) -> Result> { let starknet_artifact_files = if use_test_target_contracts { let test_targets = test_targets_by_name(package); @@ -115,6 +129,8 @@ pub fn get_contracts_artifacts_and_source_sierra_paths( }; if let Some(starknet_artifact_files) = starknet_artifact_files { + #[cfg(feature = "cairo-native")] + let starknet_artifact_files = starknet_artifact_files.compile_native(run_native); starknet_artifact_files.load_contracts_artifacts() } else { Ok(HashMap::default()) @@ -205,7 +221,7 @@ pub fn test_targets_by_name(package: &PackageMetadata) -> HashMap, + pub no_deps: bool, + pub profile: Option, +} + +/// Fetches `scarb` metadata for a specific directory. +pub fn metadata_for_dir(dir: impl Into) -> Result { + metadata_with_opts(MetadataOpts { + current_dir: Some(dir.into()), + ..MetadataOpts::default() + }) +} -pub trait MetadataCommandExt { - fn run(&mut self) -> Result; +/// Fetches `scarb` metadata for the current directory. +pub fn metadata() -> Result { + metadata_with_opts(MetadataOpts::default()) } -impl MetadataCommandExt for MetadataCommand { - fn run(&mut self) -> Result { - self.inherit_stdout() - .exec() - .context("error: could not gather project metadata from Scarb due to previous error") +/// Fetches `scarb` metadata with specified options. +pub fn metadata_with_opts( + MetadataOpts { + current_dir, + no_deps, + profile, + }: MetadataOpts, +) -> Result { + ensure_scarb_available()?; + + let mut command = MetadataCommand::new(); + + if let Some(dir) = current_dir { + command.current_dir(dir); + } + + if let Some(profile) = profile { + command.profile(profile); } + + if no_deps { + command.no_deps(); + } + + command + .inherit_stderr() + .inherit_stdout() + .exec() + .map_err(MetadataError::ScarbExecutionFailed) } diff --git a/crates/scarb-api/src/version.rs b/crates/scarb-api/src/version.rs index 3720379df6..4d68541b41 100644 --- a/crates/scarb-api/src/version.rs +++ b/crates/scarb-api/src/version.rs @@ -1,71 +1,118 @@ -use crate::ScarbCommand; -use anyhow::{Context, Result}; +//! Functionality for fetching and parsing `scarb` version information. + +use crate::{ScarbCommand, ScarbUnavailableError, ensure_scarb_available}; use regex::Regex; use semver::Version; -use shared::command::CommandExt; -use std::path::PathBuf; -use std::str::from_utf8; +use shared::command::{CommandError, CommandExt}; +use std::collections::HashMap; +use std::path::Path; +use std::process::Output; +use std::sync::LazyLock; +use thiserror::Error; +#[derive(Debug)] pub struct ScarbVersionOutput { pub scarb: Version, pub cairo: Version, + pub sierra: Version, } -#[derive(Clone, Debug, Default)] -pub struct VersionCommand { - current_dir: Option, +/// Errors that can occur when fetching `scarb` version information. +#[derive(Error, Debug)] +pub enum VersionError { + #[error(transparent)] + ScarbNotFound(#[from] ScarbUnavailableError), + + #[error("Failed to execute `scarb --version`: {0}")] + CommandExecutionError(#[from] CommandError), + + #[error("Could not parse version for tool `{0}`: {1}")] + VersionParseError(String, #[source] semver::Error), + + #[error("Missing version entry for `{0}`")] + MissingToolVersion(String), } -impl VersionCommand { - #[must_use] - pub fn new() -> Self { - Self { current_dir: None } - } +/// Fetches `scarb` version information for the current directory. +pub fn scarb_version() -> Result { + scarb_version_internal(None) +} + +/// Fetches `scarb` version information for a specific directory. +pub fn scarb_version_for_dir(dir: impl AsRef) -> Result { + scarb_version_internal(Some(dir.as_ref())) +} - /// Current directory of the `scarb --command` process. - #[must_use] - pub fn current_dir(mut self, path: impl Into) -> Self { - self.current_dir = Some(path.into()); - self +/// Internal function to fetch `scarb` version information. +fn scarb_version_internal(dir: Option<&Path>) -> Result { + let output = run_command(dir)?; + parse_output(&output) +} + +/// Runs the `scarb --version` command in the specified directory. +fn run_command(dir: Option<&Path>) -> Result { + ensure_scarb_available()?; + let mut scarb_version_command = ScarbCommand::new().arg("--version").command(); + + if let Some(path) = dir { + scarb_version_command.current_dir(path); } + + Ok(scarb_version_command.output_checked()?) } -impl VersionCommand { - pub fn run(self) -> Result { - let mut scarb_version_command = ScarbCommand::new().arg("--version").command(); +/// Parses the output of the `scarb --version` command. +fn parse_output(output: &Output) -> Result { + let output_str = str::from_utf8(&output.stdout).expect("valid UTF-8 from scarb"); - if let Some(path) = &self.current_dir { - scarb_version_command.current_dir(path); - } + let mut versions = extract_versions(output_str)?; - let scarb_version = scarb_version_command - .output_checked() - .context("Failed to execute `scarb --version`")?; + let mut get_version = |tool: &str| { + versions + .remove(tool) + .ok_or_else(|| VersionError::MissingToolVersion(tool.into())) + }; - let version_output = from_utf8(&scarb_version.stdout) - .context("Failed to parse `scarb --version` output to UTF-8")?; + Ok(ScarbVersionOutput { + scarb: get_version("scarb")?, + cairo: get_version("cairo")?, + sierra: get_version("sierra")?, + }) +} - Ok(ScarbVersionOutput { - scarb: Self::extract_version(version_output, "scarb")?, - cairo: Self::extract_version(version_output, "cairo:")?, - }) - } +/// Extracts tool versions from the version output string. +fn extract_versions(version_output: &str) -> Result, VersionError> { + // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + static VERSION_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"(?P\w+)[\s:]+(?P(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?)").expect("this should be a valid regex") + }); - fn extract_version(version_output: &str, tool: &str) -> Result { - // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string - let version_regex = Regex::new( - &format!(r"{tool}?\s*((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)")) - .context("Could not create version matching regex")?; + VERSION_REGEX + .captures_iter(version_output) + .map(|cap| { + let tool = cap + .name("tool") + .expect("regex ensures tool name exists") + .as_str() + .to_string(); - let version_capture = version_regex - .captures(version_output) - .context(format!("Could not find {tool} version"))?; + let ver_str = cap + .name("ver") + .expect("regex ensures version string exists") + .as_str(); - let version = version_capture - .get(1) - .context(format!("Could not find {tool} version"))? - .as_str(); + Version::parse(ver_str) + .map_err(|e| VersionError::VersionParseError(tool.clone(), e)) + .map(|version| (tool, version)) + }) + .collect() +} +#[cfg(test)] +mod tests { + use super::*; - Version::parse(version).context(format!("Failed to parse {tool} version")) + #[test] + fn test_happy_case() { + scarb_version().unwrap(); } } diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index cbf8ebb84a..23b13b3067 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -19,6 +19,7 @@ clap_complete.workspace = true cairo-vm.workspace = true num-traits.workspace = true foundry-ui ={ path = "../foundry-ui" } +thiserror = "2.0.17" [features] testing = [] diff --git a/crates/shared/src/command.rs b/crates/shared/src/command.rs index ea668b0033..a86751413e 100644 --- a/crates/shared/src/command.rs +++ b/crates/shared/src/command.rs @@ -1,22 +1,42 @@ -use anyhow::{Context, Ok, Result, bail}; -use std::process::{Command, Output}; +use std::io; +use std::process::{Command, ExitStatus, Output}; +use thiserror::Error; +/// Error type for command execution failures +#[derive(Debug, Error)] +pub enum CommandError { + #[error("Failed to run {command}: {source}")] + IoError { + command: String, + #[source] + source: io::Error, + }, + + #[error("Command {command} failed with status {status}")] + FailedStatus { command: String, status: ExitStatus }, +} + +/// Trait extension for Command to check output status pub trait CommandExt { - fn output_checked(&mut self) -> Result; + fn output_checked(&mut self) -> Result; } impl CommandExt for Command { - fn output_checked(&mut self) -> Result { + fn output_checked(&mut self) -> Result { let command = self.get_program().to_string_lossy().to_string(); - let output = self - .output() - .with_context(|| format!("Failed to run {command}"))?; - - if !output.status.success() { - bail!("Command {command} failed with status {}", output.status); + match self.output() { + Ok(output) => { + if output.status.success() { + Ok(output) + } else { + Err(CommandError::FailedStatus { + command, + status: output.status, + }) + } + } + Err(source) => Err(CommandError::IoError { command, source }), } - - Ok(output) } } diff --git a/crates/sncast/src/helpers/artifacts.rs b/crates/sncast/src/helpers/artifacts.rs new file mode 100644 index 0000000000..74d5395582 --- /dev/null +++ b/crates/sncast/src/helpers/artifacts.rs @@ -0,0 +1,8 @@ +/// Contains compiled Starknet artifacts +#[derive(Debug, Clone)] +pub struct CastStarknetContractArtifacts { + /// Compiled sierra code + pub sierra: String, + /// Compiled casm code + pub casm: String, +} diff --git a/crates/sncast/src/helpers/mod.rs b/crates/sncast/src/helpers/mod.rs index b49d348d34..2ec5ac1418 100644 --- a/crates/sncast/src/helpers/mod.rs +++ b/crates/sncast/src/helpers/mod.rs @@ -1,4 +1,5 @@ pub mod account; +pub mod artifacts; pub mod block_explorer; pub mod braavos; pub mod command; diff --git a/crates/sncast/src/helpers/scarb_utils.rs b/crates/sncast/src/helpers/scarb_utils.rs index 56d83db9a5..8cf13387ed 100644 --- a/crates/sncast/src/helpers/scarb_utils.rs +++ b/crates/sncast/src/helpers/scarb_utils.rs @@ -1,16 +1,17 @@ +use crate::helpers::artifacts::CastStarknetContractArtifacts; use anyhow::{Context, Result, anyhow}; use camino::{Utf8Path, Utf8PathBuf}; use foundry_ui::{UI, components::warning::WarningMessage}; +use scarb_api::metadata::{MetadataError, MetadataOpts, metadata_for_dir, metadata_with_opts}; use scarb_api::{ - ScarbCommand, ScarbCommandError, StarknetContractArtifacts, + CompilationOpts, ScarbCommand, ScarbCommandError, ensure_scarb_available, get_contracts_artifacts_and_source_sierra_paths, - metadata::{Metadata, MetadataCommand, PackageMetadata}, + metadata::{Metadata, PackageMetadata}, target_dir_for_workspace, }; use scarb_ui::args::PackagesFilter; use shared::command::CommandExt; use std::collections::HashMap; -use std::env; use std::str::FromStr; pub fn get_scarb_manifest() -> Result { @@ -18,7 +19,7 @@ pub fn get_scarb_manifest() -> Result { } pub fn get_scarb_manifest_for(dir: &Utf8Path) -> Result { - ScarbCommand::new().ensure_available()?; + ensure_scarb_available()?; let output = ScarbCommand::new() .current_dir(dir) @@ -36,34 +37,23 @@ pub fn get_scarb_manifest_for(dir: &Utf8Path) -> Result { Ok(path) } -fn get_scarb_metadata_command(manifest_path: &Utf8PathBuf) -> Result { - ScarbCommand::new().ensure_available()?; - - let mut command = ScarbCommand::metadata(); - command.inherit_stderr().manifest_path(manifest_path); - Ok(command) -} - -fn execute_scarb_metadata_command(command: &MetadataCommand) -> Result { - command.exec().context(format!( - "Failed to read the `Scarb.toml` manifest file. Doesn't exist in the current or parent directories = {}", - env::current_dir() - .expect("Failed to access the current directory") - .into_os_string() - .into_string() - .expect("Failed to convert current directory into a string") - )) -} - -pub fn get_scarb_metadata(manifest_path: &Utf8PathBuf) -> Result { - let mut command = get_scarb_metadata_command(manifest_path)?; - let command = command.no_deps(); - execute_scarb_metadata_command(command) +pub fn get_scarb_metadata(manifest_path: &Utf8PathBuf) -> Result { + metadata_with_opts(MetadataOpts { + current_dir: Some( + manifest_path + .parent() + .expect("manifest should have parent") + .into(), + ), + no_deps: true, + ..MetadataOpts::default() + }) } -pub fn get_scarb_metadata_with_deps(manifest_path: &Utf8PathBuf) -> Result { - let command = get_scarb_metadata_command(manifest_path)?; - execute_scarb_metadata_command(&command) +pub fn get_scarb_metadata_with_deps( + manifest_path: &Utf8PathBuf, +) -> Result { + metadata_for_dir(manifest_path.parent().expect("manifest should have parent")) } pub fn get_cairo_version(manifest_path: &Utf8PathBuf) -> Result { @@ -157,7 +147,7 @@ pub fn build_and_load_artifacts( config: &BuildConfig, build_for_script: bool, ui: &UI, -) -> Result> { +) -> Result> { // TODO (#2042): Remove this logic, always use release as default let default_profile = if build_for_script { "dev" } else { "release" }; build(package, config, default_profile) @@ -170,11 +160,11 @@ pub fn build_and_load_artifacts( Ok(get_contracts_artifacts_and_source_sierra_paths( &target_dir.join(&config.profile), package, - false, - ui + ui, + CompilationOpts::default() ).context("Failed to load artifacts. Make sure you have enabled sierra code generation in Scarb.toml")? .into_iter() - .map(|(name, (artifacts, _))| (name, artifacts)) + .map(|(name, (artifacts, _))| (name, CastStarknetContractArtifacts { sierra: artifacts.sierra, casm: serde_json::to_string(&artifacts.casm).expect("valid serialization") })) .collect()) } else { let profile = &config.profile; @@ -184,11 +174,11 @@ pub fn build_and_load_artifacts( Ok(get_contracts_artifacts_and_source_sierra_paths( &target_dir.join(default_profile), package, - false, - ui + ui, + CompilationOpts::default(), ).context("Failed to load artifacts. Make sure you have enabled sierra code generation in Scarb.toml")? .into_iter() - .map(|(name, (artifacts, _))| (name, artifacts)) + .map(|(name, (artifacts, _))| (name, CastStarknetContractArtifacts { sierra: artifacts.sierra, casm: serde_json::to_string(&artifacts.casm).expect("valid serialization") })) .collect()) } } @@ -204,16 +194,6 @@ mod tests { assert!(metadata.is_ok()); } - #[test] - fn test_get_scarb_metadata_not_found() { - let metadata_err = get_scarb_metadata(&"Scarb.toml".into()).unwrap_err(); - assert!( - metadata_err - .to_string() - .contains("Failed to read the `Scarb.toml` manifest file.") - ); - } - #[test] fn test_get_package_metadata_happy_default() { let metadata = get_package_metadata( diff --git a/crates/sncast/src/starknet_commands/declare.rs b/crates/sncast/src/starknet_commands/declare.rs index 870f7b324a..d63b54ee8d 100644 --- a/crates/sncast/src/starknet_commands/declare.rs +++ b/crates/sncast/src/starknet_commands/declare.rs @@ -3,7 +3,7 @@ use clap::Args; use conversions::IntoConv; use conversions::byte_array::ByteArray; use foundry_ui::UI; -use scarb_api::StarknetContractArtifacts; +use sncast::helpers::artifacts::CastStarknetContractArtifacts; use sncast::helpers::fee::{FeeArgs, FeeSettings}; use sncast::helpers::rpc::RpcArgs; use sncast::response::declare::{ @@ -56,7 +56,7 @@ pub async fn declare( fee_args: FeeArgs, nonce: Option, account: &SingleOwnerAccount<&JsonRpcClient, LocalWallet>, - artifacts: &HashMap, + artifacts: &HashMap, wait_config: WaitForTx, skip_on_already_declared: bool, ui: &UI, diff --git a/crates/sncast/src/starknet_commands/declare_from.rs b/crates/sncast/src/starknet_commands/declare_from.rs index 1eb01b642b..db0b4a5de1 100644 --- a/crates/sncast/src/starknet_commands/declare_from.rs +++ b/crates/sncast/src/starknet_commands/declare_from.rs @@ -15,7 +15,7 @@ use starknet::providers::Provider; use starknet::providers::jsonrpc::{HttpTransport, JsonRpcClient}; use starknet::signers::LocalWallet; use starknet_types_core::felt::Felt; -use universal_sierra_compiler_api::{SierraType, compile_sierra}; +use universal_sierra_compiler_api::compile_contract_sierra; #[derive(Args)] #[command(about = "Declare a contract by fetching it from a different Starknet instance", long_about = None)] @@ -110,11 +110,13 @@ pub async fn declare_from( let sierra: SierraClass = flattened_sierra_to_sierra(flattened_sierra) .expect("Failed to parse flattened sierra class"); - let casm_json: String = compile_sierra( - &serde_json::to_value(&sierra).expect("Failed to convert sierra to json value"), - &SierraType::Contract, + let casm_json: String = serde_json::to_string( + &compile_contract_sierra( + &serde_json::to_value(&sierra).expect("Failed to convert sierra to json value"), + ) + .expect("Failed to compile sierra to casm"), ) - .expect("Failed to compile sierra to casm"); + .expect("serialization should succeed"); let casm: CompiledClass = serde_json::from_str(&casm_json) .expect("Failed to deserialize casm JSON into CompiledClass"); let sierra_class_hash = sierra.class_hash().map_err(anyhow::Error::from)?; diff --git a/crates/sncast/src/starknet_commands/script/run.rs b/crates/sncast/src/starknet_commands/script/run.rs index 8da3ff79f4..ea0b398ee0 100644 --- a/crates/sncast/src/starknet_commands/script/run.rs +++ b/crates/sncast/src/starknet_commands/script/run.rs @@ -31,12 +31,13 @@ use runtime::{ CheatcodeHandlingResult, EnhancedHintError, ExtendedRuntime, ExtensionLogic, StarknetRuntime, SyscallHandlingResult, }; -use scarb_api::{StarknetContractArtifacts, package_matches_version_requirement}; +use scarb_api::package_matches_version_requirement; use scarb_metadata::{Metadata, PackageMetadata}; use script_runtime::CastScriptRuntime; use semver::{Comparator, Op, Version, VersionReq}; use shared::utils::build_readable_text; use sncast::get_nonce; +use sncast::helpers::artifacts::CastStarknetContractArtifacts; use sncast::helpers::configuration::CastConfig; use sncast::helpers::constants::SCRIPT_LIB_ARTIFACT_NAME; use sncast::helpers::fee::{FeeArgs, ScriptFeeSettings}; @@ -58,8 +59,6 @@ use tokio::runtime::Runtime; mod script_runtime; -type ScriptStarknetContractArtifacts = StarknetContractArtifacts; - #[derive(Args, Debug)] #[command(about = "Execute a deployment script")] pub struct Run { @@ -83,7 +82,7 @@ pub struct CastScriptExtension<'a> { pub account: Option<&'a SingleOwnerAccount<&'a JsonRpcClient, LocalWallet>>, pub tokio_runtime: Runtime, pub config: &'a CastConfig, - pub artifacts: &'a HashMap, + pub artifacts: &'a HashMap, pub state: StateManager, pub ui: &'a UI, } @@ -278,7 +277,7 @@ pub fn run( module_name: &str, metadata: &Metadata, package_metadata: &PackageMetadata, - artifacts: &mut HashMap, + artifacts: &mut HashMap, provider: &JsonRpcClient, url: &str, tokio_runtime: Runtime, @@ -448,8 +447,8 @@ fn warn_if_sncast_std_not_compatible(scarb_metadata: &Metadata, ui: &UI) -> Resu fn inject_lib_artifact( metadata: &Metadata, package_metadata: &PackageMetadata, - artifacts: &mut HashMap, -) -> Result> { + artifacts: &mut HashMap, +) -> Result> { let sierra_filename = format!("{}.sierra.json", package_metadata.name); let target_dir = &metadata @@ -459,7 +458,7 @@ fn inject_lib_artifact( // TODO(#2042) let sierra_path = &target_dir.join("dev").join(sierra_filename); - let lib_artifacts = ScriptStarknetContractArtifacts { + let lib_artifacts = CastStarknetContractArtifacts { sierra: fs::read_to_string(sierra_path)?, casm: String::new(), }; diff --git a/crates/sncast/src/starknet_commands/utils/class_hash.rs b/crates/sncast/src/starknet_commands/utils/class_hash.rs index f21e0f22bc..8241cc0c25 100644 --- a/crates/sncast/src/starknet_commands/utils/class_hash.rs +++ b/crates/sncast/src/starknet_commands/utils/class_hash.rs @@ -1,7 +1,7 @@ use anyhow::Context; use clap::Args; use conversions::{IntoConv, byte_array::ByteArray}; -use scarb_api::StarknetContractArtifacts; +use sncast::helpers::artifacts::CastStarknetContractArtifacts; use sncast::{ ErrorData, response::{errors::StarknetCommandError, utils::class_hash::ClassHashResponse}, @@ -24,7 +24,7 @@ pub struct ClassHash { #[allow(clippy::result_large_err)] pub fn get_class_hash( class_hash: &ClassHash, - artifacts: &HashMap, + artifacts: &HashMap, ) -> Result { let contract_artifacts = artifacts.get(&class_hash.contract).ok_or( StarknetCommandError::ContractArtifactsNotFound(ErrorData { diff --git a/crates/sncast/src/starknet_commands/verify/mod.rs b/crates/sncast/src/starknet_commands/verify/mod.rs index 62eb72372c..7869e5772c 100644 --- a/crates/sncast/src/starknet_commands/verify/mod.rs +++ b/crates/sncast/src/starknet_commands/verify/mod.rs @@ -4,7 +4,6 @@ use clap::{ArgGroup, Args, ValueEnum}; use foundry_ui::UI; use foundry_ui::components::warning::WarningMessage; use promptly::prompt; -use scarb_api::StarknetContractArtifacts; use sncast::get_provider; use sncast::helpers::configuration::CastConfig; use sncast::helpers::rpc::FreeProvider; @@ -19,6 +18,7 @@ pub mod walnut; use explorer::ContractIdentifier; use explorer::VerificationInterface; +use sncast::helpers::artifacts::CastStarknetContractArtifacts; use voyager::Voyager; use walnut::WalnutVerificationInterface; @@ -87,7 +87,7 @@ fn display_files_and_confirm( files_to_display: Vec, confirm_verification: bool, ui: &UI, - artifacts: &HashMap, + artifacts: &HashMap, contract_name: &str, ) -> Result<()> { // Display files that will be uploaded @@ -119,7 +119,7 @@ fn display_files_and_confirm( pub async fn verify( args: Verify, manifest_path: &Utf8PathBuf, - artifacts: &HashMap, + artifacts: &HashMap, config: &CastConfig, ui: &UI, ) -> Result { diff --git a/crates/sncast/src/starknet_commands/verify/voyager.rs b/crates/sncast/src/starknet_commands/verify/voyager.rs index 52d43681f7..fc8e8b1e24 100644 --- a/crates/sncast/src/starknet_commands/verify/voyager.rs +++ b/crates/sncast/src/starknet_commands/verify/voyager.rs @@ -3,7 +3,7 @@ use camino::{Utf8Path, Utf8PathBuf}; use foundry_ui::{UI, components::warning::WarningMessage}; use itertools::Itertools; use reqwest::{self, StatusCode}; -use scarb_api::metadata::MetadataCommand; +use scarb_api::metadata::metadata_for_dir; use scarb_metadata::{Metadata, PackageMetadata}; use serde::Serialize; use sncast::Network; @@ -112,10 +112,7 @@ fn gather_packages(metadata: &Metadata, packages: &mut Vec) -> .collect(); for (name, manifest) in out_of_workspace_dependencies { - let new_meta = MetadataCommand::new() - .json() - .manifest_path(manifest) - .exec() + let new_meta = metadata_for_dir(manifest.parent().expect("manifest should have a parent")) .map_err(|_| VoyagerApiError::MetadataError { name: name.clone(), path: manifest.to_string_lossy().to_string(), diff --git a/crates/universal-sierra-compiler-api/Cargo.toml b/crates/universal-sierra-compiler-api/Cargo.toml index 72210181ad..73470a062f 100644 --- a/crates/universal-sierra-compiler-api/Cargo.toml +++ b/crates/universal-sierra-compiler-api/Cargo.toml @@ -12,4 +12,8 @@ which.workspace = true tempfile.workspace = true num-bigint.workspace = true cairo-lang-casm.workspace = true +cairo-lang-starknet-classes.workspace = true camino.workspace = true +tracing.workspace = true +strum_macros.workspace = true +thiserror.workspace = true diff --git a/crates/universal-sierra-compiler-api/src/command.rs b/crates/universal-sierra-compiler-api/src/command.rs index 7383baedb8..76c4058843 100644 --- a/crates/universal-sierra-compiler-api/src/command.rs +++ b/crates/universal-sierra-compiler-api/src/command.rs @@ -1,88 +1,71 @@ -use anyhow::Context; -use std::env; -use std::ffi::{OsStr, OsString}; -use std::path::PathBuf; -use std::process::{Command, Stdio}; +use shared::command::{CommandError, CommandExt}; +use std::process::Output; +use std::{ + env, + ffi::OsStr, + process::{Command, Stdio}, +}; +use thiserror::Error; -/// A builder for `universal-sierra-compiler` command invocation. -#[derive(Clone, Debug, Default)] -pub struct UniversalSierraCompilerCommand { - args: Vec, - current_dir: Option, - inherit_stderr: bool, -} - -impl UniversalSierraCompilerCommand { - /// Creates a default `universal-sierra-compiler` command, - /// which will look for `universal-sierra-compiler` in `$PATH` - #[must_use] - pub fn new() -> Self { - Self::default() - } +/// Errors that can occur while working with `universal-sierra-compiler` command. +#[derive(Debug, Error)] +pub enum USCError { + #[error( + "`universal-sierra-compiler` binary not available. \ + Make sure it is installed and available in PATH or set via UNIVERSAL_SIERRA_COMPILER." + )] + NotFound(#[source] which::Error), - /// Ensures that `universal-sierra-compiler` binary is available in the system. - pub fn ensure_available() -> anyhow::Result<()> { - which::which(UniversalSierraCompilerCommand::binary_path()) - .context("Cannot find `universal-sierra-compiler` binary. \ - Make sure you have USC installed https://github.com/software-mansion/universal-sierra-compiler \ - and added to PATH (or set at UNIVERSAL_SIERRA_COMPILER env var)" - )?; - Ok(()) - } + #[error( + "Error while compiling Sierra. \ + Make sure you have the latest universal-sierra-compiler binary installed. \ + Contact us if it doesn't help." + )] + RunFailed(#[source] CommandError), +} - /// Current directory of the `universal-sierra-compiler` process. - pub fn current_dir(&mut self, path: impl Into) -> &mut Self { - self.current_dir = Some(path.into()); - self - } +/// An internal builder for `universal-sierra-compiler` command invocation. +#[derive(Debug)] +pub struct USCInternalCommand { + inner: Command, +} - /// Inherit standard error, i.e. show USC errors in this process's standard error. - pub fn inherit_stderr(&mut self) -> &mut Self { - self.inherit_stderr = true; - self +impl USCInternalCommand { + /// Creates a new `universal-sierra-compiler` command builder. + pub fn new() -> Result { + ensure_available()?; + let mut cmd = Command::new(binary_path()); + cmd.stderr(Stdio::inherit()); + Ok(Self { inner: cmd }) } /// Adds an argument to pass to `universal-sierra-compiler`. - pub fn arg>(&mut self, arg: S) -> &mut Self { - self.args.push(arg.as_ref().to_os_string()); - self - } - - /// Adds multiple arguments to pass to `universal-sierra-compiler`. - pub fn args(&mut self, args: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - { - self.args - .extend(args.into_iter().map(|s| s.as_ref().to_os_string())); + pub fn arg(mut self, arg: impl AsRef) -> Self { + self.inner.arg(arg); self } - /// Build executable `universal-sierra-compiler` command. + /// Returns the constructed [`Command`]. #[must_use] - pub fn command(&self) -> Command { - let universal_sierra_compiler = UniversalSierraCompilerCommand::binary_path(); - - let mut cmd = Command::new(universal_sierra_compiler); - - cmd.args(&self.args); - - if let Some(path) = &self.current_dir { - cmd.current_dir(path); - } - - if self.inherit_stderr { - cmd.stderr(Stdio::inherit()); - } - - cmd + pub fn command(self) -> Command { + self.inner } - fn binary_path() -> PathBuf { - env::var("UNIVERSAL_SIERRA_COMPILER") - .map(PathBuf::from) - .ok() - .unwrap_or_else(|| PathBuf::from("universal-sierra-compiler")) + /// Runs the `universal-sierra-compiler` command and returns the [`Output`]. + pub fn run(self) -> Result { + self.command().output_checked().map_err(USCError::RunFailed) } } + +/// Ensures that `universal-sierra-compiler` binary is available in the system. +pub fn ensure_available() -> Result<(), USCError> { + which::which(binary_path()) + .map(|_| ()) + .map_err(USCError::NotFound) +} + +/// Returns the binary path either from env or fallback to default name. +fn binary_path() -> String { + env::var("UNIVERSAL_SIERRA_COMPILER") + .unwrap_or_else(|_| "universal-sierra-compiler".to_string()) +} diff --git a/crates/universal-sierra-compiler-api/src/compile.rs b/crates/universal-sierra-compiler-api/src/compile.rs new file mode 100644 index 0000000000..1c9fae3721 --- /dev/null +++ b/crates/universal-sierra-compiler-api/src/compile.rs @@ -0,0 +1,61 @@ +use crate::command::{USCError, USCInternalCommand}; +use camino::Utf8Path; +use serde_json::Value; +use std::io; +use std::io::Write; +use strum_macros::Display; +use tempfile::Builder; +use thiserror::Error; + +/// Errors that can occur during Sierra compilation. +#[derive(Debug, Error)] +pub enum CompilationError { + #[error("Failed to write Sierra JSON to temp file: {0}")] + TempFileWrite(#[from] io::Error), + + #[error("Could not serialize Sierra JSON")] + Serialization(serde_json::Error), + + #[error(transparent)] + USCSetup(#[from] USCError), + + #[error("Failed to deserialize compilation output")] + Deserialization(serde_json::Error), +} + +#[derive(Debug, Display, Copy, Clone)] +#[strum(serialize_all = "lowercase")] +pub enum SierraType { + Contract, + Raw, +} + +/// Compiles the given Sierra JSON into the specified type using the `universal-sierra-compiler`. +pub fn compile_sierra( + sierra_json: &Value, + sierra_type: SierraType, +) -> Result { + let mut temp_sierra_file = Builder::new().tempfile()?; + + let json_bytes = serde_json::to_vec(sierra_json).map_err(CompilationError::Serialization)?; + temp_sierra_file.write_all(&json_bytes)?; + let utf8_path = + Utf8Path::from_path(temp_sierra_file.path()).expect("temp file path is valid UTF-8"); + + compile_sierra_at_path(utf8_path, sierra_type) +} + +/// Compiles the Sierra file at the given path into the specified type using the `universal-sierra-compiler`. +#[tracing::instrument(skip_all, level = "debug")] +pub fn compile_sierra_at_path( + sierra_file_path: &Utf8Path, + sierra_type: SierraType, +) -> Result { + let usc_output = USCInternalCommand::new()? + .arg(format!("compile-{sierra_type}")) + .arg("--sierra-path") + .arg(sierra_file_path.as_str()) + .run()?; + + Ok(String::from_utf8(usc_output.stdout).expect("valid UTF-8 from universal-sierra-compiler")) +} diff --git a/crates/universal-sierra-compiler-api/src/lib.rs b/crates/universal-sierra-compiler-api/src/lib.rs index a75f95bb66..4f17622936 100644 --- a/crates/universal-sierra-compiler-api/src/lib.rs +++ b/crates/universal-sierra-compiler-api/src/lib.rs @@ -1,107 +1,53 @@ -use anyhow::{Context, Result, anyhow}; -use cairo_lang_casm::hints::Hint; +//! API for compiling Sierra programs using the `universal-sierra-compiler` (USC). +//! +//! This crate provides functions to compile Sierra JSON representations of contracts and raw programs into their respective Casm formats. +//! +//! # Note: +//! To allow more flexibility when changing internals, please make public as few items as possible. + +use crate::command::{USCError, USCInternalCommand}; +use crate::compile::{CompilationError, SierraType, compile_sierra, compile_sierra_at_path}; +use crate::representation::RawCasmProgram; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use camino::Utf8Path; -use num_bigint::BigInt; -use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::fmt::Display; -use std::io::Write; -use std::str::from_utf8; -use tempfile::Builder; - -pub use command::*; -use shared::command::CommandExt; +use std::process::Command; mod command; +mod compile; +pub mod representation; -pub type CasmCodeOffset = usize; -pub type CasmInstructionIdx = usize; - -#[derive(Serialize, Deserialize, Debug)] -pub struct AssembledProgramWithDebugInfo { - pub assembled_cairo_program: AssembledCairoProgramWithSerde, - /// `debug_info[i]` contains the following information about the first CASM instruction that - /// was generated by the Sierra statement with - /// `StatementIdx(i)` (i-th index in Sierra statements vector): - /// - code offset in the CASM bytecode - /// - index in CASM instructions vector - /// - /// Those 2 values are usually not equal since the instruction sizes in CASM may vary - pub debug_info: Vec<(CasmCodeOffset, CasmInstructionIdx)>, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct AssembledCairoProgramWithSerde { - pub bytecode: Vec, - pub hints: Vec<(usize, Vec)>, +/// Compiles Sierra JSON of a contract into [`CasmContractClass`]. +pub fn compile_contract_sierra(sierra_json: &Value) -> Result { + let json = compile_sierra(sierra_json, SierraType::Contract)?; + serde_json::from_str(&json).map_err(CompilationError::Deserialization) } -pub trait UniversalSierraCompilerOutput { - fn convert(output: String) -> Self; -} - -impl UniversalSierraCompilerOutput for String { - fn convert(output: String) -> Self { - output - } -} - -impl UniversalSierraCompilerOutput for AssembledProgramWithDebugInfo { - fn convert(output: String) -> Self { - serde_json::from_str(&output).expect("Failed to deserialize casm from json string") - } +/// Compiles Sierra JSON file at the given path of a contract into [`CasmContractClass`]. +pub fn compile_contract_sierra_at_path( + sierra_file_path: &Utf8Path, +) -> Result { + let json = compile_sierra_at_path(sierra_file_path, SierraType::Contract)?; + serde_json::from_str(&json).map_err(CompilationError::Deserialization) } -pub fn compile_sierra( - sierra_json: &Value, - sierra_type: &SierraType, -) -> Result { - let mut temp_sierra_file = Builder::new().tempfile()?; - temp_sierra_file.write_all(serde_json::to_vec(sierra_json)?.as_slice())?; - - compile_sierra_at_path( - Utf8Path::from_path(temp_sierra_file.path()) - .ok_or_else(|| anyhow!("Failed to create Utf8Path from temp file path"))?, - sierra_type, - ) +/// Compiles Sierra JSON of a raw program into [`RawCasmProgram`]. +pub fn compile_raw_sierra(sierra_json: &Value) -> Result { + let json = compile_sierra(sierra_json, SierraType::Raw)?; + serde_json::from_str(&json).map_err(CompilationError::Deserialization) } -pub fn compile_sierra_at_path( +/// Compiles Sierra JSON file at the given path of a raw program into [`RawCasmProgram`]. +pub fn compile_raw_sierra_at_path( sierra_file_path: &Utf8Path, - sierra_type: &SierraType, -) -> Result { - let usc_output = UniversalSierraCompilerCommand::new() - .inherit_stderr() - .args(vec![ - &("compile-".to_string() + &sierra_type.to_string()), - "--sierra-path", - sierra_file_path.as_str(), - ]) - .command() - .output_checked() - .context( - "Error while compiling Sierra. \ - Make sure you have the latest universal-sierra-compiler binary installed. \ - Contact us if it doesn't help", - )?; - - Ok(T::convert(from_utf8(&usc_output.stdout)?.to_string())) -} - -pub enum SierraType { - Contract, - Raw, +) -> Result { + let json = compile_sierra_at_path(sierra_file_path, SierraType::Raw)?; + serde_json::from_str(&json).map_err(CompilationError::Deserialization) } -impl Display for SierraType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - SierraType::Contract => "contract", - SierraType::Raw => "raw", - } - ) - } +/// Creates a `universal-sierra-compiler --version` command. +/// +/// Only exists because of how requirements checker was implemented. +pub fn version_command() -> Result { + Ok(USCInternalCommand::new()?.arg("--version").command()) } diff --git a/crates/universal-sierra-compiler-api/src/representation.rs b/crates/universal-sierra-compiler-api/src/representation.rs new file mode 100644 index 0000000000..19f76e4220 --- /dev/null +++ b/crates/universal-sierra-compiler-api/src/representation.rs @@ -0,0 +1,25 @@ +use cairo_lang_casm::hints::Hint; +use num_bigint::BigInt; +use serde::{Deserialize, Serialize}; + +pub type CasmCodeOffset = usize; +pub type CasmInstructionIdx = usize; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct RawCasmProgram { + pub assembled_cairo_program: AssembledCairoProgramWithSerde, + /// `debug_info[i]` contains the following information about the first CASM instruction that + /// was generated by the Sierra statement with + /// `StatementIdx(i)` (i-th index in Sierra statements vector): + /// - code offset in the CASM bytecode + /// - index in CASM instructions vector + /// + /// Those 2 values are usually not equal since the instruction sizes in CASM may vary + pub debug_info: Vec<(CasmCodeOffset, CasmInstructionIdx)>, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct AssembledCairoProgramWithSerde { + pub bytecode: Vec, + pub hints: Vec<(usize, Vec)>, +} diff --git a/docs/src/appendix/snforge/test.md b/docs/src/appendix/snforge/test.md index 32e7e078e4..8d183162b2 100644 --- a/docs/src/appendix/snforge/test.md +++ b/docs/src/appendix/snforge/test.md @@ -32,6 +32,14 @@ Available components: - `call-type` - `call-result` +## `--run-native` + +_This flag is only available if `snforge` was built with `cairo-native` flag_ + +Run contracts on [`cairo-native`](https://github.com/lambdaclass/cairo_native) instead of the default `cairo-vm`. + +Note: Only contracts execution through native is supported, test code itself will still run on `cairo-vm`. + ## `-e`, `--exact` Will only run a test with a name exactly matching the test filter. diff --git a/docs/src/development/environment-setup.md b/docs/src/development/environment-setup.md index 88aa6d572e..a4d1cc0175 100644 --- a/docs/src/development/environment-setup.md +++ b/docs/src/development/environment-setup.md @@ -36,9 +36,8 @@ about installing `cairo-profiler` [here](https://github.com/software-mansion/cai > ❗️ **Warning** > -> If you haven't pushed your branch to the remote yet (you've been working only locally), two tests will fail: +> If you haven't pushed your branch to the remote yet (you've been working only locally) some tests may fail, including: > -> - `e2e::running::init_new_project_test` > - `e2e::running::simple_package_with_git_dependency` > > After pushing the branch to the remote, those tests should pass. @@ -49,8 +48,6 @@ Install [starknet-devnet](https://github.com/0xSpaceShard/starknet-devnet) via [ ### Universal sierra compiler Install the latest [universal-sierra-compiler](https://github.com/software-mansion/universal-sierra-compiler) version. - - ## Running Tests Tests can be run with: @@ -58,6 +55,46 @@ Tests can be run with: $ cargo test ``` +## Cairo Native + +To develop Starknet Foundry with Cairo Native support, you need to enable the `cairo-native` feature in Cargo and +install the required dependencies. + +### LLVM + +LLVM 19 is required to build forge with Cairo Native support and to run it. + +#### macOS + +On macOS in can be installed with + +```shell +$ brew install llvm@19 +``` + +Next, export the following environment variables: + +``` +LIBRARY_PATH=/opt/homebrew/lib +MLIR_SYS_190_PREFIX="$(brew --prefix llvm@19)" +LLVM_SYS_191_PREFIX="$(brew --prefix llvm@19)" +TABLEGEN_190_PREFIX="$(brew --prefix llvm@19)" +``` + +#### Linux + +LLVM installation varies between distributions. +See [here](https://llvm.org/docs/GettingStarted.html) and [here](https://releases.llvm.org/download.html) for more +details. + +Next, export the following environment variables, note that the paths may differe depending on your distribution and +installation method: + +``` +MLIR_SYS_190_PREFIX=/usr/lib/llvm-19 +LLVM_SYS_191_PREFIX=/usr/lib/llvm-19 +TABLEGEN_190_PREFIX=/usr/lib/llvm-19 +``` ## Formatting and Lints @@ -91,13 +128,9 @@ $ typos Some typos can be automatically fixed by running -
-Output: - ```shell $ typos -w ``` -
## Contributing