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..101d2aa1b3 --- /dev/null +++ b/.github/workflows/_build-binaries-native.yml @@ -0,0 +1,111 @@ +# 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: + 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 f7c2ac3a26..c3d72c9451 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" @@ -4213,6 +4409,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" @@ -4247,6 +4453,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" @@ -4324,6 +4544,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" @@ -4400,6 +4646,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" @@ -4433,6 +4688,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" @@ -5139,6 +5404,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" @@ -5206,6 +5481,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" @@ -5552,6 +5848,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" @@ -5632,6 +5934,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" @@ -5955,10 +6266,12 @@ version = "1.0.0" dependencies = [ "anyhow", "assert_fs", + "cairo-native", "camino", "foundry-ui", "indoc", "itertools 0.14.0", + "native-api", "rayon", "regex", "scarb-metadata", @@ -5968,6 +6281,7 @@ dependencies = [ "serde_json", "shared", "thiserror 2.0.17", + "tracing", "universal-sierra-compiler-api", "which", ] @@ -6399,6 +6713,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" @@ -6779,6 +7103,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" @@ -6969,6 +7299,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" @@ -7391,6 +7733,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" @@ -7401,12 +7753,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]] @@ -7446,6 +7801,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" @@ -7572,6 +7933,18 @@ 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" @@ -7584,6 +7957,7 @@ dependencies = [ "serde_json", "shared", "tempfile", + "tracing", "which", ] diff --git a/Cargo.toml b/Cargo.toml index d74513d8a2..50c8914e00 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..e545486d92 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,19 @@ +[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/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..9aa3bccefb 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,9 +82,7 @@ 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 { 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..2adb08203e 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,22 @@ 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_json_string( + &contract_artifact.casm, + 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..7d267b30fb --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/call.rs @@ -0,0 +1,69 @@ +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; + +#[expect(clippy::mut_mut)] +#[allow(clippy::result_large_err)] +pub fn execute_inner_call( + syscall_handler_base: &mut SyscallHandlerBase, + cheatnet_state: &mut CheatnetState, + call: &mut CallEntryPoint, + remaining_gas: &mut u64, +) -> BaseSyscallResult> { + 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..4ca4b116b4 --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/deploy.rs @@ -0,0 +1,185 @@ +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, CallType, ConstructorContext, ConstructorEntryPointExecutionResult, + EntryPointExecutionContext, handle_empty_constructor, +}; +use blockifier::execution::errors::ConstructorEntryPointExecutionError; +use blockifier::execution::syscalls::syscall_base::SyscallHandlerBase; +use blockifier::execution::syscalls::vm_syscall_utils::SyscallSelector; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::State; +use starknet_api::contract_class::EntryPointType; +use starknet_api::core::{ClassHash, ContractAddress, calculate_contract_address}; +use starknet_api::transaction::fields::{Calldata, ContractAddressSalt}; + +// Copied from blockifer/src/execution/entry_point.rs +#[allow(clippy::result_large_err)] +#[expect(clippy::needless_pass_by_value)] +pub fn execute_constructor_entry_point( + state: &mut dyn State, + // region: Modified blockifer code + cheatnet_state: &mut CheatnetState, + // endregion + context: &mut EntryPointExecutionContext, + ctor_context: ConstructorContext, + calldata: Calldata, + remaining_gas: &mut u64, +) -> ConstructorEntryPointExecutionResult { + // Ensure the class is declared (by reading it). + let compiled_class = state + .get_compiled_class(ctor_context.class_hash) + .map_err(|error| { + ConstructorEntryPointExecutionError::new(error.into(), &ctor_context, None) + })?; + let Some(constructor_selector) = compiled_class.constructor_selector() else { + // region: Modified blockifer code + cheatnet_state + .trace_data + .add_deploy_without_constructor_node(); + // endregion + // Contract has no constructor. + return handle_empty_constructor( + compiled_class, + context, + &ctor_context, + calldata, + *remaining_gas, + ) + .map_err(|error| ConstructorEntryPointExecutionError::new(error, &ctor_context, None)); + }; + + let mut constructor_call = CallEntryPoint { + class_hash: None, + code_address: ctor_context.code_address, + entry_point_type: EntryPointType::Constructor, + entry_point_selector: constructor_selector, + calldata, + storage_address: ctor_context.storage_address, + caller_address: ctor_context.caller_address, + call_type: CallType::Call, + initial_gas: *remaining_gas, + }; + + // region: Modified blockifer code + let call_info = execute_call_entry_point( + &mut constructor_call, + state, + cheatnet_state, + context, + remaining_gas, + &ExecuteCallEntryPointExtraOptions { + trace_data_handled_by_revert_call: false, + }, + ) + .map_err(|error| { + ConstructorEntryPointExecutionError::new(error, &ctor_context, Some(constructor_selector)) + })?; + + Ok(call_info) + // endregion +} + +#[expect(clippy::result_large_err)] +// Copied from blockifer/src/execution/execution_utils.rs +fn execute_deployment( + state: &mut dyn State, + cheatnet_state: &mut CheatnetState, + context: &mut EntryPointExecutionContext, + ctor_context: ConstructorContext, + constructor_calldata: Calldata, + remaining_gas: &mut u64, +) -> ConstructorEntryPointExecutionResult { + // Address allocation in the state is done before calling the constructor, so that it is + // visible from it. + let deployed_contract_address = ctor_context.storage_address; + let current_class_hash = + state + .get_class_hash_at(deployed_contract_address) + .map_err(|error| { + ConstructorEntryPointExecutionError::new(error.into(), &ctor_context, None) + })?; + if current_class_hash != ClassHash::default() { + return Err(ConstructorEntryPointExecutionError::new( + StateError::UnavailableContractAddress(deployed_contract_address).into(), + &ctor_context, + None, + )); + } + + state + .set_class_hash_at(deployed_contract_address, ctor_context.class_hash) + .map_err(|error| { + ConstructorEntryPointExecutionError::new(error.into(), &ctor_context, None) + })?; + + execute_constructor_entry_point( + state, + cheatnet_state, + context, + ctor_context, + constructor_calldata, + remaining_gas, + ) +} + +#[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()); + // let versioned_constants = &syscall_handler_base + // .context + // .tx_context + // .block_context + // .versioned_constants; + // TODO(#3790) support for reject + // 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..fe2935d4c6 --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/execution.rs @@ -0,0 +1,221 @@ +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(), + }) +} + +// Copied from blockifier +// todo(rodrigo): add an `entry point not found` test for Native +#[allow(clippy::result_large_err)] +pub fn execute_entry_point_call( + call: &ExecutableCallEntryPoint, + compiled_class: &NativeCompiledClassV1, + // state: &mut dyn State, + // context: &mut EntryPointExecutionContext, + syscall_handler: &mut CheatableNativeSyscallHandler, +) -> EntryPointExecutionResult { + let entry_point = compiled_class.get_entry_point(&call.type_and_selector())?; + + // let mut syscall_handler: NativeSyscallHandler<'_> = + // NativeSyscallHandler::new(call, state, context); + + let gas_costs = &syscall_handler + .native_syscall_handler + .base + .context + .gas_costs(); + let builtin_costs = BuiltinCosts { + // todo(rodrigo): Unsure of what value `const` means, but 1 is the right value. + 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 blockifier +#[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..deb43a311d --- /dev/null +++ b/crates/cheatnet/src/runtime_extensions/native/native_syscall_handler.rs @@ -0,0 +1,733 @@ +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() + } + + /// 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(()) + } + + // Copied from blockifer/src/exection/native/syscall_handler.rs + #[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(); + // region: Modified blockifier code + 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, + ) + }, + )), + ) + })?; + // endregion + Ok(Retdata(raw_data)) + } + + 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) impl 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, + }) + } + + 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)?; + + 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, + ) + .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) + } + + // Copied from blockifier/src/execution/native/syscall_handler.rs + 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) + } + + // Copied from blockifier/src/execution/native/syscall_handler.rs + 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) + } + + 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..f466c5d66e 100644 --- a/crates/cheatnet/tests/common/mod.rs +++ b/crates/cheatnet/tests/common/mod.rs @@ -29,7 +29,8 @@ use runtime::starknet::constants::TEST_ADDRESS; use runtime::starknet::context::build_context; use scarb_api::metadata::MetadataCommandExt; use scarb_api::{ - ScarbCommand, get_contracts_artifacts_and_source_sierra_paths, target_dir_for_workspace, + CompilationOpts, ScarbCommand, 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; @@ -85,8 +86,17 @@ pub fn get_contracts() -> ContractsData { 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/forge-runner/src/running/config_run.rs b/crates/forge-runner/src/running/config_run.rs index e9ee61cd1f..e25afc02ea 100644 --- a/crates/forge-runner/src/running/config_run.rs +++ b/crates/forge-runner/src/running/config_run.rs @@ -58,6 +58,7 @@ impl StateReader for PhantomStateReader { } } +#[tracing::instrument(skip_all, level = "debug")] pub fn run_config_pass( test_details: &TestDetails, casm_program: &AssembledProgramWithDebugInfo, diff --git a/crates/forge-runner/src/running/with_config.rs b/crates/forge-runner/src/running/with_config.rs index 2789fd8588..145881a991 100644 --- a/crates/forge-runner/src/running/with_config.rs +++ b/crates/forge-runner/src/running/with_config.rs @@ -88,6 +88,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/lib.rs b/crates/forge/src/lib.rs index 477f94a565..9e5067ed89 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -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. @@ -215,6 +225,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)] @@ -287,7 +312,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() 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 c41248491f..8a1d83d375 100644 --- a/crates/forge/src/run_tests/package.rs +++ b/crates/forge/src/run_tests/package.rs @@ -31,7 +31,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; @@ -83,11 +83,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/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/runner.rs b/crates/forge/test_utils/src/runner.rs index afa323338f..cffe5bc8f2 100644 --- a/crates/forge/test_utils/src/runner.rs +++ b/crates/forge/test_utils/src/runner.rs @@ -17,8 +17,9 @@ use forge_runner::{ use foundry_ui::UI; use indoc::formatdoc; use scarb_api::{ - ScarbCommand, StarknetContractArtifacts, get_contracts_artifacts_and_source_sierra_paths, - metadata::MetadataCommandExt, target_dir_for_workspace, + CompilationOpts, ScarbCommand, StarknetContractArtifacts, + get_contracts_artifacts_and_source_sierra_paths, metadata::MetadataCommandExt, + 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"); @@ -104,14 +105,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 +225,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 c1609ab26c..5047677194 100644 --- a/crates/forge/test_utils/src/running_tests.rs +++ b/crates/forge/test_utils/src/running_tests.rs @@ -45,11 +45,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..f446ddfdc7 100644 --- a/crates/forge/tests/e2e/clean.rs +++ b/crates/forge/tests/e2e/clean.rs @@ -21,6 +21,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 +58,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 +126,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 +164,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); 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/fuzzing.rs b/crates/forge/tests/e2e/fuzzing.rs index 581a396512..f9159220d9 100644 --- a/crates/forge/tests/e2e/fuzzing.rs +++ b/crates/forge/tests/e2e/fuzzing.rs @@ -285,7 +285,7 @@ fn generate_arg_cheatcode() { Failure data: "`generate_arg` cheatcode: `min_value` must be <= `max_value`, provided values after deserialization: 101 and 100" - [PASS] fuzzing_integrationtest::generate_arg::use_generate_arg_outside_fuzzer (l1_gas: ~0, l1_data_gas: ~0, l2_gas: ~40000) + [PASS] fuzzing_integrationtest::generate_arg::use_generate_arg_outside_fuzzer [..] Tests: 1 passed, 1 failed, 0 ignored, 23 filtered out "#}, ); 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/running.rs b/crates/forge/tests/e2e/running.rs index 0a807c53c0..7e5fd0024b 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() { 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/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..223046bd01 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,6 +22,9 @@ 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 } +native-api = { path = "../native-api", optional = true } +tracing.workspace = true [dev-dependencies] assert_fs.workspace = true diff --git a/crates/scarb-api/src/artifacts.rs b/crates/scarb-api/src/artifacts.rs index e20b5ba32d..2a9cb27cd5 100644 --- a/crates/scarb-api/src/artifacts.rs +++ b/crates/scarb-api/src/artifacts.rs @@ -1,6 +1,8 @@ use anyhow::Result; use crate::artifacts::representation::StarknetArtifactsRepresentation; +#[cfg(feature = "cairo-native")] +use cairo_native::executor::AotContractExecutor; use camino::{Utf8Path, Utf8PathBuf}; use itertools::Itertools; use rayon::iter::{IntoParallelIterator, ParallelIterator}; @@ -12,18 +14,35 @@ 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, + + #[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 +50,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 +80,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_sierra_at_path(path, &SierraType::Contract)?; + + #[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,29 +145,13 @@ 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 camino::Utf8PathBuf; use deserialized::{StarknetArtifacts, StarknetContract, StarknetContractArtifactPaths}; @@ -109,6 +166,8 @@ mod tests { StarknetContractArtifacts { sierra: "sierra1".to_string(), casm: "casm1".to_string(), + #[cfg(feature = "cairo-native")] + executor: None, }, Utf8PathBuf::from("path1"), ), @@ -152,9 +211,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 +254,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/lib.rs b/crates/scarb-api/src/lib.rs index 0cf9f7f627..57bf25dbcd 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()) @@ -577,8 +593,12 @@ mod tests { let contracts = get_contracts_artifacts_and_source_sierra_paths( target_dir.as_path(), package, - false, &ui, + CompilationOpts { + use_test_target_contracts: false, + #[cfg(feature = "cairo-native")] + run_native: true, + }, ) .unwrap(); 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 c3982de862..bd4f64c0cd 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..d049da1136 100644 --- a/crates/sncast/src/helpers/scarb_utils.rs +++ b/crates/sncast/src/helpers/scarb_utils.rs @@ -1,8 +1,9 @@ +use crate::helpers::artifacts::CastStarknetContractArtifacts; use anyhow::{Context, Result, anyhow}; use camino::{Utf8Path, Utf8PathBuf}; use foundry_ui::{UI, components::warning::WarningMessage}; use scarb_api::{ - ScarbCommand, ScarbCommandError, StarknetContractArtifacts, + CompilationOpts, ScarbCommand, ScarbCommandError, get_contracts_artifacts_and_source_sierra_paths, metadata::{Metadata, MetadataCommand, PackageMetadata}, target_dir_for_workspace, @@ -157,7 +158,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 +171,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: artifacts.casm })) .collect()) } else { let profile = &config.profile; @@ -184,11 +185,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: artifacts.casm })) .collect()) } } diff --git a/crates/sncast/src/starknet_commands/declare.rs b/crates/sncast/src/starknet_commands/declare.rs index 17161d623e..6a0f5a813c 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::{ @@ -51,7 +51,7 @@ pub struct Declare { pub async fn declare( declare: &Declare, 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/script/run.rs b/crates/sncast/src/starknet_commands/script/run.rs index 619979c06f..6f83abbf62 100644 --- a/crates/sncast/src/starknet_commands/script/run.rs +++ b/crates/sncast/src/starknet_commands/script/run.rs @@ -32,12 +32,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}; @@ -59,8 +60,6 @@ use tokio::runtime::Runtime; mod script_runtime; -type ScriptStarknetContractArtifacts = StarknetContractArtifacts; - #[derive(Args, Debug)] #[command(about = "Execute a deployment script")] pub struct Run { @@ -84,7 +83,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, } @@ -282,7 +281,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, @@ -452,8 +451,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 @@ -463,7 +462,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 ac961d7f8e..76efef613b 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::{class_hash::ClassHashResponse, errors::StarknetCommandError}, @@ -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 2d18d43aec..a971bba035 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/universal-sierra-compiler-api/Cargo.toml b/crates/universal-sierra-compiler-api/Cargo.toml index 72210181ad..dd6ea2715a 100644 --- a/crates/universal-sierra-compiler-api/Cargo.toml +++ b/crates/universal-sierra-compiler-api/Cargo.toml @@ -13,3 +13,4 @@ tempfile.workspace = true num-bigint.workspace = true cairo-lang-casm.workspace = true camino.workspace = true +tracing.workspace = true diff --git a/crates/universal-sierra-compiler-api/src/lib.rs b/crates/universal-sierra-compiler-api/src/lib.rs index a75f95bb66..eeb61753d2 100644 --- a/crates/universal-sierra-compiler-api/src/lib.rs +++ b/crates/universal-sierra-compiler-api/src/lib.rs @@ -66,6 +66,7 @@ pub fn compile_sierra( ) } +#[tracing::instrument(skip_all, level = "debug")] pub fn compile_sierra_at_path( sierra_file_path: &Utf8Path, sierra_type: &SierraType, diff --git a/docs/src/appendix/snforge/test.md b/docs/src/appendix/snforge/test.md index ca0709615b..968a830a1a 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.