diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e2e2ea7ef..ec466f2fa1 100755 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,15 +42,20 @@ commands: - run: name: Install Rust command: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - source "$HOME/.cargo/env" - rustup install 1.82.0 - rustup override set 1.82.0 - cargo --version --verbose - rustc --version + # If Rust is not installed on the machine, install it + if ! command -v rustc &> /dev/null; then + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" + rustup install 1.82.0 + rustup override set 1.82.0 + cargo --version --verbose + rustc --version + fi if [ ! -f "Cargo.lock" ]; then cargo generate-lockfile fi + cargo install cargo-mtime + install-rust-windows: @@ -71,6 +76,7 @@ commands: if (!(Test-Path "Cargo.lock" -PathType Leaf)) { cargo generate-lockfile } + cargo install cargo-mtime build-and-test: description: "Build and run tests" @@ -79,12 +85,14 @@ commands: name: Build no_output_timeout: 30m command: | + cargo-mtime . ~/.cache/mtimes/project.db cargo test --no-run --all --locked --profile ci --features only_testnet - run: name: Run tests no_output_timeout: 30m # The `--verbose` flag is used to check which files are being recompiled. Ideally, this should be none. command: | + cargo-mtime . ~/.cache/mtimes/project.db cargo test --all --locked --profile ci --features only_testnet --verbose install_rust_nightly: @@ -144,6 +152,7 @@ jobs: - cargo-v1-{{ arch }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }} - cargo-v1-{{ arch }}-{{ checksum "Cargo.toml" }} - cargo-v1-{{ arch }} + - install-rust - run: name: Update Submodules command: git submodule update --init --recursive diff --git a/.circleci/leo-example.sh b/.circleci/leo-example.sh index 2e794f8121..8fbce490c0 100755 --- a/.circleci/leo-example.sh +++ b/.circleci/leo-example.sh @@ -1,3 +1,9 @@ +# Alias the leo command to use the local binary. +# Note: Use a full path for $LEO when running locally. +leo() { + $LEO "$@" +} + ( # Create a new Leo lottery example program. $LEO example lottery || exit diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 1e31031cbb..4a2ae1f647 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -55,7 +55,7 @@ jobs: make install DESTDIR=../../kcov-build cd ../.. rm -rf kcov-master - for file in target/debug/deps/*-*; do if [[ "$file" != *\.* ]]; then mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --exclude-region='@kcov_skip(start):@kcov_skip(end)' --verify "target/cov/$(basename $file)" "$file"; fi done + for file in target/ci/deps/*-*; do if [[ "$file" != *\.* ]]; then mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --exclude-region='@kcov_skip(start):@kcov_skip(end)' --verify "target/cov/$(basename $file)" "$file"; fi done - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bfcba93e2..d50739d62f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,15 @@ name: Leo Release + on: - push: - tags: - - 'v*.*.*' workflow_dispatch: + inputs: + tag: + description: "The release tag (e.g., v1.0.0)." + required: true + release_flag: + description: "Set to true to upload the release, false to skip." + required: false + default: "false" env: RUST_BACKTRACE: 0 @@ -13,29 +19,36 @@ jobs: name: Ubuntu runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout Specific Tag uses: actions/checkout@v2 with: - submodules: true + ref: ${{ github.event.inputs.tag }} + fetch-depth: 0 + submodules: recursive - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: stable - override: true components: rustfmt + - name: Verify Cargo.lock Exists + run: | + if [ ! -f Cargo.lock ]; then + echo "Error: Cargo.lock file is missing. Aborting build." + exit 1 + fi + - name: Build Leo run: | - cargo build --package leo-lang --release --features noconfig && strip target/release/leo + cargo build --package leo-lang --release --locked --features noconfig && strip target/release/leo env: CARGO_NET_GIT_FETCH_WITH_CLI: true - id: get_version uses: battila7/get-version-action@v2 - - name: Zip + - name: Zip Artifact run: | mkdir tempdir mv target/release/leo tempdir @@ -46,8 +59,9 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event.inputs.release_flag == 'true' }} with: + tag_name: ${{ github.event.inputs.tag }} files: | leo-${{ steps.get_version.outputs.version }}-x86_64-unknown-linux-gnu.zip env: @@ -57,26 +71,49 @@ jobs: name: Linux musl runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout Specific Tag + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.tag }} + fetch-depth: 0 + submodules: recursive - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: stable - override: true components: rustfmt target: x86_64-unknown-linux-musl - default: stable-x86_64-unknown-linux-musl + + - name: Verify Cargo.lock Exists + run: | + if [ ! -f Cargo.lock ]; then + echo "Error: Cargo.lock file is missing. Aborting build." + exit 1 + fi - name: Build run: | docker pull clux/muslrust:stable - docker run -v cargo-cache:/root/.cargo/registry -v $PWD:/volume --rm -t clux/muslrust:stable cargo build --target x86_64-unknown-linux-musl --package leo-lang --release --features noconfig && ldd target/x86_64-unknown-linux-musl/release/leo + docker run \ + -v ${{ github.workspace }}:/volume \ + -v cargo-cache:/root/.cargo/registry \ + --rm \ + -t clux/muslrust:stable \ + bash -c " + rustup install stable && + rustup default stable && + rustup target add x86_64-unknown-linux-musl && + cargo build --target x86_64-unknown-linux-musl --package leo-lang --release --locked --features noconfig + " + + - name: Check Binary + run: file target/x86_64-unknown-linux-musl/release/leo + - id: get_version uses: battila7/get-version-action@v2 - - name: Zip + - name: Zip Artifact run: | mkdir tempdir cp target/x86_64-unknown-linux-musl/release/leo tempdir @@ -88,8 +125,9 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event.inputs.release_flag == 'true' }} with: + tag_name: ${{ github.event.inputs.tag }} files: | leo-${{ steps.get_version.outputs.version }}-x86_64-unknown-linux-musl.zip env: @@ -99,29 +137,36 @@ jobs: name: macOS runs-on: macos-13 steps: - - name: Checkout + - name: Checkout Specific Tag uses: actions/checkout@v2 with: - submodules: true + ref: ${{ github.event.inputs.tag }} + fetch-depth: 0 + submodules: recursive - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: stable - override: true components: rustfmt + - name: Verify Cargo.lock Exists + run: | + if [ ! -f Cargo.lock ]; then + echo "Error: Cargo.lock file is missing. Aborting build." + exit 1 + fi + - name: Build Leo run: | - cargo build --package leo-lang --release && strip target/release/leo + cargo build --package leo-lang --release --locked && strip target/release/leo env: CARGO_NET_GIT_FETCH_WITH_CLI: true - id: get_version uses: battila7/get-version-action@v2 - - name: Zip + - name: Zip Artifact run: | mkdir tempdir mv target/release/leo tempdir @@ -132,8 +177,9 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event.inputs.release_flag == 'true' }} with: + tag_name: ${{ github.event.inputs.tag }} files: | leo-${{ steps.get_version.outputs.version }}-x86_64-apple-darwin.zip env: @@ -148,32 +194,42 @@ jobs: with: xcode-version: latest-stable - - name: Checkout + - name: Checkout Specific Tag uses: actions/checkout@v2 with: - submodules: true + ref: ${{ github.event.inputs.tag }} + fetch-depth: 0 + submodules: recursive - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: + toolchain: stable profile: minimal target: aarch64-apple-darwin - toolchain: stable - override: true components: rustfmt + + - name: Verify Cargo.lock Exists + run: | + if [ ! -f Cargo.lock ]; then + echo "Error: Cargo.lock file is missing. Aborting build." + exit 1 + fi + - name: Build Leo run: | - SDKROOT=$(xcrun -sdk macosx --show-sdk-path) \ - MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version) \ - cargo build -p leo-lang --release --target=aarch64-apple-darwin --features noconfig && strip target/aarch64-apple-darwin/release/leo + SDKROOT=$(xcrun --sdk macosx --show-sdk-path) \ + MACOSX_DEPLOYMENT_TARGET=$(xcrun --sdk macosx --show-sdk-platform-version) \ + cargo build --package leo-lang --release --locked --target=aarch64-apple-darwin --features noconfig + strip target/aarch64-apple-darwin/release/leo env: CARGO_NET_GIT_FETCH_WITH_CLI: true - id: get_version uses: battila7/get-version-action@v2 - - name: Zip + - name: Zip Artifact run: | mkdir tempdir mv target/aarch64-apple-darwin/release/leo tempdir @@ -184,67 +240,74 @@ jobs: mv tempdir/leo-${{ steps.get_version.outputs.version }}-aarch64-apple-darwin.zip . mv tempdir/leo.zip . - - name: Release macos m1 version + - name: Release macOS M1 Version uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event.inputs.release_flag == 'true' }} with: + tag_name: ${{ github.event.inputs.tag }} files: | leo-${{ steps.get_version.outputs.version }}-aarch64-apple-darwin.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Release leo.zip + - name: Release Universal Version uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event.inputs.release_flag == 'true' }} with: + tag_name: ${{ github.event.inputs.tag }} files: | leo.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + windows: name: Windows runs-on: windows-latest steps: - - name: Checkout + - name: Checkout Specific Tag uses: actions/checkout@v2 with: - submodules: true + ref: ${{ github.event.inputs.tag }} + fetch-depth: 0 + submodules: recursive - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: stable - override: true components: rustfmt - name: Install LLVM and Clang uses: KyleMayes/install-llvm-action@v1 with: - version: "11" + version: "11" # Specify the LLVM/Clang version you need directory: ${{ runner.temp }}/llvm - name: Set LIBCLANG_PATH - run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV + run: echo "LIBCLANG_PATH=${{ runner.temp }}/llvm/lib" >> $GITHUB_ENV + + - name: Verify Cargo.lock Exists + run: | + if (!(Test-Path "Cargo.lock")) { + Write-Host "Error: Cargo.lock file is missing. Aborting build." + exit 1 + } - name: Build Leo run: | - cargo build --package leo-lang --release --features noconfig + cargo build --package leo-lang --release --locked --features noconfig env: CARGO_NET_GIT_FETCH_WITH_CLI: true - id: get_version uses: battila7/get-version-action@v2 - - name: Zip + - name: Zip Artifact run: | Compress-Archive target/release/leo.exe leo-${{ steps.get_version.outputs.version }}-x86_64-pc-windows-msvc.zip - name: Release uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') + if: ${{ github.event.inputs.release_flag == 'true' }} with: + tag_name: ${{ github.event.inputs.tag }} files: | leo-${{ steps.get_version.outputs.version }}-x86_64-pc-windows-msvc.zip env: diff --git a/.gitignore b/.gitignore index 53a9838170..943e10b577 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/target /tmp/ +/temp/ **.idea/ *.DS_Store .vscode @@ -23,4 +24,4 @@ sccache*/ *.bat # environment -.env \ No newline at end of file +.env diff --git a/.resources/release-version b/.resources/release-version index a6316f06bb..02b6b4fd7a 100644 --- a/.resources/release-version +++ b/.resources/release-version @@ -1 +1 @@ -v2.3.0 \ No newline at end of file +v2.4.1 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bd88612d94..fb6f362ebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,12 +362,27 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.98" @@ -482,6 +497,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.8" @@ -491,7 +520,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.12", "windows-sys 0.52.0", ] @@ -700,6 +729,41 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote 1.0.36", + "strsim", + "syn 2.0.82", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote 1.0.36", + "syn 2.0.82", +] + [[package]] name = "der" version = "0.7.9" @@ -754,6 +818,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -1079,7 +1149,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width", + "unicode-width 0.1.12", ] [[package]] @@ -1365,6 +1435,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1407,7 +1483,27 @@ dependencies = [ "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.1.12", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote 1.0.36", + "syn 2.0.82", ] [[package]] @@ -1492,7 +1588,7 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leo-abnf" -version = "2.3.0" +version = "2.4.1" dependencies = [ "abnf", "anyhow", @@ -1500,7 +1596,7 @@ dependencies = [ [[package]] name = "leo-ast" -version = "2.3.0" +version = "2.4.1" dependencies = [ "criterion", "indexmap 2.6.0", @@ -1514,7 +1610,7 @@ dependencies = [ [[package]] name = "leo-compiler" -version = "2.3.0" +version = "2.4.1" dependencies = [ "aleo-std-storage", "indexmap 2.6.0", @@ -1538,7 +1634,7 @@ dependencies = [ [[package]] name = "leo-disassembler" -version = "2.3.0" +version = "2.4.1" dependencies = [ "leo-ast", "leo-errors", @@ -1548,7 +1644,7 @@ dependencies = [ [[package]] name = "leo-errors" -version = "2.3.0" +version = "2.4.1" dependencies = [ "anyhow", "backtrace", @@ -1561,9 +1657,36 @@ dependencies = [ "thiserror", ] +[[package]] +name = "leo-interpreter" +version = "2.4.1" +dependencies = [ + "colored", + "crossterm", + "dialoguer", + "indexmap 2.6.0", + "leo-ast", + "leo-errors", + "leo-package", + "leo-parser", + "leo-passes", + "leo-span", + "leo-test-framework", + "rand", + "rand_chacha", + "ratatui", + "serial_test", + "snarkvm", + "snarkvm-circuit", + "snarkvm-synthesizer-program", + "tempfile", + "toml 0.8.19", + "tui-input", +] + [[package]] name = "leo-lang" -version = "2.3.0" +version = "2.4.1" dependencies = [ "aleo-std", "ansi_term", @@ -1576,11 +1699,14 @@ dependencies = [ "indexmap 2.6.0", "leo-ast", "leo-compiler", + "leo-disassembler", "leo-errors", + "leo-interpreter", "leo-package", "leo-retriever", "leo-span", "num-format", + "num_cpus", "rand", "rand_chacha", "rpassword", @@ -1602,7 +1728,7 @@ dependencies = [ [[package]] name = "leo-package" -version = "2.3.0" +version = "2.4.1" dependencies = [ "leo-errors", "leo-retriever", @@ -1614,7 +1740,7 @@ dependencies = [ [[package]] name = "leo-parser" -version = "2.3.0" +version = "2.4.1" dependencies = [ "clap", "indexmap 2.6.0", @@ -1630,7 +1756,7 @@ dependencies = [ [[package]] name = "leo-passes" -version = "2.3.0" +version = "2.4.1" dependencies = [ "indexmap 2.6.0", "itertools 0.13.0", @@ -1645,7 +1771,7 @@ dependencies = [ [[package]] name = "leo-retriever" -version = "2.3.0" +version = "2.4.1" dependencies = [ "aleo-std", "indexmap 2.6.0", @@ -1665,7 +1791,7 @@ dependencies = [ [[package]] name = "leo-span" -version = "2.3.0" +version = "2.4.1" dependencies = [ "fxhash", "indexmap 2.6.0", @@ -1675,7 +1801,7 @@ dependencies = [ [[package]] name = "leo-test-framework" -version = "2.3.0" +version = "2.4.1" dependencies = [ "backtrace", "clap", @@ -2130,11 +2256,21 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2202,6 +2338,27 @@ dependencies = [ "rand_core", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "rayon" version = "1.10.0" @@ -2470,6 +2627,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "rusty-hook" version = "0.11.2" @@ -2789,9 +2952,9 @@ dependencies = [ [[package]] name = "snarkvm" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1061bdbbe9f6b7eff608380fc2bc27c511f180915c1b6bd9ab3c43f848392a52" +checksum = "3a2afaf734585b42090147ce2aae7ce90a3e5f10b452d4efbcd47d2e4ca26969" dependencies = [ "anstyle", "anyhow", @@ -2819,9 +2982,9 @@ dependencies = [ [[package]] name = "snarkvm-algorithms" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4c2cb2fb5fe072253de93ff27a27d02d9f6fa8aef15b8399d9aae03fa106f" +checksum = "59d91040c5a51a3c335741aa4fbb8176a612d44760f040c7d9d5833653c6691e" dependencies = [ "aleo-std", "anyhow", @@ -2850,9 +3013,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67c386f93ffbb7fbeba46a630bb837d5aa091173cc6ea46600cf7d4bf201b8e" +checksum = "8208642e213e2e20a07d7ceb7facb2e080be99714d55d2ebc67dcdbd6b4aef8b" dependencies = [ "snarkvm-circuit-account", "snarkvm-circuit-algorithms", @@ -2865,9 +3028,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-account" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f69da527dfb8a06b363fd9acae69d6365aa85073256bb15ef5ad339cb034bd5d" +checksum = "ca4758873037211d81f1d40bfdd6ae1904aabb7298a4217ff83baaddca7f70b7" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-network", @@ -2877,9 +3040,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-algorithms" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f8627c5f44cc7f4c4678600695f3f47ec2f8eb9f4f8393bb17f4c852fd314bb" +checksum = "eec8ca5e7f6b08e6854e31be1c1207cf6ca26da64f91912d264b0752b14f51cf" dependencies = [ "snarkvm-circuit-types", "snarkvm-console-algorithms", @@ -2888,9 +3051,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-collections" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "362fcfa5fe6b4d33390789d29f3319911c84defd1dea2743c4519b293f58fecc" +checksum = "8a709a676a90ce395b811d138698995b515ecc39186772ff7f3c3bb1e1654fbc" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-types", @@ -2899,9 +3062,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cddfa1c833dc94ce9b7535b85ed81cc5a34cf2561f1af4c2f6f5c528b26432e6" +checksum = "789c270e95ca1707e3f7d7bd0cc6e90a144cc3a502fe08afde553d6ac0bba9b1" dependencies = [ "indexmap 2.6.0", "itertools 0.11.0", @@ -2918,15 +3081,15 @@ dependencies = [ [[package]] name = "snarkvm-circuit-environment-witness" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4065b220b837d3f4bd96f35936c6a2933e19a2f699e636b0df3d8181f51a4c67" +checksum = "e6ad62fa4ac3a96440d5ebbda604d9cadaf89d518ecd9b062538213820f37c45" [[package]] name = "snarkvm-circuit-network" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9d254a30e2845428af200f95158884faf72728aa6b1de46ac2d53af74dfa884" +checksum = "cb397309d0c2ddae4fa21fe28b676801e1b4ac352c51c19d7f5fb5f71d968b2d" dependencies = [ "snarkvm-circuit-algorithms", "snarkvm-circuit-collections", @@ -2936,9 +3099,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-program" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3691543e4c0baa34ed530b12e0f301fa230992f612e1fc063cb43a3c7852f2cf" +checksum = "5c32c9e1d5f97212a8f78083b73141d38ee65b120fbd30494fbb76829cb41414" dependencies = [ "paste", "snarkvm-circuit-account", @@ -2952,9 +3115,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cea90b7bc5e098dcbd1bd9d728501edc85a87cbd739c2bc635562529cb1cd03" +checksum = "6aff3706d5b770254d967c4771e684424f2440d774945c671fa45883eb5d59e0" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-address", @@ -2968,9 +3131,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-address" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5715abd9e1fb0a56e79f6ff6db1e9b8e87729478f7598958b122ef1b3ce88249" +checksum = "0973cefa284be203c7b6e8f408d8115a8520fc10fa0e717c2560fca2ee1f07af" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -2982,9 +3145,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-boolean" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a324ea89623793473a14358ca1fa141264f74462c950a29465f104415bff677" +checksum = "a7137bc6814f983597d6f50d2f88e7dce90237f34586526fbc8d77f841b5455b" dependencies = [ "snarkvm-circuit-environment", "snarkvm-console-types-boolean", @@ -2992,9 +3155,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-field" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234e943a1ee4add148e6cbff71c10e01fd455f38a96f447c4477385d3eb87747" +checksum = "1ee531ed89f191a5714684e65c7c271eb1af87bec71abbbc9928e325c0e80674" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -3003,9 +3166,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-group" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b5bf2e2feb77d7397a298776e18d03cf8dadb466bdc277fb74ff99b7957de6" +checksum = "2696c6891bf303cfe71ead8378d98f7faea98c76311ef3d15c85191598ddd481" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -3016,9 +3179,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-integers" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97574f286199b541a10534786e50c35ac2fe0f253e8bfc77b7845ddece4793a9" +checksum = "85f70807c61d48cabe62b59e5e93fcbb107b62835176c2fe10dcd7a98d02ce15" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -3029,9 +3192,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-scalar" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a510f4c84e8008ad4428a57b59655c4039c65ee8078e486c144d30ab1e610c" +checksum = "aba07230428833ac7eecd478f18baa6370b1eb68fe1f1ef93fc3e6c37312e8b6" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -3041,9 +3204,9 @@ dependencies = [ [[package]] name = "snarkvm-circuit-types-string" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6bc5a0408577eb7f189aa046f5ac8ee4b8aa7bb966dfa81c68d8cea026c5a25" +checksum = "30fb5a0a45eb4982902325912636a82e601b002fadff3d8415e489129c6a846c" dependencies = [ "snarkvm-circuit-environment", "snarkvm-circuit-types-boolean", @@ -3054,9 +3217,9 @@ dependencies = [ [[package]] name = "snarkvm-console" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed74b1e7be553d4fdd80927c6d12d30b61ad28314effc365e1ba91949926c655" +checksum = "157d8026f2ad796f03a4d41753a9cf2496625d829bb785020e83c2d5554e246a" dependencies = [ "snarkvm-console-account", "snarkvm-console-algorithms", @@ -3068,9 +3231,9 @@ dependencies = [ [[package]] name = "snarkvm-console-account" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfcfc296f0400d2625c0ef610e1eb48a12867c56d632650a62ae2e26fde5a9a" +checksum = "909aeac3e1fd4e0f4e56b0ed74d590b9c1a4ff9ab6144fb9b6b81483f0705b99" dependencies = [ "bs58", "snarkvm-console-network", @@ -3080,9 +3243,9 @@ dependencies = [ [[package]] name = "snarkvm-console-algorithms" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa04a8fe25fbc2158d4f3abaff90b1ed2448eae85a5fa97725b6d2ff9ce712e" +checksum = "7271e3a4e79b8e23a2461695b84e7d22e329b2089b9b05365627f9e6f3e17cb8" dependencies = [ "blake2s_simd", "smallvec", @@ -3094,9 +3257,9 @@ dependencies = [ [[package]] name = "snarkvm-console-collections" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aca93893f6d697f0f1938b114f0d4538c5fab2b131ba508fe543ecae4097042a" +checksum = "2b330afcf51263442b406b260b18d04b91ce395133390290c637ca79c38973b8" dependencies = [ "aleo-std", "rayon", @@ -3106,9 +3269,9 @@ dependencies = [ [[package]] name = "snarkvm-console-network" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90917fbec1e94134eb73c318672fa13e13e7dbd9813408bfdf142040cd054deb" +checksum = "63d8154a1456f6e4d0ff458a1107d98c596e095a5ef1fd019cbcfa4ea2e6bf6f" dependencies = [ "anyhow", "indexmap 2.6.0", @@ -3130,9 +3293,9 @@ dependencies = [ [[package]] name = "snarkvm-console-network-environment" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a0ad6b84cec66a885a88552178767b1a781309f3a4fe8d2f4fa840fb151091" +checksum = "20fd9b2abd65743d04f45533941af710fece03d6ab6adf577000cfbaeb845f32" dependencies = [ "anyhow", "bech32", @@ -3149,9 +3312,9 @@ dependencies = [ [[package]] name = "snarkvm-console-program" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a655d52f38795d6f037a1f76d3f0dbd3f835cb34fbb6918cdea6362aa6a9e020" +checksum = "7340c6fb347209fe846765b1b2911c63f72c788615f6150c3c588c0b8195a410" dependencies = [ "enum-iterator", "enum_index", @@ -3172,9 +3335,9 @@ dependencies = [ [[package]] name = "snarkvm-console-types" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3128d85a6fbd3970f9d4f5877007c2b9a80ed8eea647f5079733f6b4ba93c6" +checksum = "d2d66f03c46d92eaf59d18451fad067728e0a5e17618c2447cd82969294c0221" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-address", @@ -3188,9 +3351,9 @@ dependencies = [ [[package]] name = "snarkvm-console-types-address" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28905bc383be6c2e0ae8c0f0a02f40b0fa6642fa2e166bac8a32c7d75dd8163" +checksum = "7c9cd4cd1f8c2f9129a25d51922a5b1ce6a79719202435c24579998b0e2794ee" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -3200,18 +3363,18 @@ dependencies = [ [[package]] name = "snarkvm-console-types-boolean" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d8ac49336d82e4ed1d7d90f0403a917feea00d84951963c4799f8d961acd63" +checksum = "909a71996dd113a07e748d818145223af9893d4c2c45a9fc165b8426f97a0571" dependencies = [ "snarkvm-console-network-environment", ] [[package]] name = "snarkvm-console-types-field" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9267b908876d5d29f011db164398765cc66a8d086e5de6fcd5ddc5f120752e2" +checksum = "edf76e7ef6c508d01a8a1b2a1340262127eb48253984ac1aca98bdb4f43c662f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -3220,9 +3383,9 @@ dependencies = [ [[package]] name = "snarkvm-console-types-group" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d667d3f4afe1e6692dac476f6ead3d2739ed60c9ef0f70db7767fa010e99928" +checksum = "223bcd81ed18834c042dcf077f71805f28fcb976549b35f8ca47b52f39fe317f" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -3232,9 +3395,9 @@ dependencies = [ [[package]] name = "snarkvm-console-types-integers" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5832e7aecd318968406deffc0cea4e794f6902465c69771a997fe733124920" +checksum = "08a392d380a382749b75225552ec27c86b5f33adea11dd7a1774c8927781aeca" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -3244,9 +3407,9 @@ dependencies = [ [[package]] name = "snarkvm-console-types-scalar" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89d7d37cdc1efb57dc0baaeb950a01532761d42d645c986202206d4493d2fa6" +checksum = "d7b7d745b3c27eee7dd4d3a8278341617c9aa10783c7919e3eb3efe7084e0853" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -3256,9 +3419,9 @@ dependencies = [ [[package]] name = "snarkvm-console-types-string" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dec34afefe1cb78a77d11f494cd654701b2b0b169c6aef8131a156b2b9db96d" +checksum = "98f1048223dc7839e19a2fb60666801fd0fefdcb5a5987394ffe7603f928bc45" dependencies = [ "snarkvm-console-network-environment", "snarkvm-console-types-boolean", @@ -3268,9 +3431,9 @@ dependencies = [ [[package]] name = "snarkvm-curves" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d5c7963b24dc45ec0663ded89bdf05986bf61c339b104c64d37622143c4cc13" +checksum = "02bbd49b827182dd51c3054ec04528ab4eebb61883f0e7d8efeeb53800f684e5" dependencies = [ "rand", "rayon", @@ -3283,9 +3446,9 @@ dependencies = [ [[package]] name = "snarkvm-fields" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e8966ab9ec5300b20285d9054521785b63a95b501323bad8216066a90015ae" +checksum = "942b47d900853fbebdb22cf609b7f2cef3e352032c07d11759ee22027d07e758" dependencies = [ "aleo-std", "anyhow", @@ -3301,9 +3464,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151c38941f583a0ac28cf99e0af446fc7e6e39ea1e3895d722b91365091e3bf5" +checksum = "f7fb0a9ba2b0373f3c242cc13140039f66b82167c31e578172e048da2c375033" dependencies = [ "aleo-std", "anyhow", @@ -3326,9 +3489,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-authority" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df0d3b6bd6b853916c956e46746c33d470ca5bb5f5404d98b9c3f1fd6390cc3" +checksum = "c0b24b9e54725e0eb3d3dea16159a0e5871fc5a48a7a93ad30c45914a87509d1" dependencies = [ "anyhow", "rand", @@ -3339,9 +3502,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-block" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100f73fcc90866ed7c7e14af4afd1b66a87b3bc4325aab8455e9120f6bd0dcb8" +checksum = "eec668a611a2e15e927943914253bfd3b9a6f709fd56a8f40bc75046cd07d77e" dependencies = [ "indexmap 2.6.0", "rayon", @@ -3360,9 +3523,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-committee" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c002b3ab30383901bc0a684b8c1fdfb6b9f4aaa71e7a59e323e5558ccd1df4b" +checksum = "a7a6cb54ae8d7dc233164123c0be74786c746ed07a525d3c5b3ad1df368560f8" dependencies = [ "indexmap 2.6.0", "rayon", @@ -3373,9 +3536,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abd5008b0837ac2fe1d33dc2be4ea38af778c3a8cfcab0f1839a70cf85eb54e" +checksum = "f74c3b84031541e865bc972aa773154f429c1350a848cac1fe42d5bf1e326b57" dependencies = [ "snarkvm-ledger-narwhal-batch-certificate", "snarkvm-ledger-narwhal-batch-header", @@ -3387,9 +3550,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-certificate" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d096cf0c0cc0b205797a811f7cd8694cb487bb945f17058a5fc8710703c793a" +checksum = "550fbc76ff67e050f480751fc52930015e6ccaeea852ad8eddb5ec595e804375" dependencies = [ "indexmap 2.6.0", "rayon", @@ -3401,9 +3564,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-batch-header" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3770f99778463ce8be6fb10f29a5737f889882ba358ea948d6c8e8a9021bd6" +checksum = "0451c6227e6cb15688129fa24cf0f5a49120e0e26c89e61fbfcd8a9c0ba4ad40" dependencies = [ "indexmap 2.6.0", "rayon", @@ -3414,9 +3577,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-data" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0f5641442b912623075331c51154fb447ba3b1990a28554417f55f1139a8667" +checksum = "ee5a38e42c52112d9ceb2331ded74d4e68040979471a5a1d9041165e83e43291" dependencies = [ "bytes", "serde_json", @@ -3426,9 +3589,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-subdag" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7747990ad94acb969cda70976b478ed40a0beb22583774434114e5419e5ea5d9" +checksum = "adbcd2d0dbc7dbe837dfb30de786ac341dd88afdb973b3a2a4c076a39d414e2e" dependencies = [ "indexmap 2.6.0", "rayon", @@ -3442,9 +3605,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-transmission" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abbc85999cce7f6c3cfff47db8bae834cb5eee374c5a06d9be959bb5bc5ce5f2" +checksum = "e8d5f001471a0a4f72d06bc9140c8fc3f52f23512956b657903b12a04c3504cf" dependencies = [ "bytes", "serde_json", @@ -3456,9 +3619,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-narwhal-transmission-id" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25614b92aed358b842cd3d0993afca1c4c41b5901f3f8b4fc806d0a97bda797d" +checksum = "44e348c47cb47148043240680a89839210d6bdfbcd8fcffe4df7b7b99e25411d" dependencies = [ "snarkvm-console", "snarkvm-ledger-puzzle", @@ -3466,9 +3629,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710122e2513287c1e992fb4dd11d74f1cfbdb49f15c0f50a3bf8bab82caedb49" +checksum = "7251252c6b6d48ccdfeee38f63127f01bcd1da1edcc8e73f71de5f33719cb059" dependencies = [ "aleo-std", "anyhow", @@ -3487,9 +3650,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-puzzle-epoch" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aae558db942169cafcfa83544562155d870f1770c473a8f800f3cea92886882" +checksum = "d1e0de23eabafe1380f54556a694c1a9a3574767fb4dd49c9c15975641a30e66" dependencies = [ "aleo-std", "anyhow", @@ -3509,9 +3672,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-query" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49609967badb249a3600f20d3f5a6600f727394f344800d30b1f89e0f278fe55" +checksum = "72d0a7c91e30017cf69a2f8d1636ea682b54a674c555d3d23b9f74d0cf376482" dependencies = [ "async-trait", "reqwest 0.11.27", @@ -3523,9 +3686,9 @@ dependencies = [ [[package]] name = "snarkvm-ledger-store" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0496ffd4e01b9a2a7914d6afa04d3b6a8ff8fa85edb37c0b25108d0a8b636a52" +checksum = "184854df19e419bc4f363713f1c5ba73248fa98e184edec881aba77b397b77cd" dependencies = [ "aleo-std-storage", "anyhow", @@ -3547,9 +3710,9 @@ dependencies = [ [[package]] name = "snarkvm-parameters" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f76c9e570372079d5705e45233aee5d18e72f77f19232b4c910146c8cef023" +checksum = "73c31bcc6cc152a8a9fc6992e32edba8f885b736d9ed67c4d9eead979e51d009" dependencies = [ "aleo-std", "anyhow", @@ -3573,9 +3736,9 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123712831bcb405db1eac34db195b07da96ab7787833750cbc52b03e966a2820" +checksum = "01171b6362e9b10f0a1b19364e86758c79f5469227d25ad677a37d675c31f172" dependencies = [ "aleo-std", "anyhow", @@ -3605,9 +3768,9 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-process" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39539619196b61e499db123e0e499c5a6ec61346ec1784d6350c9325146881d2" +checksum = "6576f8a4e376780da7a047a8424ddcc3d991192802fc4e333af275aa90c9bf37" dependencies = [ "aleo-std", "colored", @@ -3630,9 +3793,9 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-program" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5520850ffbfe8fd074608fded31f1a4fbc62100f83de1bb0f6b083b8b6e381" +checksum = "baea91c7ea8d8584bbe632340dd90cc56b4cc2127025981c298113d227bb587a" dependencies = [ "indexmap 2.6.0", "paste", @@ -3645,9 +3808,9 @@ dependencies = [ [[package]] name = "snarkvm-synthesizer-snark" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48155b6411c0735196e95668a7d8347b15a36ebae7dc91a824d292897f10bd8a" +checksum = "3fe2b943d82c5361917cbac96fbf8060f030ae0555efc9cfe0bd13e1e45680cb" dependencies = [ "bincode", "once_cell", @@ -3659,9 +3822,9 @@ dependencies = [ [[package]] name = "snarkvm-utilities" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fcbfa865d7b4a30922bcdb18adc15c5bf8ce2ea42250184d7d95655b493f7c" +checksum = "05c395af67af40e35bb22a1996acbc01119ba129199f6e2b498bf91706754c98" dependencies = [ "aleo-std", "anyhow", @@ -3681,9 +3844,9 @@ dependencies = [ [[package]] name = "snarkvm-utilities-derives" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b78577ade70f11743b6f76f6cf4b5fc16905b41caa499d691e0ae822d0340ab" +checksum = "857e98d8e92501c0c6a8102e3f2714a550bf2455977d05c25a10a45b5f8ce047" dependencies = [ "proc-macro2", "quote 1.0.36", @@ -3716,12 +3879,40 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote 1.0.36", + "rustversion", + "syn 2.0.82", +] + [[package]] name = "subtle" version = "2.5.0" @@ -4140,6 +4331,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui-input" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1733c47f1a217b7deff18730ff7ca4ecafc5771368f715ab072d679a36114" +dependencies = [ + "ratatui", + "unicode-width 0.2.0", +] + [[package]] name = "typenum" version = "1.17.0" @@ -4167,12 +4368,35 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width 0.1.12", +] + [[package]] name = "unicode-width" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.0.4" @@ -4582,6 +4806,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index 6ac213531d..ff84d6f311 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-lang" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "The Leo programming language" homepage = "https://leo-lang.org" @@ -27,7 +27,7 @@ include = [ ] license = "GPL-3.0" edition = "2021" -rust-version = "1.82.0" # Be sure to update CI and the ./rust-toolchain file. +rust-version = "1.82.0" [workspace] members = [ @@ -38,6 +38,7 @@ members = [ "compiler/span", "docs/grammar", "errors", + "interpreter", "leo/package", "tests/test-framework", "utils/disassembler", @@ -46,44 +47,51 @@ members = [ [workspace.dependencies.leo-ast] path = "./compiler/ast" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-compiler] path = "./compiler/compiler" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-disassembler] path = "./utils/disassembler" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-errors] path = "./errors" -version = "2.3.0" +version = "2.4.1" + +[workspace.dependencies.leo-interpreter] +path = "./interpreter" +version = "2.4.1" [workspace.dependencies.leo-package] path = "./leo/package" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-parser] path = "./compiler/parser" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-passes] path = "./compiler/passes" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-span] path = "./compiler/span" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.leo-retriever] path = "./utils/retriever" -version = "2.3.0" +version = "2.4.1" [workspace.dependencies.aleo-std] version = "0.1.24" default-features = false +[workspace.dependencies.colored] +version = "2.0" + [workspace.dependencies.indexmap] version = "2.6" features = [ "serde" ] @@ -95,11 +103,15 @@ version = "0.13.0" version = "0.8" default-features = false +[workspace.dependencies.rand_chacha] +version = "0.3.0" +default-features = false + [workspace.dependencies.regex] version = "1.11.1" [workspace.dependencies.snarkvm] -version = "1.0.0" +version = "1.1.0" [workspace.dependencies.serde] version = "1.0.214" @@ -109,6 +121,9 @@ features = [ "derive", "rc" ] version = "1.0" features = [ "preserve_order" ] +[workspace.dependencies.tempfile] +version = "3.13" + [workspace.dependencies.toml] version = "0.8" features = [ "preserve_order" ] @@ -127,7 +142,6 @@ path = "leo/cli/main.rs" default = [ ] ci_skip = [ "leo-compiler/ci_skip" ] noconfig = [ ] -# TODO: Consider refactoring to `testnet`, `mainnet`, and `canary` features, with all three being the default. only_testnet = [ ] [dependencies] @@ -142,9 +156,15 @@ workspace = true [dependencies.leo-compiler] workspace = true +[dependencies.leo-disassembler] +workspace = true + [dependencies.leo-errors] workspace = true +[dependencies.leo-interpreter] +workspace = true + [dependencies.leo-package] workspace = true @@ -165,7 +185,7 @@ version = "4.5" features = [ "derive", "env", "color", "unstable-styles" ] [dependencies.colored] -version = "2.0" +workspace = true [dependencies.dotenvy] version = "0.15.7" @@ -173,12 +193,14 @@ version = "0.15.7" [dependencies.indexmap] workspace = true +[dependencies.num_cpus] +version = "1.16.0" + [dependencies.rand] workspace = true [dependencies.rand_chacha] -version = "0.3.0" -default-features = false +workspace = true [dependencies.self_update] version = "0.41.0" diff --git a/build.rs b/build.rs index 5420a093a7..c374d98662 100644 --- a/build.rs +++ b/build.rs @@ -71,8 +71,24 @@ fn check_file_licenses>(path: P) { } } - // Re-run upon any changes to the workspace. - println!("cargo:rerun-if-changed=."); + // Watch the directories that contain Rust source files + println!("cargo:rerun-if-changed=compiler"); + println!("cargo:rerun-if-changed=errors"); + println!("cargo:rerun-if-changed=leo"); + println!("cargo:rerun-if-changed=tests/test-framework"); + println!("cargo:rerun-if-changed=utils"); + + // Watch the build script itself + println!("cargo:rerun-if-changed=build.rs"); + + // Watch the license header file + println!("cargo:rerun-if-changed=.resources/license_header"); + + // Watch the Cargo.toml file + println!("cargo:rerun-if-changed=Cargo.toml"); + + // Watch the Cargo.lock file + println!("cargo:rerun-if-changed=Cargo.lock"); } // The build script; it currently only checks the licenses. diff --git a/compiler/ast/Cargo.toml b/compiler/ast/Cargo.toml index 62bc6652b5..45a043a8d9 100644 --- a/compiler/ast/Cargo.toml +++ b/compiler/ast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-ast" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Abstract syntax tree (AST) for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/compiler/ast/src/functions/annotation.rs b/compiler/ast/src/functions/annotation.rs index 2cb07b54f4..13819aac35 100644 --- a/compiler/ast/src/functions/annotation.rs +++ b/compiler/ast/src/functions/annotation.rs @@ -18,6 +18,7 @@ use crate::{Identifier, Node, NodeID, simple_node_impl}; use leo_span::Span; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use std::fmt; @@ -27,6 +28,8 @@ pub struct Annotation { // TODO: Consider using a symbol instead of an identifier. /// The name of the annotation. pub identifier: Identifier, + /// The data associated with the annotation. + pub data: IndexMap>, /// A span locating where the annotation occurred in the source. pub span: Span, /// The ID of the node. @@ -37,6 +40,19 @@ simple_node_impl!(Annotation); impl fmt::Display for Annotation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "@{}", self.identifier) + let data = match self.data.is_empty() { + true => "".to_string(), + false => { + let mut string = String::new(); + for (key, value) in self.data.iter() { + match value { + None => string.push_str(&format!("{key},")), + Some(value) => string.push_str(&format!("{key} = \"{value}\",")), + } + } + format!("({string})") + } + }; + write!(f, "@{}{}", self.identifier, data) } } diff --git a/compiler/ast/src/functions/core_function.rs b/compiler/ast/src/functions/core_function.rs index 3732666df4..6af63f333c 100644 --- a/compiler/ast/src/functions/core_function.rs +++ b/compiler/ast/src/functions/core_function.rs @@ -289,6 +289,9 @@ pub enum CoreFunction { SignatureVerify, FutureAwait, + + CheatCodePrintMapping, + CheatCodeSetBlockHeight, } impl CoreFunction { @@ -566,6 +569,9 @@ impl CoreFunction { (sym::signature, sym::verify) => Self::SignatureVerify, (sym::Future, sym::Await) => Self::FutureAwait, + + (sym::CheatCode, sym::print_mapping) => Self::CheatCodePrintMapping, + (sym::CheatCode, sym::set_block_height) => Self::CheatCodeSetBlockHeight, _ => return None, }) } @@ -844,6 +850,9 @@ impl CoreFunction { Self::SignatureVerify => 3, Self::FutureAwait => 1, + + Self::CheatCodePrintMapping => 1, + Self::CheatCodeSetBlockHeight => 1, } } @@ -1101,7 +1110,9 @@ impl CoreFunction { | CoreFunction::SHA3_512HashToScalar | CoreFunction::GroupToXCoordinate | CoreFunction::GroupToYCoordinate - | CoreFunction::SignatureVerify => false, + | CoreFunction::SignatureVerify + | CoreFunction::CheatCodePrintMapping + | CoreFunction::CheatCodeSetBlockHeight => false, } } } diff --git a/compiler/ast/src/lib.rs b/compiler/ast/src/lib.rs index 2d079a93c5..1a42274a76 100644 --- a/compiler/ast/src/lib.rs +++ b/compiler/ast/src/lib.rs @@ -70,6 +70,7 @@ use leo_errors::{AstError, Result}; /// /// The [`Ast`] type represents a Leo program as a series of recursive data types. /// These data types form a tree that begins from a [`Program`] type root. +// TODO: Clean up by removing the `Ast` type and renaming the exiting `Program` type to `Ast`. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Ast { pub ast: Program, @@ -81,6 +82,15 @@ impl Ast { Self { ast: program } } + /// Combines the two ASTs into a single AST. + /// The ASTs are combined by extending the components of the first AST with the components of the second AST. + pub fn combine(&mut self, other: Self) { + let Program { imports, stubs, program_scopes } = other.ast; + self.ast.imports.extend(imports); + self.ast.stubs.extend(stubs); + self.ast.program_scopes.extend(program_scopes); + } + /// Returns a reference to the inner program AST representation. pub fn as_repr(&self) -> &Program { &self.ast diff --git a/compiler/ast/src/passes/reconstructor.rs b/compiler/ast/src/passes/reconstructor.rs index dea305c0f4..4371a0e12c 100644 --- a/compiler/ast/src/passes/reconstructor.rs +++ b/compiler/ast/src/passes/reconstructor.rs @@ -443,9 +443,6 @@ pub trait ProgramReconstructor: StatementReconstructor { fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope { ProgramScope { program_id: input.program_id, - structs: input.structs.into_iter().map(|(i, c)| (i, self.reconstruct_struct(c))).collect(), - mappings: input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect(), - functions: input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(), consts: input .consts .into_iter() @@ -454,6 +451,9 @@ pub trait ProgramReconstructor: StatementReconstructor { _ => unreachable!("`reconstruct_const` can only return `Statement::Const`"), }) .collect(), + structs: input.structs.into_iter().map(|(i, c)| (i, self.reconstruct_struct(c))).collect(), + mappings: input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect(), + functions: input.functions.into_iter().map(|(i, f)| (i, self.reconstruct_function(f))).collect(), span: input.span, } } diff --git a/compiler/ast/src/passes/visitor.rs b/compiler/ast/src/passes/visitor.rs index eb1f36a87a..d2842f980f 100644 --- a/compiler/ast/src/passes/visitor.rs +++ b/compiler/ast/src/passes/visitor.rs @@ -20,6 +20,28 @@ use crate::*; +// TODO: The Visitor and Reconstructor patterns need a redesign so that the default implementation can easily be invoked though its implemented in an overriding trait. +// Here is a pattern that seems to work +// trait ProgramVisitor { +// // The trait method that can be overridden +// fn visit_program_scope(&mut self); +// +// // Private helper function containing the default implementation +// fn default_visit_program_scope(&mut self) { +// println!("Do default stuff"); +// } +// } +// +// struct YourStruct; +// +// impl ProgramVisitor for YourStruct { +// fn visit_program_scope(&mut self) { +// println!("Do custom stuff."); +// // Call the default implementation +// self.default_visit_program_scope(); +// } +// } + /// A Visitor trait for expressions in the AST. pub trait ExpressionVisitor<'a> { type AdditionalInput: Default; @@ -92,7 +114,12 @@ pub trait ExpressionVisitor<'a> { Default::default() } - fn visit_struct_init(&mut self, _input: &'a StructExpression, _additional: &Self::AdditionalInput) -> Self::Output { + fn visit_struct_init(&mut self, input: &'a StructExpression, additional: &Self::AdditionalInput) -> Self::Output { + for StructVariableInitializer { expression, .. } in input.members.iter() { + if let Some(expression) = expression { + self.visit_expression(expression, additional); + } + } Default::default() } @@ -227,13 +254,10 @@ pub trait ProgramVisitor<'a>: StatementVisitor<'a> { } fn visit_program_scope(&mut self, input: &'a ProgramScope) { + input.consts.iter().for_each(|(_, c)| (self.visit_const(c))); input.structs.iter().for_each(|(_, c)| (self.visit_struct(c))); - input.mappings.iter().for_each(|(_, c)| (self.visit_mapping(c))); - input.functions.iter().for_each(|(_, c)| (self.visit_function(c))); - - input.consts.iter().for_each(|(_, c)| (self.visit_const(c))); } fn visit_stub(&mut self, _input: &'a Stub) {} diff --git a/compiler/compiler/Cargo.toml b/compiler/compiler/Cargo.toml index 2ba94cf4b8..c29555421c 100644 --- a/compiler/compiler/Cargo.toml +++ b/compiler/compiler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-compiler" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Compiler for Leo programming language" homepage = "https://leo-lang.org" @@ -74,7 +74,7 @@ workspace = true version = "3.1.1" [dev-dependencies.tempfile] -version = "3.13" +workspace = true [features] default = [ ] diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index 1cbe9d8b41..beb027e995 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -30,22 +30,19 @@ use leo_span::{Symbol, source_map::FileName, symbol::with_session_globals}; use snarkvm::prelude::Network; use indexmap::{IndexMap, IndexSet}; -use sha2::{Digest, Sha256}; -use std::{fs, path::PathBuf}; +use std::path::PathBuf; /// The primary entry point of the Leo compiler. #[derive(Clone)] pub struct Compiler<'a, N: Network> { + /// A name used to identify the instance of the compiler. + pub name: String, /// The handler is used for error and warning emissions. handler: &'a Handler, - /// The path to the main leo file. - main_file_path: PathBuf, + /// The source files and their content. + sources: Vec<(FileName, String)>, /// The path to where the compiler outputs all generated files. output_directory: PathBuf, - /// The program name, - pub program_name: String, - /// The network name, - pub network: String, /// The AST for the program. pub ast: Ast, /// Options configuring compilation. @@ -65,25 +62,23 @@ pub struct Compiler<'a, N: Network> { impl<'a, N: Network> Compiler<'a, N> { /// Returns a new Leo compiler. pub fn new( - program_name: String, - network: String, + name: String, handler: &'a Handler, - main_file_path: PathBuf, + sources: Vec<(FileName, String)>, output_directory: PathBuf, - compiler_options: Option, + compiler_options: CompilerOptions, import_stubs: IndexMap, ) -> Self { let node_builder = NodeBuilder::default(); let assigner = Assigner::default(); let type_table = TypeTable::default(); Self { + name, handler, - main_file_path, + sources, output_directory, - program_name, - network, ast: Ast::new(Program::default()), - compiler_options: compiler_options.unwrap_or_default(), + compiler_options, node_builder, assigner, import_stubs, @@ -92,58 +87,52 @@ impl<'a, N: Network> Compiler<'a, N> { } } - /// Returns a SHA256 checksum of the program file. - pub fn checksum(&self) -> Result { - // Read in the main file as string - let unparsed_file = fs::read_to_string(&self.main_file_path) - .map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?; - - // Hash the file contents - let mut hasher = Sha256::new(); - hasher.update(unparsed_file.as_bytes()); - let hash = hasher.finalize(); - - Ok(format!("{hash:x}")) + // TODO: Rethink build caching. + // /// Returns a SHA256 checksum of the program file. + // pub fn checksum(&self) -> Result { + // // Read in the main file as string + // let unparsed_file = fs::read_to_string(&self.main_file_path) + // .map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?; + // + // // Hash the file contents + // let mut hasher = Sha256::new(); + // hasher.update(unparsed_file.as_bytes()); + // let hash = hasher.finalize(); + // + // Ok(format!("{hash:x}")) + // } + + /// Reset the compiler with new sources. + pub fn reset(&mut self, sources: Vec<(FileName, String)>) { + // Reset the sources and AST. + self.sources = sources; + self.ast = Ast::new(Program::default()); + // Reset the internal state. + self.node_builder = NodeBuilder::default(); + self.assigner = Assigner::default(); + self.type_table = TypeTable::default(); } - /// Parses and stores a program file content from a string, constructs a syntax tree, and generates a program. - pub fn parse_program_from_string(&mut self, program_string: &str, name: FileName) -> Result<()> { - // Register the source (`program_string`) in the source map. - let prg_sf = with_session_globals(|s| s.source_map.new_source(program_string, name)); - - // Use the parser to construct the abstract syntax tree (ast). - self.ast = leo_parser::parse_ast::(self.handler, &self.node_builder, &prg_sf.src, prg_sf.start_pos)?; - - // If the program is imported, then check that the name of its program scope matches the file name. - // Note that parsing enforces that there is exactly one program scope in a file. - // TODO: Clean up check. - let program_scope = self.ast.ast.program_scopes.values().next().unwrap(); - let program_scope_name = format!("{}", program_scope.program_id.name); - if program_scope_name != self.program_name { - return Err(CompilerError::program_scope_name_does_not_match( - program_scope_name, - self.program_name.clone(), - program_scope.program_id.name.span, - ) - .into()); + /// Parses and stores the source information, constructs the AST, and optionally outputs it. + pub fn parse(&mut self) -> Result<()> { + // Initialize the AST. + let mut ast = Ast::default(); + // Parse the sources. + for (name, program_string) in &self.sources { + // Register the source (`program_string`) in the source map. + let prg_sf = with_session_globals(|s| s.source_map.new_source(program_string, name.clone())); + // Use the parser to construct the abstract syntax tree (ast). + ast.combine(leo_parser::parse_ast::(self.handler, &self.node_builder, &prg_sf.src, prg_sf.start_pos)?); } - + // Store the AST. + self.ast = ast; + // Write the AST to a JSON file. if self.compiler_options.output.initial_ast { self.write_ast_to_json("initial_ast.json")?; } - Ok(()) } - /// Parses and stores the main program file, constructs a syntax tree, and generates a program. - pub fn parse_program(&mut self) -> Result<()> { - // Load the program file. - let program_string = fs::read_to_string(&self.main_file_path) - .map_err(|e| CompilerError::file_read_error(&self.main_file_path, e))?; - - self.parse_program_from_string(&program_string, FileName::Real(self.main_file_path.clone())) - } - /// Runs the symbol table pass. pub fn symbol_table_pass(&self) -> Result { let symbol_table = SymbolTableCreator::do_pass((&self.ast, self.handler))?; @@ -155,19 +144,24 @@ impl<'a, N: Network> Compiler<'a, N> { /// Runs the type checker pass. pub fn type_checker_pass(&'a self, symbol_table: SymbolTable) -> Result<(SymbolTable, StructGraph, CallGraph)> { - let (symbol_table, struct_graph, call_graph) = TypeChecker::::do_pass(( + let (symbol_table, struct_graph, call_graph) = + TypeChecker::::do_pass((&self.ast, self.handler, symbol_table, &self.type_table, false))?; + if self.compiler_options.output.type_checked_symbol_table { + self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?; + } + Ok((symbol_table, struct_graph, call_graph)) + } + + /// Runs the static analysis pass. + pub fn static_analysis_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> { + StaticAnalyzer::::do_pass(( &self.ast, self.handler, symbol_table, &self.type_table, self.compiler_options.build.conditional_block_max_depth, self.compiler_options.build.disable_conditional_branch_type_checking, - false, // is_test - ))?; - if self.compiler_options.output.type_checked_symbol_table { - self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?; - } - Ok((symbol_table, struct_graph, call_graph)) + )) } /// Runs the loop unrolling pass. @@ -286,8 +280,11 @@ impl<'a, N: Network> Compiler<'a, N> { /// Runs the compiler stages. pub fn compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> { let st = self.symbol_table_pass()?; + let (st, struct_graph, call_graph) = self.type_checker_pass(st)?; + self.static_analysis_pass(&st)?; + // TODO: Make this pass optional. let st = self.loop_unrolling_pass(st)?; @@ -304,10 +301,165 @@ impl<'a, N: Network> Compiler<'a, N> { Ok((st, struct_graph, call_graph)) } + /// Runs the test symbol table pass. + pub fn test_symbol_table_pass(&self) -> Result { + let symbol_table = SymbolTableCreator::do_pass((&self.ast, self.handler))?; + if self.compiler_options.output.initial_symbol_table { + self.write_symbol_table_to_json("initial_symbol_table.json", &symbol_table)?; + } + Ok(symbol_table) + } + + /// Runs the test type checker pass. + pub fn test_type_checker_pass( + &'a self, + symbol_table: SymbolTable, + ) -> Result<(SymbolTable, StructGraph, CallGraph)> { + let (symbol_table, struct_graph, call_graph) = + TypeChecker::::do_pass((&self.ast, self.handler, symbol_table, &self.type_table, true))?; + if self.compiler_options.output.type_checked_symbol_table { + self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?; + } + Ok((symbol_table, struct_graph, call_graph)) + } + + /// Runs the test loop unrolling pass. + pub fn test_loop_unrolling_pass(&mut self, symbol_table: SymbolTable) -> Result { + let (ast, symbol_table) = Unroller::do_pass(( + std::mem::take(&mut self.ast), + self.handler, + &self.node_builder, + symbol_table, + &self.type_table, + ))?; + self.ast = ast; + + if self.compiler_options.output.unrolled_ast { + self.write_ast_to_json("unrolled_ast.json")?; + } + + if self.compiler_options.output.unrolled_symbol_table { + self.write_symbol_table_to_json("unrolled_symbol_table.json", &symbol_table)?; + } + + Ok(symbol_table) + } + + /// Runs the test static single assignment pass. + pub fn test_static_single_assignment_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> { + self.ast = StaticSingleAssigner::do_pass(( + std::mem::take(&mut self.ast), + &self.node_builder, + &self.assigner, + symbol_table, + &self.type_table, + ))?; + + if self.compiler_options.output.ssa_ast { + self.write_ast_to_json("ssa_ast.json")?; + } + + Ok(()) + } + + /// Runs the test flattening pass. + pub fn test_flattening_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> { + self.ast = Flattener::do_pass(( + std::mem::take(&mut self.ast), + symbol_table, + &self.type_table, + &self.node_builder, + &self.assigner, + ))?; + + if self.compiler_options.output.flattened_ast { + self.write_ast_to_json("flattened_ast.json")?; + } + + Ok(()) + } + + /// Runs the test destructuring pass. + pub fn test_destructuring_pass(&mut self) -> Result<()> { + self.ast = Destructurer::do_pass(( + std::mem::take(&mut self.ast), + &self.type_table, + &self.node_builder, + &self.assigner, + ))?; + + if self.compiler_options.output.destructured_ast { + self.write_ast_to_json("destructured_ast.json")?; + } + + Ok(()) + } + + /// Runs the test function inlining pass. + pub fn test_function_inlining_pass(&mut self, call_graph: &CallGraph) -> Result<()> { + let ast = FunctionInliner::do_pass(( + std::mem::take(&mut self.ast), + &self.node_builder, + call_graph, + &self.assigner, + &self.type_table, + ))?; + self.ast = ast; + + if self.compiler_options.output.inlined_ast { + self.write_ast_to_json("inlined_ast.json")?; + } + + Ok(()) + } + + /// Runs the test dead code elimination pass. + pub fn test_dead_code_elimination_pass(&mut self) -> Result<()> { + if self.compiler_options.build.dce_enabled { + self.ast = DeadCodeEliminator::do_pass((std::mem::take(&mut self.ast), &self.node_builder))?; + } + + if self.compiler_options.output.dce_ast { + self.write_ast_to_json("dce_ast.json")?; + } + + Ok(()) + } + + /// Runs the test code generation pass. + pub fn test_code_generation_pass( + &mut self, + symbol_table: &SymbolTable, + struct_graph: &StructGraph, + call_graph: &CallGraph, + ) -> Result { + CodeGenerator::do_pass((&self.ast, symbol_table, &self.type_table, struct_graph, call_graph, &self.ast.ast)) + } + + /// Runs the test compiler stages. + pub fn test_compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> { + let st = self.test_symbol_table_pass()?; + let (st, struct_graph, call_graph) = self.test_type_checker_pass(st)?; + + let st = self.test_loop_unrolling_pass(st)?; + + self.test_static_single_assignment_pass(&st)?; + + self.test_flattening_pass(&st)?; + + self.test_destructuring_pass()?; + + self.test_function_inlining_pass(&call_graph)?; + + self.test_dead_code_elimination_pass()?; + + Ok((st, struct_graph, call_graph)) + } + /// Returns a compiled Leo program. pub fn compile(&mut self) -> Result { // Parse the program. - self.parse_program()?; + self.parse()?; // Copy the dependencies specified in `program.json` into the AST. self.add_import_stubs()?; // Run the intermediate compiler stages. @@ -317,15 +469,28 @@ impl<'a, N: Network> Compiler<'a, N> { Ok(bytecode) } + /// Returns the compiled Leo tests. + pub fn compile_tests(&mut self) -> Result { + // Parse the program. + self.parse()?; + // Copy the dependencies specified in `program.json` into the AST. + self.add_import_stubs()?; + // Run the intermediate compiler stages. + let (symbol_table, struct_graph, call_graph) = self.test_compiler_stages()?; + // Run code generation. + let bytecode = self.test_code_generation_pass(&symbol_table, &struct_graph, &call_graph)?; + Ok(bytecode) + } + /// Writes the AST to a JSON file. fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> { // Remove `Span`s if they are not enabled. if self.compiler_options.output.ast_spans_enabled { - self.ast.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?; + self.ast.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.name))?; } else { self.ast.to_json_file_without_keys( self.output_directory.clone(), - &format!("{}.{file_suffix}", self.program_name), + &format!("{}.{file_suffix}", self.name), &["_span", "span"], )?; } @@ -336,12 +501,11 @@ impl<'a, N: Network> Compiler<'a, N> { fn write_symbol_table_to_json(&self, file_suffix: &str, symbol_table: &SymbolTable) -> Result<()> { // Remove `Span`s if they are not enabled. if self.compiler_options.output.symbol_table_spans_enabled { - symbol_table - .to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?; + symbol_table.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.name))?; } else { symbol_table.to_json_file_without_keys( self.output_directory.clone(), - &format!("{}.{file_suffix}", self.program_name), + &format!("{}.{file_suffix}", self.name), &["_span", "span"], )?; } @@ -367,7 +531,6 @@ impl<'a, N: Network> Compiler<'a, N> { } } else { return Err(CompilerError::imported_program_not_found( - self.program_name.clone(), *program_name, self.ast.ast.imports[program_name].1, ) diff --git a/compiler/compiler/src/options.rs b/compiler/compiler/src/options.rs index 6ed93c4309..263a0a1353 100644 --- a/compiler/compiler/src/options.rs +++ b/compiler/compiler/src/options.rs @@ -32,6 +32,8 @@ pub struct BuildOptions { pub conditional_block_max_depth: usize, /// Whether to disable type checking for nested conditionals. pub disable_conditional_branch_type_checking: bool, + /// If enabled builds all test programs. + pub build_tests: bool, } #[derive(Clone, Default)] diff --git a/compiler/compiler/tests/integration/compile.rs b/compiler/compiler/tests/integration/compile.rs index 33c53a8969..46c8a440d0 100644 --- a/compiler/compiler/tests/integration/compile.rs +++ b/compiler/compiler/tests/integration/compile.rs @@ -111,12 +111,11 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result Result(&program_name, &bytecode).map_err(|err| err.into()), + disassemble_from_str::(program_name, &bytecode).map_err(|err| err.into()), )?; - import_stubs.insert(Symbol::intern(&program_name), stub); + import_stubs.insert(Symbol::intern(program_name), stub); // Hash the ast files. let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, destructured_ast, inlined_ast, dce_ast) = - hash_asts(&program_name); + hash_asts(program_name); // Hash the symbol tables. let (initial_symbol_table, type_checked_symbol_table, unrolled_symbol_table) = - hash_symbol_tables(&program_name); + hash_symbol_tables(program_name); // Clean up the output directory. if fs::read_dir("/tmp/output").is_ok() { diff --git a/compiler/compiler/tests/integration/execute.rs b/compiler/compiler/tests/integration/execute.rs index 2d7c92a75e..a8fd6b8d0f 100644 --- a/compiler/compiler/tests/integration/execute.rs +++ b/compiler/compiler/tests/integration/execute.rs @@ -140,12 +140,11 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result Result(&program_name, &bytecode).map_err(|err| err.into()), + disassemble_from_str::(program_name, &bytecode).map_err(|err| err.into()), )?; - import_stubs.insert(Symbol::intern(&program_name), stub); + import_stubs.insert(Symbol::intern(program_name), stub); // Hash the ast files. let (initial_ast, unrolled_ast, ssa_ast, flattened_ast, destructured_ast, inlined_ast, dce_ast) = - hash_asts(&program_name); + hash_asts(program_name); // Hash the symbol tables. let (initial_symbol_table, type_checked_symbol_table, unrolled_symbol_table) = - hash_symbol_tables(&program_name); + hash_symbol_tables(program_name); // Clean up the output directory. if fs::read_dir("/tmp/output").is_ok() { diff --git a/compiler/compiler/tests/integration/utilities/mod.rs b/compiler/compiler/tests/integration/utilities/mod.rs index 449aa0b1bb..7deaa302a7 100644 --- a/compiler/compiler/tests/integration/utilities/mod.rs +++ b/compiler/compiler/tests/integration/utilities/mod.rs @@ -104,6 +104,7 @@ pub fn get_build_options(test_config: &TestConfig) -> Vec { .expect("Expected value to be a boolean."), conditional_block_max_depth: 10, disable_conditional_branch_type_checking: false, + build_tests: true, } }) .collect() @@ -112,6 +113,7 @@ pub fn get_build_options(test_config: &TestConfig) -> Vec { dce_enabled: true, conditional_block_max_depth: 10, disable_conditional_branch_type_checking: false, + build_tests: true, }], } } @@ -152,43 +154,30 @@ pub fn setup_build_directory( } pub fn new_compiler( - program_name: String, + name: String, handler: &Handler, - main_file_path: PathBuf, - compiler_options: Option, + sources: Vec<(FileName, String)>, + compiler_options: CompilerOptions, import_stubs: IndexMap, ) -> Compiler<'_, CurrentNetwork> { let output_dir = PathBuf::from("/tmp/output/"); fs::create_dir_all(output_dir.clone()).unwrap(); - Compiler::new( - program_name, - String::from("aleo"), - handler, - main_file_path, - output_dir, - compiler_options, - import_stubs, - ) + Compiler::new(name, handler, sources, output_dir, compiler_options, import_stubs) } pub fn parse_program<'a>( - program_name: String, + name: String, handler: &'a Handler, program_string: &str, cwd: Option, - compiler_options: Option, + compiler_options: CompilerOptions, import_stubs: IndexMap, ) -> Result, LeoError> { - let mut compiler = new_compiler( - program_name, - handler, - cwd.clone().unwrap_or_else(|| "compiler-test".into()), - compiler_options, - import_stubs, - ); - let name = cwd.map_or_else(|| FileName::Custom("compiler-test".into()), FileName::Real); - compiler.parse_program_from_string(program_string, name)?; + let file_name = cwd.map_or_else(|| FileName::Custom("compiler-test".into()), FileName::Real); + let mut compiler = + new_compiler(name, handler, vec![(file_name, program_string.into())], compiler_options, import_stubs); + compiler.parse()?; CheckUniqueNodeIds::new().visit_program(&compiler.ast.ast); @@ -265,6 +254,8 @@ pub fn compile_and_process<'a>(parsed: &'a mut Compiler<'a, CurrentNetwork>) -> let (st, struct_graph, call_graph) = parsed.type_checker_pass(st)?; + parsed.static_analysis_pass(&st)?; + CheckUniqueNodeIds::new().visit_program(&parsed.ast.ast); let st = parsed.loop_unrolling_pass(st)?; diff --git a/compiler/parser/Cargo.toml b/compiler/parser/Cargo.toml index c60a39378a..3f5b2e1fd1 100644 --- a/compiler/parser/Cargo.toml +++ b/compiler/parser/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-parser" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Parser for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index 2c2ff395b0..7f49a565f9 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -58,7 +58,7 @@ impl ParserContext<'_, N> { Ok(Program { imports, stubs: IndexMap::new(), program_scopes }) } - fn unexpected_item(token: &SpannedToken, expected: &[Token]) -> ParserError { + pub(super) fn unexpected_item(token: &SpannedToken, expected: &[Token]) -> ParserError { ParserError::unexpected( &token.token, expected.iter().map(|x| format!("'{x}'")).collect::>().join(", "), @@ -290,9 +290,10 @@ impl ParserContext<'_, N> { let name = self.expect_identifier()?; self.expect(&Token::Colon)?; - let type_ = self.parse_type()?.0; + let (type_, type_span) = self.parse_type()?; + let span = name.span() + type_span; - Ok(functions::Input { identifier: name, mode, type_, span: name.span, id: self.node_builder.next_id() }) + Ok(functions::Input { identifier: name, mode, type_, span, id: self.node_builder.next_id() }) } /// Returns an [`Output`] AST node if the next tokens represent a function output. @@ -316,15 +317,42 @@ impl ParserContext<'_, N> { // TODO: Verify that this check is sound. // Check that there is no whitespace or comments in between the `@` symbol and identifier. - match identifier.span.hi.0 - start.lo.0 > 1 + identifier.name.to_string().len() as u32 { - true => Err(ParserError::space_in_annotation(span).into()), - false => Ok(Annotation { identifier, span, id: self.node_builder.next_id() }), + if identifier.span.hi.0 - start.lo.0 > 1 + identifier.name.to_string().len() as u32 { + return Err(ParserError::space_in_annotation(span).into()); } + + // Optionally parse the data associated with the annotation. + // The data is a comma-separated sequence of identifiers or identifiers with associated strings. + // For example, `@test(should_fail, private_key = "foobar")` + let (data, span) = match &self.token.token { + Token::LeftParen => { + let (data, _, span) = self.parse_paren_comma_list(|p| { + let key = p.expect_identifier()?; + let value = if p.eat(&Token::Assign) { + match &p.token.token { + Token::StaticString(s) => { + let value = s.clone(); + p.expect(&Token::StaticString(value.clone()))?; + Some(value) + } + _ => return Err(ParserError::expected_string_literal_in_annotation(p.token.span).into()), + } + } else { + None + }; + Ok(Some((key, value))) + })?; + (data.into_iter().collect(), span) + } + _ => (Default::default(), span), + }; + + Ok(Annotation { identifier, data, span, id: self.node_builder.next_id() }) } /// Returns an [`(Identifier, Function)`] AST node if the next tokens represent a function name /// and function definition. - fn parse_function(&mut self) -> Result<(Symbol, Function)> { + pub(super) fn parse_function(&mut self) -> Result<(Symbol, Function)> { // TODO: Handle dangling annotations. // Parse annotations, if they exist. let mut annotations = Vec::new(); diff --git a/compiler/parser/src/parser/mod.rs b/compiler/parser/src/parser/mod.rs index f39da3a305..d91d30e2eb 100644 --- a/compiler/parser/src/parser/mod.rs +++ b/compiler/parser/src/parser/mod.rs @@ -22,7 +22,7 @@ use crate::{Token, tokenizer::*}; use leo_ast::*; -use leo_errors::{Result, emitter::Handler}; +use leo_errors::{ParserError, Result, emitter::Handler}; use leo_span::{Span, span::BytePos}; use snarkvm::prelude::Network; @@ -49,3 +49,35 @@ pub fn parse( tokens.parse_program() } + +pub fn parse_expression( + handler: &Handler, + node_builder: &NodeBuilder, + source: &str, + start_pos: BytePos, +) -> Result { + let mut context = ParserContext::::new(handler, node_builder, crate::tokenize(source, start_pos)?); + + let expression = context.parse_expression()?; + if context.token.token == Token::Eof { + Ok(expression) + } else { + Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into()) + } +} + +pub fn parse_statement( + handler: &Handler, + node_builder: &NodeBuilder, + source: &str, + start_pos: BytePos, +) -> Result { + let mut context = ParserContext::::new(handler, node_builder, crate::tokenize(source, start_pos)?); + + let statement = context.parse_statement()?; + if context.token.token == Token::Eof { + Ok(statement) + } else { + Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into()) + } +} diff --git a/compiler/passes/Cargo.toml b/compiler/passes/Cargo.toml index 3d2a045be5..824e883739 100644 --- a/compiler/passes/Cargo.toml +++ b/compiler/passes/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-passes" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Compiler passes for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/compiler/passes/src/code_generation/visit_expressions.rs b/compiler/passes/src/code_generation/visit_expressions.rs index 3ab9a3bc4b..eb4c7cbcde 100644 --- a/compiler/passes/src/code_generation/visit_expressions.rs +++ b/compiler/passes/src/code_generation/visit_expressions.rs @@ -485,6 +485,10 @@ impl<'a> CodeGenerator<'a> { writeln!(instruction, " {};", arguments[0]).expect("failed to write to string"); (String::new(), instruction) } + sym::CheatCode => { + (String::new(), String::new()) + // Do nothing. Cheat codes do not generate instructions. + } _ => { unreachable!("All core functions should be known at this phase of compilation") } diff --git a/compiler/passes/src/common/graph/mod.rs b/compiler/passes/src/common/graph/mod.rs index 74dcbf2982..a162dd3166 100644 --- a/compiler/passes/src/common/graph/mod.rs +++ b/compiler/passes/src/common/graph/mod.rs @@ -121,7 +121,7 @@ impl DiGraph { // Detects if there is a cycle in the graph starting from the given node, via a recursive depth-first search. // If there is no cycle, returns `None`. // If there is a cycle, returns the node that was most recently discovered. - // Nodes are added to to `finished` in post-order order. + // Nodes are added to `finished` in post-order order. fn contains_cycle_from(&self, node: N, discovered: &mut IndexSet, finished: &mut IndexSet) -> Option { // Add the node to the set of discovered nodes. discovered.insert(node); diff --git a/compiler/passes/src/common/tree_node/mod.rs b/compiler/passes/src/common/tree_node/mod.rs index cab96aaffa..45ef3c5fdc 100644 --- a/compiler/passes/src/common/tree_node/mod.rs +++ b/compiler/passes/src/common/tree_node/mod.rs @@ -50,9 +50,20 @@ impl TreeNode { } /// Removes an element from the current node. - pub fn remove_element(&mut self, element: &N) { + /// If the element does not exist, increment an internal counter which later used to generate an error that the user attempted to await a future twice. + /// Returns `true` if the element was removed but not the first one in the node. + pub fn remove_element(&mut self, element: &N) -> bool { + // Check if the element is the first one in the node. + let is_not_first = match self.elements.first() { + Some(first) => first != element, + None => false, + }; + // Remove the element from the node. if !self.elements.shift_remove(element) { self.counter += 1; + false + } else { + is_not_first } } } diff --git a/compiler/passes/src/dead_code_elimination/eliminate_expression.rs b/compiler/passes/src/dead_code_elimination/eliminate_expression.rs index d6fe402e01..0e4115dea0 100644 --- a/compiler/passes/src/dead_code_elimination/eliminate_expression.rs +++ b/compiler/passes/src/dead_code_elimination/eliminate_expression.rs @@ -32,9 +32,12 @@ impl ExpressionReconstructor for DeadCodeEliminator<'_> { /// Reconstructs the associated function access expression. fn reconstruct_associated_function(&mut self, input: AssociatedFunction) -> (Expression, Self::AdditionalOutput) { - // If the associated function manipulates a mapping, mark the statement as necessary. + // If the associated function manipulates a mapping, or a cheat code, mark the statement as necessary. match (&input.variant.name, input.name.name) { - (&sym::Mapping, sym::remove) | (&sym::Mapping, sym::set) | (&sym::Future, sym::Await) => { + (&sym::Mapping, sym::remove) + | (&sym::Mapping, sym::set) + | (&sym::Future, sym::Await) + | (&sym::CheatCode, _) => { self.is_necessary = true; } _ => {} diff --git a/compiler/passes/src/lib.rs b/compiler/passes/src/lib.rs index 2a79116858..d77f14647d 100644 --- a/compiler/passes/src/lib.rs +++ b/compiler/passes/src/lib.rs @@ -17,6 +17,9 @@ #![forbid(unsafe_code)] #![doc = include_str!("../README.md")] +pub mod static_analysis; +pub use static_analysis::*; + pub mod code_generation; pub use code_generation::*; diff --git a/compiler/passes/src/loop_unrolling/unroller.rs b/compiler/passes/src/loop_unrolling/unroller.rs index 5ac47f0ff3..e63e164d02 100644 --- a/compiler/passes/src/loop_unrolling/unroller.rs +++ b/compiler/passes/src/loop_unrolling/unroller.rs @@ -99,6 +99,7 @@ impl<'a> Unroller<'a> { .swap(previous_constant_propagation_table.borrow().lookup_scope_by_index(index).unwrap()); self.constant_propagation_table.borrow_mut().parent = Some(Box::new(previous_constant_propagation_table.into_inner())); + core::mem::replace(&mut self.scope_index, 0) } diff --git a/compiler/passes/src/static_analysis/analyze_expression.rs b/compiler/passes/src/static_analysis/analyze_expression.rs new file mode 100644 index 0000000000..6326ce61ad --- /dev/null +++ b/compiler/passes/src/static_analysis/analyze_expression.rs @@ -0,0 +1,67 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::StaticAnalyzer; + +use leo_ast::*; + +use snarkvm::console::network::Network; + +impl<'a, N: Network> ExpressionVisitor<'a> for StaticAnalyzer<'a, N> { + type AdditionalInput = (); + type Output = (); + + fn visit_access(&mut self, input: &'a AccessExpression, _: &Self::AdditionalInput) -> Self::Output { + if let AccessExpression::AssociatedFunction(access) = input { + // Get the core function. + let core_function = match CoreFunction::from_symbols(access.variant.name, access.name.name) { + Some(core_function) => core_function, + None => unreachable!("Typechecking guarantees that this function exists."), + }; + + // Check that the future was awaited correctly. + if core_function == CoreFunction::FutureAwait { + self.assert_future_await(&access.arguments.first(), input.span()); + } + } + } + + fn visit_call(&mut self, input: &'a CallExpression, _: &Self::AdditionalInput) -> Self::Output { + match &*input.function { + // Note that the parser guarantees that `input.function` is always an identifier. + Expression::Identifier(ident) => { + // If the function call is an external async transition, then for all async calls that follow a non-async call, + // we must check that the async call is not an async function that takes a future as an argument. + if self.non_async_external_call_seen + && self.variant == Some(Variant::AsyncTransition) + && input.program.is_some() + { + // Note that this unwrap is safe since we check that `input.program` is `Some` above. + self.assert_simple_async_transition_call(input.program.unwrap(), ident.name, input.span()); + } + // Otherwise look up the function and check if it is a non-async call. + if let Some(function_symbol) = + self.symbol_table.lookup_fn_symbol(Location::new(input.program, ident.name)) + { + if function_symbol.variant == Variant::Transition { + self.non_async_external_call_seen = true; + } + } + } + _ => unreachable!("Parsing guarantees that a function name is always an identifier."), + } + } +} diff --git a/compiler/passes/src/static_analysis/analyze_program.rs b/compiler/passes/src/static_analysis/analyze_program.rs new file mode 100644 index 0000000000..1c2f90ac34 --- /dev/null +++ b/compiler/passes/src/static_analysis/analyze_program.rs @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::StaticAnalyzer; + +use leo_ast::{Type, *}; +use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning}; + +use snarkvm::console::network::Network; + +impl<'a, N: Network> ProgramVisitor<'a> for StaticAnalyzer<'a, N> { + fn visit_program_scope(&mut self, input: &'a ProgramScope) { + // Set the current program name. + self.current_program = Some(input.program_id.name.name); + // Do the default implementation for visiting the program scope. + input.structs.iter().for_each(|(_, c)| (self.visit_struct(c))); + input.mappings.iter().for_each(|(_, c)| (self.visit_mapping(c))); + input.functions.iter().for_each(|(_, c)| (self.visit_function(c))); + input.consts.iter().for_each(|(_, c)| (self.visit_const(c))); + } + + fn visit_function(&mut self, function: &'a Function) { + // Set the function name and variant. + self.variant = Some(function.variant); + + // Set `non_async_external_call_seen` to false. + self.non_async_external_call_seen = false; + + if matches!(self.variant, Some(Variant::AsyncFunction) | Some(Variant::AsyncTransition)) { + super::future_checker::future_check_function(function, self.type_table, self.handler); + } + + // If the function is an async function, initialize the await checker. + if self.variant == Some(Variant::AsyncFunction) { + // Initialize the list of input futures. Each one must be awaited before the end of the function. + self.await_checker.set_futures( + function + .input + .iter() + .filter_map(|input| { + if let Type::Future(_) = input.type_.clone() { Some(input.identifier.name) } else { None } + }) + .collect(), + ); + } + + self.visit_block(&function.block); + + // Check that all futures were awaited exactly once. + if self.variant == Some(Variant::AsyncFunction) { + // Throw error if not all futures awaits even appear once. + if !self.await_checker.static_to_await.is_empty() { + self.emit_err(StaticAnalyzerError::future_awaits_missing( + self.await_checker + .static_to_await + .clone() + .iter() + .map(|f| f.to_string()) + .collect::>() + .join(", "), + function.span(), + )); + } else if self.await_checker.enabled && !self.await_checker.to_await.is_empty() { + // Tally up number of paths that are unawaited and number of paths that are awaited more than once. + let (num_paths_unawaited, num_paths_duplicate_awaited, num_perfect) = + self.await_checker.to_await.iter().fold((0, 0, 0), |(unawaited, duplicate, perfect), path| { + ( + unawaited + if !path.elements.is_empty() { 1 } else { 0 }, + duplicate + if path.counter > 0 { 1 } else { 0 }, + perfect + if path.counter > 0 || !path.elements.is_empty() { 0 } else { 1 }, + ) + }); + + // Throw error if there does not exist a path in which all futures are awaited exactly once. + if num_perfect == 0 { + self.emit_err(StaticAnalyzerError::no_path_awaits_all_futures_exactly_once( + self.await_checker.to_await.len(), + function.span(), + )); + } + + // Throw warning if not all futures are awaited in some paths. + if num_paths_unawaited > 0 { + self.emit_warning(StaticAnalyzerWarning::some_paths_do_not_await_all_futures( + self.await_checker.to_await.len(), + num_paths_unawaited, + function.span(), + )); + } + + // Throw warning if some futures are awaited more than once in some paths. + if num_paths_duplicate_awaited > 0 { + self.emit_warning(StaticAnalyzerWarning::some_paths_contain_duplicate_future_awaits( + self.await_checker.to_await.len(), + num_paths_duplicate_awaited, + function.span(), + )); + } + } + } + } +} diff --git a/compiler/passes/src/static_analysis/analyze_statement.rs b/compiler/passes/src/static_analysis/analyze_statement.rs new file mode 100644 index 0000000000..f35295a9a2 --- /dev/null +++ b/compiler/passes/src/static_analysis/analyze_statement.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; +use crate::ConditionalTreeNode; + +use leo_ast::*; + +impl<'a, N: Network> StatementVisitor<'a> for StaticAnalyzer<'a, N> { + fn visit_conditional(&mut self, input: &'a ConditionalStatement) { + self.visit_expression(&input.condition, &Default::default()); + + // Create scope for checking awaits in `then` branch of conditional. + let current_bst_nodes: Vec = + match self.await_checker.create_then_scope(self.variant == Some(Variant::AsyncFunction), input.span) { + Ok(nodes) => nodes, + Err(warn) => return self.emit_warning(warn), + }; + + // Visit block. + self.visit_block(&input.then); + + // Exit scope for checking awaits in `then` branch of conditional. + let saved_paths = + self.await_checker.exit_then_scope(self.variant == Some(Variant::AsyncFunction), current_bst_nodes); + + if let Some(otherwise) = &input.otherwise { + match &**otherwise { + Statement::Block(stmt) => { + // Visit the otherwise-block. + self.visit_block(stmt); + } + Statement::Conditional(stmt) => self.visit_conditional(stmt), + _ => unreachable!("Else-case can only be a block or conditional statement."), + } + } + + // Update the set of all possible BST paths. + self.await_checker.exit_statement_scope(self.variant == Some(Variant::AsyncFunction), saved_paths); + } +} diff --git a/compiler/passes/src/static_analysis/analyzer.rs b/compiler/passes/src/static_analysis/analyzer.rs new file mode 100644 index 0000000000..6527c34069 --- /dev/null +++ b/compiler/passes/src/static_analysis/analyzer.rs @@ -0,0 +1,143 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{SymbolTable, TypeTable, static_analysis::await_checker::AwaitChecker}; + +use leo_ast::*; +use leo_errors::{StaticAnalyzerError, StaticAnalyzerWarning, emitter::Handler}; +use leo_span::{Span, Symbol}; + +use snarkvm::console::network::Network; + +use std::marker::PhantomData; + +pub struct StaticAnalyzer<'a, N: Network> { + /// The symbol table for the program. + pub(crate) symbol_table: &'a SymbolTable, + /// The type table for the program. + // Note that this pass does not use the type table in a meaningful way. + // However, this may be useful in future static analysis passes. + pub(crate) type_table: &'a TypeTable, + /// The error handler. + pub(crate) handler: &'a Handler, + /// Struct to store the state relevant to checking all futures are awaited. + pub(crate) await_checker: AwaitChecker, + /// The current program name. + pub(crate) current_program: Option, + /// The variant of the function that we are currently traversing. + pub(crate) variant: Option, + /// Whether or not a non-async external call has been seen in this function. + pub(crate) non_async_external_call_seen: bool, + // Allows the type checker to be generic over the network. + phantom: PhantomData, +} + +impl<'a, N: Network> StaticAnalyzer<'a, N> { + /// Returns a new static analyzer given a symbol table and error handler. + pub fn new( + symbol_table: &'a SymbolTable, + _type_table: &'a TypeTable, + handler: &'a Handler, + max_depth: usize, + disabled: bool, + ) -> Self { + Self { + symbol_table, + type_table: _type_table, + handler, + await_checker: AwaitChecker::new(max_depth, !disabled), + current_program: None, + variant: None, + non_async_external_call_seen: false, + phantom: Default::default(), + } + } + + /// Emits a type checker error. + pub(crate) fn emit_err(&self, err: StaticAnalyzerError) { + self.handler.emit_err(err); + } + + /// Emits a type checker warning + pub fn emit_warning(&self, warning: StaticAnalyzerWarning) { + self.handler.emit_warning(warning.into()); + } + + /// Type checks the awaiting of a future. + pub(crate) fn assert_future_await(&mut self, future: &Option<&Expression>, span: Span) { + // Make sure that it is an identifier expression. + let future_variable = match future { + Some(Expression::Identifier(name)) => name, + _ => { + return self.emit_err(StaticAnalyzerError::invalid_await_call(span)); + } + }; + + // Make sure that the future is defined. + match self.type_table.get(&future_variable.id) { + Some(type_) => { + if !matches!(type_, Type::Future(_)) { + self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); + } + // Mark the future as consumed. + // If the call returns true, it means that a future was not awaited in the order of the input list, emit a warning. + if self.await_checker.remove(future_variable) { + self.emit_warning(StaticAnalyzerWarning::future_not_awaited_in_order( + future_variable.name, + future_variable.span(), + )); + } + } + None => { + self.emit_err(StaticAnalyzerError::expected_future(future_variable.name, future_variable.span())); + } + } + } + + /// Assert that an async call is a "simple" one. + /// Simple is defined as an async transition function which does not return a `Future` that itself takes a `Future` as an argument. + pub(crate) fn assert_simple_async_transition_call(&self, program: Symbol, function_name: Symbol, span: Span) { + // Look up the function. + let function = match self.symbol_table.lookup_fn_symbol(Location::new(Some(program), function_name)) { + Some(function) => function, + None => { + unreachable!("Type checking guarantees that this function exists."); + } + }; + // If it is not an async transition, return. + if function.variant != Variant::AsyncTransition { + return; + } + // Otherwise, get the location of the finalize block. + let location = match &function.finalize { + Some(location) => location.clone(), + None => { + unreachable!("Typechecking guarantees that all async transitions have an associated `finalize` field."); + } + }; + // Look up the async function. + let async_function = match self.symbol_table.lookup_fn_symbol(location) { + Some(function) => function, + None => { + unreachable!("Type checking guarantees that this function exists."); + } + }; + // If the async function takes a future as an argument, emit an error. + if !async_function.future_inputs.is_empty() { + self.emit_err(StaticAnalyzerError::async_transition_call_with_future_argument(function_name, span)); + } + } +} diff --git a/compiler/passes/src/type_checking/await_checker.rs b/compiler/passes/src/static_analysis/await_checker.rs similarity index 86% rename from compiler/passes/src/type_checking/await_checker.rs rename to compiler/passes/src/static_analysis/await_checker.rs index b6ddf198d9..a8086e13d0 100644 --- a/compiler/passes/src/type_checking/await_checker.rs +++ b/compiler/passes/src/static_analysis/await_checker.rs @@ -17,7 +17,7 @@ use crate::ConditionalTreeNode; use indexmap::IndexSet; use leo_ast::Identifier; -use leo_errors::TypeCheckerWarning; +use leo_errors::StaticAnalyzerWarning; use leo_span::{Span, Symbol}; // TODO: Could optimize by removing duplicate paths (if set of futures is the same). @@ -39,15 +39,20 @@ impl AwaitChecker { } /// Remove from list. - pub fn remove(&mut self, id: &Identifier) { + /// Returns `true` if there was a path where the future was not awaited in the order of the input list. + pub fn remove(&mut self, id: &Identifier) -> bool { // Can assume in finalize block. - if self.enabled { + let is_not_first = if self.enabled { // Remove from dynamic list. - self.to_await.iter_mut().for_each(|node| node.remove_element(&id.name)); - } + self.to_await.iter_mut().any(|node| node.remove_element(&id.name)) + } else { + false + }; // Remove from static list. self.static_to_await.shift_remove(&id.name); + + is_not_first } /// Initialize futures. @@ -65,14 +70,14 @@ impl AwaitChecker { &mut self, is_finalize: bool, input: Span, - ) -> Result, TypeCheckerWarning> { + ) -> Result, StaticAnalyzerWarning> { if is_finalize && self.enabled { let mut current_nodes = Vec::new(); // Extend all paths by one node to represent the upcoming `then` branch. for node in self.to_await.iter() { // Error if exceed maximum depth. if node.depth > self.max_depth { - return Err(TypeCheckerWarning::max_conditional_block_depth_exceeded(self.max_depth, input)); + return Err(StaticAnalyzerWarning::max_conditional_block_depth_exceeded(self.max_depth, input)); } // Extend current path. current_nodes.push(node.clone().create_child()); diff --git a/compiler/passes/src/static_analysis/future_checker.rs b/compiler/passes/src/static_analysis/future_checker.rs new file mode 100644 index 0000000000..f4d8de0c08 --- /dev/null +++ b/compiler/passes/src/static_analysis/future_checker.rs @@ -0,0 +1,167 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::TypeTable; + +use leo_ast::{CoreFunction, Expression, ExpressionVisitor, Function, Node, StatementVisitor, Type}; +use leo_errors::{StaticAnalyzerError, emitter::Handler}; + +/// Error if futures are used improperly. +/// +/// This prevents, for instance, a bare call which creates an unused future. +pub fn future_check_function(function: &Function, type_table: &TypeTable, handler: &Handler) { + let mut future_checker = FutureChecker { type_table, handler }; + future_checker.visit_block(&function.block); +} + +#[derive(Clone, Copy, Debug, Default)] +enum Position { + #[default] + Misc, + Await, + TupleAccess, + Return, + FunctionArgument, + LastTupleLiteral, + Definition, +} + +struct FutureChecker<'a> { + type_table: &'a TypeTable, + handler: &'a Handler, +} + +impl<'a> FutureChecker<'a> { + fn emit_err(&self, err: StaticAnalyzerError) { + self.handler.emit_err(err); + } +} + +impl<'a> ExpressionVisitor<'a> for FutureChecker<'a> { + type AdditionalInput = Position; + type Output = (); + + fn visit_expression(&mut self, input: &'a Expression, additional: &Self::AdditionalInput) -> Self::Output { + use Position::*; + let is_call = matches!(input, Expression::Call(..)); + match self.type_table.get(&input.id()) { + Some(Type::Future(..)) if is_call => { + // A call producing a Future may appear in any of these positions. + if !matches!(additional, Await | Return | FunctionArgument | LastTupleLiteral | Definition) { + self.emit_err(StaticAnalyzerError::misplaced_future(input.span())); + } + } + Some(Type::Future(..)) => { + // A Future expression that's not a call may appear in any of these positions. + if !matches!(additional, Await | Return | FunctionArgument | LastTupleLiteral | TupleAccess) { + self.emit_err(StaticAnalyzerError::misplaced_future(input.span())); + } + } + Some(Type::Tuple(tuple)) if !matches!(tuple.elements().last(), Some(Type::Future(_))) => {} + Some(Type::Tuple(..)) if is_call => { + // A call producing a Tuple ending in a Future may appear in any of these positions. + if !matches!(additional, Return | Definition) { + self.emit_err(StaticAnalyzerError::misplaced_future(input.span())); + } + } + Some(Type::Tuple(..)) => { + // A Tuple ending in a Future that's not a call may appear in any of these positions. + if !matches!(additional, Return | TupleAccess) { + self.emit_err(StaticAnalyzerError::misplaced_future(input.span())); + } + } + _ => {} + } + + match input { + Expression::Access(access) => self.visit_access(access, &Position::Misc), + Expression::Array(array) => self.visit_array(array, &Position::Misc), + Expression::Binary(binary) => self.visit_binary(binary, &Position::Misc), + Expression::Call(call) => self.visit_call(call, &Position::Misc), + Expression::Cast(cast) => self.visit_cast(cast, &Position::Misc), + Expression::Struct(struct_) => self.visit_struct_init(struct_, &Position::Misc), + Expression::Err(err) => self.visit_err(err, &Position::Misc), + Expression::Identifier(identifier) => self.visit_identifier(identifier, &Position::Misc), + Expression::Literal(literal) => self.visit_literal(literal, &Position::Misc), + Expression::Locator(locator) => self.visit_locator(locator, &Position::Misc), + Expression::Ternary(ternary) => self.visit_ternary(ternary, &Position::Misc), + Expression::Tuple(tuple) => self.visit_tuple(tuple, additional), + Expression::Unary(unary) => self.visit_unary(unary, &Position::Misc), + Expression::Unit(unit) => self.visit_unit(unit, &Position::Misc), + } + } + + fn visit_access( + &mut self, + input: &'a leo_ast::AccessExpression, + _additional: &Self::AdditionalInput, + ) -> Self::Output { + match input { + leo_ast::AccessExpression::Array(array) => { + self.visit_expression(&array.array, &Position::Misc); + self.visit_expression(&array.index, &Position::Misc); + } + leo_ast::AccessExpression::AssociatedFunction(function) => { + let core_function = CoreFunction::from_symbols(function.variant.name, function.name.name) + .expect("Typechecking guarantees that this function exists."); + let position = + if core_function == CoreFunction::FutureAwait { Position::Await } else { Position::Misc }; + function.arguments.iter().for_each(|arg| { + self.visit_expression(arg, &position); + }); + } + leo_ast::AccessExpression::Member(member) => { + self.visit_expression(&member.inner, &Position::Misc); + } + leo_ast::AccessExpression::Tuple(tuple) => { + self.visit_expression(&tuple.tuple, &Position::TupleAccess); + } + _ => {} + } + + Default::default() + } + + fn visit_call(&mut self, input: &'a leo_ast::CallExpression, _additional: &Self::AdditionalInput) -> Self::Output { + input.arguments.iter().for_each(|expr| { + self.visit_expression(expr, &Position::FunctionArgument); + }); + Default::default() + } + + fn visit_tuple(&mut self, input: &'a leo_ast::TupleExpression, additional: &Self::AdditionalInput) -> Self::Output { + let next_position = match additional { + Position::Definition | Position::Return => Position::LastTupleLiteral, + _ => Position::Misc, + }; + let mut iter = input.elements.iter().peekable(); + while let Some(expr) = iter.next() { + let position = if iter.peek().is_some() { &Position::Misc } else { &next_position }; + self.visit_expression(expr, position); + } + Default::default() + } +} + +impl<'a> StatementVisitor<'a> for FutureChecker<'a> { + fn visit_definition(&mut self, input: &'a leo_ast::DefinitionStatement) { + self.visit_expression(&input.value, &Position::Definition); + } + + fn visit_return(&mut self, input: &'a leo_ast::ReturnStatement) { + self.visit_expression(&input.expression, &Position::Return); + } +} diff --git a/compiler/passes/src/static_analysis/mod.rs b/compiler/passes/src/static_analysis/mod.rs new file mode 100644 index 0000000000..e36a13a15b --- /dev/null +++ b/compiler/passes/src/static_analysis/mod.rs @@ -0,0 +1,46 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +mod future_checker; + +mod await_checker; + +pub mod analyze_expression; + +pub mod analyze_program; + +pub mod analyze_statement; + +pub mod analyzer; +pub use analyzer::*; + +use crate::{Pass, SymbolTable, TypeTable}; + +use leo_ast::{Ast, ProgramVisitor}; +use leo_errors::{Result, emitter::Handler}; + +use snarkvm::prelude::Network; + +impl<'a, N: Network> Pass for StaticAnalyzer<'a, N> { + type Input = (&'a Ast, &'a Handler, &'a SymbolTable, &'a TypeTable, usize, bool); + type Output = Result<()>; + + fn do_pass((ast, handler, st, tt, max_depth, await_checking): Self::Input) -> Self::Output { + let mut visitor = StaticAnalyzer::::new(st, tt, handler, max_depth, await_checking); + visitor.visit_program(ast.as_repr()); + handler.last_err().map_err(|e| *e) + } +} diff --git a/compiler/passes/src/static_single_assignment/rename_expression.rs b/compiler/passes/src/static_single_assignment/rename_expression.rs index e4614da65a..0c23fce6a9 100644 --- a/compiler/passes/src/static_single_assignment/rename_expression.rs +++ b/compiler/passes/src/static_single_assignment/rename_expression.rs @@ -320,7 +320,7 @@ impl ExpressionConsumer for StaticSingleAssigner<'_> { (Expression::Identifier(place), vec![statement]) } - /// Consumes and returns the locator expression without making any modifciations + /// Consumes and returns the locator expression without making any modifications fn consume_locator(&mut self, input: LocatorExpression) -> Self::Output { (Expression::Locator(input), Vec::new()) } diff --git a/compiler/passes/src/static_single_assignment/rename_statement.rs b/compiler/passes/src/static_single_assignment/rename_statement.rs index 479dec8892..4defd35824 100644 --- a/compiler/passes/src/static_single_assignment/rename_statement.rs +++ b/compiler/passes/src/static_single_assignment/rename_statement.rs @@ -188,7 +188,7 @@ impl StatementConsumer for StaticSingleAssigner<'_> { let if_true = create_phi_argument(&if_table, **symbol); let if_false = create_phi_argument(&else_table, **symbol); - // Create a new node ID for the the phi function. + // Create a new node ID for the phi function. let id = self.node_builder.next_id(); // Update the type of the node ID. let type_ = match self.type_table.get(&if_true.id()) { diff --git a/compiler/passes/src/symbol_table_creation/creator.rs b/compiler/passes/src/symbol_table_creation/creator.rs index aac9ae6bda..af4b77e8d4 100644 --- a/compiler/passes/src/symbol_table_creation/creator.rs +++ b/compiler/passes/src/symbol_table_creation/creator.rs @@ -114,11 +114,36 @@ impl<'a> ProgramVisitor<'a> for SymbolTableCreator<'a> { } fn visit_function_stub(&mut self, input: &'a FunctionStub) { - if let Err(err) = - self.symbol_table.insert_fn(Location::new(self.program_name, input.name()), &Function::from(input.clone())) - { + // Construct the location for the function. + let location = Location::new(self.program_name, input.name()); + // Initialize the function symbol. + if let Err(err) = self.symbol_table.insert_fn(location.clone(), &Function::from(input.clone())) { self.handler.emit_err(err); } + // If the `FunctionStub` is an async transition, attach the finalize logic to the function. + if matches!(input.variant, Variant::AsyncTransition) { + // This matches the logic in the disassembler. + let name = Symbol::intern(&format!("finalize/{}", input.name())); + if let Err(err) = self.symbol_table.attach_finalize(location, Location::new(self.program_name, name)) { + self.handler.emit_err(err); + } + } + // Otherwise is the `FunctionStub` is an async function, attach the future inputs. + else if matches!(input.variant, Variant::AsyncFunction) { + let future_inputs = input + .input + .iter() + .filter_map(|input| match &input.type_ { + Type::Future(future_type) => future_type.location.clone(), + _ => None, + }) + .collect(); + // Note that this unwrap is safe, because `self.program_name` is set before traversing the AST. + if let Err(err) = self.symbol_table.insert_futures(self.program_name.unwrap(), input.name(), future_inputs) + { + self.handler.emit_err(err); + } + } } fn visit_struct_stub(&mut self, input: &'a Composite) { diff --git a/compiler/passes/src/type_checking/check_expressions.rs b/compiler/passes/src/type_checking/check_expressions.rs index 097f7130ce..69405de1f7 100644 --- a/compiler/passes/src/type_checking/check_expressions.rs +++ b/compiler/passes/src/type_checking/check_expressions.rs @@ -119,14 +119,10 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { } // Await futures here so that can use the argument variable names to lookup. - if core_instruction == CoreFunction::FutureAwait { - if access.arguments.len() != 1 { - self.emit_err(TypeCheckerError::can_only_await_one_future_at_a_time(access.span)); - return Some(Type::Unit); - } - self.assert_future_await(&access.arguments.first(), input.span()); + if core_instruction == CoreFunction::FutureAwait && access.arguments.len() != 1 { + self.emit_err(TypeCheckerError::can_only_await_one_future_at_a_time(access.span)); + return Some(Type::Unit); } - return return_type; } else { self.emit_err(TypeCheckerError::invalid_core_function_call(access, access.span())); @@ -151,7 +147,6 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { if let Some(expected) = expected { self.check_eq_types(&Some(actual.clone()), &Some(expected.clone()), access.span()); } - // Return type of tuple index. return Some(actual); } @@ -206,7 +201,11 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { sym::height => { // Check that the operation is invoked in a `finalize` block. self.check_access_allowed("block.height", true, access.name.span()); - return Some(Type::Integer(IntegerType::U32)); + return Some(self.assert_and_return_type( + Type::Integer(IntegerType::U32), + expected, + input.span(), + )); } _ => { self.emit_err(TypeCheckerError::invalid_block_access(access.name.span())); @@ -671,10 +670,34 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { self.scope_state.is_call = true; let (mut input_futures, mut inferred_finalize_inputs) = (Vec::new(), Vec::new()); for (expected, argument) in func.input.iter().zip(input.arguments.iter()) { - // Get the type of the expression. If the type is not known, do not attempt to attempt any futher inference. + // Get the type of the expression. If the type is not known, do not attempt to attempt any further inference. let ty = self.visit_expression(argument, &Some(expected.type_().clone()))?; // Extract information about futures that are being consumed. if func.variant == Variant::AsyncFunction && matches!(expected.type_(), Type::Future(_)) { + // Consume the future. + let option_name = match argument { + Expression::Identifier(id) => Some(id.name), + Expression::Access(AccessExpression::Tuple(tuple_access)) => { + if let Expression::Identifier(id) = &*tuple_access.tuple { + Some(id.name) + } else { + None + } + } + _ => None, + }; + + if let Some(name) = option_name { + match self.scope_state.futures.shift_remove(&name) { + Some(future) => { + self.scope_state.call_location = Some(future.clone()); + } + None => { + self.emit_err(TypeCheckerError::unknown_future_consumed(name, argument.span())); + } + } + } + match argument { Expression::Identifier(_) | Expression::Call(_) @@ -857,23 +880,6 @@ impl<'a, N: Network> ExpressionVisitor<'a> for TypeChecker<'a, N> { fn visit_identifier(&mut self, input: &'a Identifier, expected: &Self::AdditionalInput) -> Self::Output { let var = self.symbol_table.borrow().lookup_variable(Location::new(None, input.name)).cloned(); if let Some(var) = &var { - if matches!(var.type_, Type::Future(_)) && matches!(expected, Some(Type::Future(_))) { - if self.scope_state.variant == Some(Variant::AsyncTransition) && self.scope_state.is_call { - // Consume future. - match self.scope_state.futures.shift_remove(&input.name) { - Some(future) => { - self.scope_state.call_location = Some(future.clone()); - return Some(var.type_.clone()); - } - None => { - self.emit_err(TypeCheckerError::unknown_future_consumed(input.name, input.span)); - } - } - } else { - // Case where accessing input argument of future. Ex `f.1`. - return Some(var.type_.clone()); - } - } Some(self.assert_and_return_type(var.type_.clone(), expected, input.span())) } else { self.emit_err(TypeCheckerError::unknown_sym("variable", input.name, input.span())); diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index 32cbac3674..f9d2283b65 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -17,7 +17,7 @@ use crate::{DiGraphError, TypeChecker}; use leo_ast::{Type, *}; -use leo_errors::{TypeCheckerError, TypeCheckerWarning}; +use leo_errors::TypeCheckerError; use leo_span::sym; use snarkvm::console::network::Network; @@ -166,7 +166,13 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { if input.is_record { "record" } else { "struct" }, identifier.span, )); + } else if matches!(type_, Type::Future(..)) { + self.emit_err(TypeCheckerError::composite_data_type_cannot_contain_future( + if input.is_record { "record" } else { "struct" }, + identifier.span, + )); } + // Ensure that there are no record members. self.assert_member_is_not_record(identifier.span, input.identifier.name, type_); // If the member is a struct, add it to the struct dependency graph. @@ -194,8 +200,9 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { fn visit_mapping(&mut self, input: &'a Mapping) { // Check that a mapping's key type is valid. self.assert_type_is_valid(&input.key_type, input.span); - // Check that a mapping's key type is not a tuple, record, or mapping. + // Check that a mapping's key type is not a future, tuple, record, or mapping. match input.key_type.clone() { + Type::Future(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "future", input.span)), Type::Tuple(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "tuple", input.span)), Type::Composite(struct_type) => { if let Some(struct_) = self.lookup_struct(struct_type.program, struct_type.id.name) { @@ -211,8 +218,9 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { // Check that a mapping's value type is valid. self.assert_type_is_valid(&input.value_type, input.span); - // Check that a mapping's value type is not a tuple, record or mapping. + // Check that a mapping's value type is not a future, tuple, record or mapping. match input.value_type.clone() { + Type::Future(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "future", input.span)), Type::Tuple(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "tuple", input.span)), Type::Composite(struct_type) => { if let Some(struct_) = self.lookup_struct(struct_type.program, struct_type.id.name) { @@ -229,17 +237,12 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { fn visit_function(&mut self, function: &'a Function) { // Check that the function's annotations are valid. - let valid_annotations = [sym::should_fail, sym::native_test, sym::interpreted_test]; for annotation in function.annotations.iter() { - // All Leo annotations currently apply only to test code. - if !self.is_test || !valid_annotations.contains(&annotation.identifier.name) { - // TODO: Change to compiler warning. - self.emit_err(TypeCheckerError::unknown_annotation(annotation, annotation.span)); - } + self.check_annotation(annotation); } // `interpret` can only be used for tests. - if !self.is_test && function.variant == Variant::Interpret { + if !self.build_tests && function.variant == Variant::Interpret { self.emit_err(TypeCheckerError::interpret_outside_test(function.span)); } @@ -270,19 +273,6 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { // Query helper function to type check function parameters and outputs. self.check_function_signature(function); - if self.scope_state.variant == Some(Variant::AsyncFunction) { - // Initialize the list of input futures. Each one must be awaited before the end of the function. - self.await_checker.set_futures( - function - .input - .iter() - .filter_map(|input| { - if let Type::Future(_) = input.type_.clone() { Some(input.identifier.name) } else { None } - }) - .collect(), - ); - } - if function.variant == Variant::Function && function.input.is_empty() { self.emit_err(TypeCheckerError::empty_function_arglist(function.span)); } @@ -304,59 +294,6 @@ impl<'a, N: Network> ProgramVisitor<'a> for TypeChecker<'a, N> { if self.scope_state.variant == Some(Variant::AsyncTransition) && !self.scope_state.has_called_finalize { self.emit_err(TypeCheckerError::async_transition_must_call_async_function(function.span)); } - - // Check that all futures were awaited exactly once. - if self.scope_state.variant == Some(Variant::AsyncFunction) { - // Throw error if not all futures awaits even appear once. - if !self.await_checker.static_to_await.is_empty() { - self.emit_err(TypeCheckerError::future_awaits_missing( - self.await_checker - .static_to_await - .clone() - .iter() - .map(|f| f.to_string()) - .collect::>() - .join(", "), - function.span(), - )); - } else if self.await_checker.enabled && !self.await_checker.to_await.is_empty() { - // Tally up number of paths that are unawaited and number of paths that are awaited more than once. - let (num_paths_unawaited, num_paths_duplicate_awaited, num_perfect) = - self.await_checker.to_await.iter().fold((0, 0, 0), |(unawaited, duplicate, perfect), path| { - ( - unawaited + if !path.elements.is_empty() { 1 } else { 0 }, - duplicate + if path.counter > 0 { 1 } else { 0 }, - perfect + if path.counter > 0 || !path.elements.is_empty() { 0 } else { 1 }, - ) - }); - - // Throw error if there does not exist a path in which all futures are awaited exactly once. - if num_perfect == 0 { - self.emit_err(TypeCheckerError::no_path_awaits_all_futures_exactly_once( - self.await_checker.to_await.len(), - function.span(), - )); - } - - // Throw warning if some futures are awaited more than once in some paths. - if num_paths_unawaited > 0 { - self.emit_warning(TypeCheckerWarning::some_paths_do_not_await_all_futures( - self.await_checker.to_await.len(), - num_paths_unawaited, - function.span(), - )); - } - - // Throw warning if not all futures are awaited in some paths. - if num_paths_duplicate_awaited > 0 { - self.emit_warning(TypeCheckerWarning::some_paths_contain_duplicate_future_awaits( - self.await_checker.to_await.len(), - num_paths_duplicate_awaited, - function.span(), - )); - } - } - } } fn visit_function_stub(&mut self, input: &'a FunctionStub) { diff --git a/compiler/passes/src/type_checking/check_statements.rs b/compiler/passes/src/type_checking/check_statements.rs index ca10467b7e..6324281f5b 100644 --- a/compiler/passes/src/type_checking/check_statements.rs +++ b/compiler/passes/src/type_checking/check_statements.rs @@ -15,7 +15,7 @@ // along with the Leo library. If not, see . use super::*; -use crate::{ConditionalTreeNode, TypeChecker, VariableSymbol, VariableType}; +use crate::{TypeChecker, VariableSymbol, VariableType}; use leo_ast::{ Type::{Future, Tuple}, @@ -23,8 +23,6 @@ use leo_ast::{ }; use leo_errors::TypeCheckerError; -use itertools::Itertools; - impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { fn visit_statement(&mut self, input: &'a Statement) { // No statements can follow a return statement. @@ -85,7 +83,7 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { } // If the variable exists and its in an async function, then check that it is in the current scope. - // Note that this unwrap is safe because the scope state is initalized before traversing the function. + // Note that this unwrap is safe because the scope state is initialized before traversing the function. if self.scope_state.variant.unwrap().is_async_function() && self.scope_state.is_conditional && self @@ -133,26 +131,12 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { // Set the `is_conditional` flag. let previous_is_conditional = core::mem::replace(&mut self.scope_state.is_conditional, true); - // Create scope for checking awaits in `then` branch of conditional. - let current_bst_nodes: Vec = match self - .await_checker - .create_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), input.span) - { - Ok(nodes) => nodes, - Err(warn) => return self.emit_warning(warn), - }; - // Visit block. self.visit_block(&input.then); // Store the `has_return` flag for the then-block. then_block_has_return = self.scope_state.has_return; - // Exit scope for checking awaits in `then` branch of conditional. - let saved_paths = self - .await_checker - .exit_then_scope(self.scope_state.variant == Some(Variant::AsyncFunction), current_bst_nodes); - if let Some(otherwise) = &input.otherwise { // Set the `has_return` flag for the otherwise-block. self.scope_state.has_return = otherwise_block_has_return; @@ -170,9 +154,6 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { otherwise_block_has_return = self.scope_state.has_return; } - // Update the set of all possible BST paths. - self.await_checker.exit_statement_scope(self.scope_state.variant == Some(Variant::AsyncFunction), saved_paths); - // Restore the previous `has_return` flag. self.scope_state.has_return = previous_has_return || (then_block_has_return && otherwise_block_has_return); // Restore the previous `is_conditional` flag. @@ -265,7 +246,7 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { // Insert the variables into the symbol table. match &input.place { Expression::Identifier(identifier) => { - self.insert_variable(inferred_type.clone(), identifier, input.type_.clone(), 0, identifier.span) + self.insert_variable(inferred_type.clone(), identifier, input.type_.clone(), identifier.span) } Expression::Tuple(tuple_expression) => { let tuple_type = match &input.type_ { @@ -282,9 +263,13 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { )); } - for ((index, expr), type_) in - tuple_expression.elements.iter().enumerate().zip_eq(tuple_type.elements().iter()) - { + for i in 0..tuple_expression.elements.len() { + let inferred = if let Some(Type::Tuple(inferred_tuple)) = &inferred_type { + inferred_tuple.elements().get(i).cloned() + } else { + None + }; + let expr = &tuple_expression.elements[i]; let identifier = match expr { Expression::Identifier(identifier) => identifier, _ => { @@ -292,7 +277,7 @@ impl<'a, N: Network> StatementVisitor<'a> for TypeChecker<'a, N> { .emit_err(TypeCheckerError::lhs_tuple_element_must_be_an_identifier(expr.span())); } }; - self.insert_variable(inferred_type.clone(), identifier, type_.clone(), index, identifier.span); + self.insert_variable(inferred, identifier, tuple_type.elements()[i].clone(), identifier.span); } } _ => self.emit_err(TypeCheckerError::lhs_must_be_identifier_or_tuple(input.place.span())), diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index 21b6f7ac79..fdf05ba30f 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -21,18 +21,19 @@ use crate::{ TypeTable, VariableSymbol, VariableType, - type_checking::{await_checker::AwaitChecker, scope_state::ScopeState}, + type_checking::scope_state::ScopeState, }; use leo_ast::*; use leo_errors::{TypeCheckerError, TypeCheckerWarning, emitter::Handler}; -use leo_span::{Span, Symbol}; +use leo_span::{Span, Symbol, sym}; use snarkvm::console::network::Network; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; -use std::{cell::RefCell, marker::PhantomData}; +use snarkvm::prelude::PrivateKey; +use std::{cell::RefCell, marker::PhantomData, str::FromStr}; pub struct TypeChecker<'a, N: Network> { /// The symbol table for the program. @@ -47,14 +48,12 @@ pub struct TypeChecker<'a, N: Network> { pub(crate) handler: &'a Handler, /// The state of the current scope being traversed. pub(crate) scope_state: ScopeState, - /// Struct to store the state relevant to checking all futures are awaited. - pub(crate) await_checker: AwaitChecker, /// Mapping from async function name to the inferred input types. pub(crate) async_function_input_types: IndexMap>, /// The set of used composites. pub(crate) used_structs: IndexSet, /// Are we compiling tests? - pub(crate) is_test: bool, + pub(crate) build_tests: bool, // Allows the type checker to be generic over the network. phantom: PhantomData, } @@ -105,14 +104,7 @@ const MAGNITUDE_TYPES: [Type; 3] = impl<'a, N: Network> TypeChecker<'a, N> { /// Returns a new type checker given a symbol table and error handler. - pub fn new( - symbol_table: SymbolTable, - type_table: &'a TypeTable, - handler: &'a Handler, - max_depth: usize, - disabled: bool, - is_test: bool, - ) -> Self { + pub fn new(symbol_table: SymbolTable, type_table: &'a TypeTable, handler: &'a Handler, is_test: bool) -> Self { let struct_names = symbol_table.structs.keys().map(|loc| loc.name).collect(); let function_names = symbol_table.functions.keys().map(|loc| loc.name).collect(); @@ -124,10 +116,9 @@ impl<'a, N: Network> TypeChecker<'a, N> { call_graph: CallGraph::new(function_names), handler, scope_state: ScopeState::new(), - await_checker: AwaitChecker::new(max_depth, !disabled), async_function_input_types: IndexMap::new(), used_structs: IndexSet::new(), - is_test, + build_tests: is_test, phantom: Default::default(), } } @@ -1033,6 +1024,16 @@ impl<'a, N: Network> TypeChecker<'a, N> { Some(Type::Boolean) } CoreFunction::FutureAwait => Some(Type::Unit), + CoreFunction::CheatCodePrintMapping => { + // Check that the argument is a mapping. + let _ = self.assert_mapping_type(&arguments[0].0, arguments[0].1); + Some(Type::Unit) + } + CoreFunction::CheatCodeSetBlockHeight => { + // Assert that the argument is a u32. + self.assert_type(&arguments[0].0, &Type::Integer(IntegerType::U32), arguments[0].1); + Some(Type::Unit) + } } } @@ -1100,6 +1101,8 @@ impl<'a, N: Network> TypeChecker<'a, N> { } // Check that the array element type is valid. match array_type.element_type() { + // Array elements cannot be futures. + Type::Future(_) => self.emit_err(TypeCheckerError::array_element_cannot_be_future(span)), // Array elements cannot be tuples. Type::Tuple(_) => self.emit_err(TypeCheckerError::array_element_cannot_be_tuple(span)), // Array elements cannot be records. @@ -1231,10 +1234,21 @@ impl<'a, N: Network> TypeChecker<'a, N> { } } - // Add function inputs to the symbol table. Futures have already been added. - if !matches!(&input_var.type_(), &Type::Future(_)) { + if matches!(&input_var.type_(), Type::Future(_)) { + // Future parameters may only appear in async functions. + if !matches!(self.scope_state.variant, Some(Variant::AsyncFunction)) { + self.emit_err(TypeCheckerError::no_future_parameters(input_var.span())); + } + } + + let location = Location::new(None, input_var.identifier().name); + if !matches!(&input_var.type_(), Type::Future(_)) + || self.symbol_table.borrow().lookup_variable_in_current_scope(location.clone()).is_none() + { + // Add function inputs to the symbol table. If inference happened properly above, futures were already added. + // But if a future was not added, add it now so as not to give confusing error messages. if let Err(err) = self.symbol_table.borrow_mut().insert_variable( - Location::new(None, input_var.identifier().name), + location.clone(), self.scope_state.program_name, VariableSymbol { type_: input_var.type_().clone(), @@ -1312,58 +1326,24 @@ impl<'a, N: Network> TypeChecker<'a, N> { struct_ } - /// Type checks the awaiting of a future. - pub(crate) fn assert_future_await(&mut self, future: &Option<&Expression>, span: Span) { - // Make sure that it is an identifier expression. - let future_variable = match future { - Some(Expression::Identifier(name)) => name, - _ => { - return self.emit_err(TypeCheckerError::invalid_await_call(span)); - } + /// Inserts variable to symbol table. + pub(crate) fn insert_variable(&mut self, inferred_type: Option, name: &Identifier, type_: Type, span: Span) { + let is_future = match &type_ { + Type::Future(..) => true, + Type::Tuple(tuple_type) if matches!(tuple_type.elements().last(), Some(Type::Future(..))) => true, + _ => false, }; - // Make sure that the future is defined. - match self.symbol_table.borrow().lookup_variable(Location::new(None, future_variable.name)) { - Some(var) => { - if !matches!(&var.type_, &Type::Future(_)) { - self.emit_err(TypeCheckerError::expected_future(future_variable.name, future_variable.span())); - } - // Mark the future as consumed. - self.await_checker.remove(future_variable); - } - None => { - self.emit_err(TypeCheckerError::expected_future(future_variable.name, future_variable.span())); - } + if is_future { + self.scope_state.futures.insert(name.name, self.scope_state.call_location.clone().unwrap()); } - } - /// Inserts variable to symbol table. - pub(crate) fn insert_variable( - &mut self, - inferred_type: Option, - name: &Identifier, - type_: Type, - index: usize, - span: Span, - ) { - let ty: Type = if let Type::Future(_) = type_ { - // Need to insert the fully inferred future type, or else will just be default future type. - let ret = match inferred_type.unwrap() { - Type::Future(future) => Type::Future(future), - Type::Tuple(tuple) => match tuple.elements().get(index) { - Some(Type::Future(future)) => Type::Future(future.clone()), - _ => unreachable!("Parsing guarantees that the inferred type is a future."), - }, - _ => { - unreachable!("TYC guarantees that the inferred type is a future, or tuple containing futures.") - } - }; - // Insert future into list of futures for the function. - self.scope_state.futures.insert(name.name, self.scope_state.call_location.clone().unwrap()); - ret - } else { - type_ + let ty = match (is_future, inferred_type) { + (false, _) => type_, + (true, Some(inferred)) => inferred, + (true, None) => unreachable!("Type checking guarantees the inferred type is present"), }; + // Insert the variable into the symbol table. if let Err(err) = self.symbol_table.borrow_mut().insert_variable( Location::new(None, name.name), @@ -1385,6 +1365,76 @@ impl<'a, N: Network> TypeChecker<'a, N> { self.handler.emit_err(TypeCheckerError::invalid_operation_outside_finalize(name, span)) } } + + // Check if the annotation is valid. + pub(crate) fn check_annotation(&mut self, annotation: &Annotation) { + match annotation.identifier.name { + sym::test => { + // Check the annotation body. + for (key, value) in annotation.data.iter() { + // Check that the key and associated value is valid. + if key.name == Symbol::intern("private_key") { + // Attempt to parse the value as a private key. + match value { + None => self.emit_warning(TypeCheckerWarning::missing_annotation_value( + annotation.identifier, + key, + key.span, + )), + Some(string) => { + if let Err(err) = PrivateKey::::from_str(&string) { + self.emit_warning(TypeCheckerWarning::invalid_annotation_value( + annotation.identifier, + key, + string, + err, + key.span, + )); + } + } + } + } else if key.name == Symbol::intern("seed") || key.name == Symbol::intern("batch") { + // Attempt to parse the value as a u64. + match value { + None => self.emit_warning(TypeCheckerWarning::missing_annotation_value( + annotation.identifier, + key, + key.span, + )), + Some(string) => { + if let Err(err) = string.parse::() { + self.emit_warning(TypeCheckerWarning::invalid_annotation_value( + annotation.identifier, + key, + string, + err, + key.span, + )); + } + } + } + } else if key.name == Symbol::intern("should_fail") { + // Check that there is no value associated with the key. + if let Some(string) = value { + self.emit_warning(TypeCheckerWarning::unexpected_annotation_value( + annotation.identifier, + key, + string, + key.span, + )); + } + } else { + self.emit_warning(TypeCheckerWarning::unknown_annotation_key( + annotation.identifier, + key, + key.span, + )) + } + } + } + _ => self.emit_warning(TypeCheckerWarning::unknown_annotation(annotation.identifier, annotation.span)), + } + } } fn types_to_string(types: &[Type]) -> String { diff --git a/compiler/passes/src/type_checking/mod.rs b/compiler/passes/src/type_checking/mod.rs index 6fde23185b..abeff42142 100644 --- a/compiler/passes/src/type_checking/mod.rs +++ b/compiler/passes/src/type_checking/mod.rs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -mod await_checker; - pub mod check_expressions; pub mod check_program; @@ -36,11 +34,11 @@ use leo_errors::{Result, emitter::Handler}; use snarkvm::prelude::Network; impl<'a, N: Network> Pass for TypeChecker<'a, N> { - type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable, usize, bool, bool); + type Input = (&'a Ast, &'a Handler, SymbolTable, &'a TypeTable, bool); type Output = Result<(SymbolTable, StructGraph, CallGraph)>; - fn do_pass((ast, handler, st, tt, max_depth, await_checking, is_test): Self::Input) -> Self::Output { - let mut visitor = TypeChecker::::new(st, tt, handler, max_depth, await_checking, is_test); + fn do_pass((ast, handler, st, tt, is_test): Self::Input) -> Self::Output { + let mut visitor = TypeChecker::::new(st, tt, handler, is_test); visitor.visit_program(ast.as_repr()); handler.last_err().map_err(|e| *e)?; diff --git a/compiler/span/Cargo.toml b/compiler/span/Cargo.toml index 7b270cfee7..dc0b1d0fd7 100644 --- a/compiler/span/Cargo.toml +++ b/compiler/span/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-span" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Span handling for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/compiler/span/src/source_map.rs b/compiler/span/src/source_map.rs index 955992b3b5..36240e288b 100644 --- a/compiler/span/src/source_map.rs +++ b/compiler/span/src/source_map.rs @@ -83,7 +83,7 @@ impl SourceMap { } /// Find the source file containing `pos`. - fn find_source_file(&self, pos: BytePos) -> Option> { + pub fn find_source_file(&self, pos: BytePos) -> Option> { Some(self.inner.borrow().source_files[self.find_source_file_index(pos)?].clone()) } diff --git a/compiler/span/src/symbol.rs b/compiler/span/src/symbol.rs index b1c845aa34..aa25f7c9c3 100644 --- a/compiler/span/src/symbol.rs +++ b/compiler/span/src/symbol.rs @@ -204,6 +204,11 @@ symbols! { verify, Await: "await", + // CheatCodes + CheatCode, + print_mapping, + set_block_height, + // types address, bool, @@ -233,8 +238,7 @@ symbols! { // annotations should_fail, - native_test, - interpreted_test, + test, // general keywords As: "as", diff --git a/docs/grammar/Cargo.toml b/docs/grammar/Cargo.toml index b196b09c54..81389b3863 100644 --- a/docs/grammar/Cargo.toml +++ b/docs/grammar/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-abnf" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "ABNF to Markdown converter for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/errors/Cargo.toml b/errors/Cargo.toml index 021b8d618f..a75f1668a3 100644 --- a/errors/Cargo.toml +++ b/errors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-errors" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Errors for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/errors/src/errors/cli/cli_errors.rs b/errors/src/errors/cli/cli_errors.rs index 710136e25d..0ad5d805ea 100644 --- a/errors/src/errors/cli/cli_errors.rs +++ b/errors/src/errors/cli/cli_errors.rs @@ -320,4 +320,18 @@ create_messages!( msg: format!("Failed to render table.\nError: {error}"), help: None, } + + @backtraced + general_cli_error { + args: (error: impl Display), + msg: format!("{error}"), + help: None, + } + + @backtraced + general_cli_error_with_help { + args: (error: impl Display, help: impl Display), + msg: format!("{error}"), + help: Some(format!("{help}")), + } ); diff --git a/errors/src/errors/compiler/compiler_errors.rs b/errors/src/errors/compiler/compiler_errors.rs index e92adfd80c..8590d3ede8 100644 --- a/errors/src/errors/compiler/compiler_errors.rs +++ b/errors/src/errors/compiler/compiler_errors.rs @@ -73,8 +73,8 @@ create_messages!( @formatted imported_program_not_found { - args: (main_program_name: impl Display, dependency_name: impl Display), - msg: format!("`{main_program_name}` imports `{dependency_name}.aleo`, but `{dependency_name}.aleo` is not found in program manifest. Use `leo add --help` for more information on how to add a dependency."), - help: None, + args: (dependency_name: impl Display), + msg: format!("`{dependency_name}.aleo` is not found in program manifest."), + help: Some("Use `leo add --help` for more information on how to add a dependency.".to_string()), } ); diff --git a/errors/src/errors/interpreter_halt.rs b/errors/src/errors/interpreter_halt.rs new file mode 100644 index 0000000000..a8fe561dcf --- /dev/null +++ b/errors/src/errors/interpreter_halt.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use std::fmt; + +use leo_span::Span; + +/// Represents the interpreter halting, which should not be considered an +/// actual runtime error. +#[derive(Clone, Debug, Error)] +pub struct InterpreterHalt { + /// Optional Span where the halt occurred. + span: Option, + + /// User visible message. + message: String, +} + +impl InterpreterHalt { + pub fn new(message: String) -> Self { + InterpreterHalt { span: None, message } + } + + pub fn new_spanned(message: String, span: Span) -> Self { + InterpreterHalt { span: Some(span), message } + } + + pub fn span(&self) -> Option { + self.span + } +} + +impl fmt::Display for InterpreterHalt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} diff --git a/errors/src/errors/mod.rs b/errors/src/errors/mod.rs index 95ca143421..ac602c91db 100644 --- a/errors/src/errors/mod.rs +++ b/errors/src/errors/mod.rs @@ -37,6 +37,9 @@ pub use self::flattener::*; pub mod loop_unroller; pub use self::loop_unroller::*; +pub mod interpreter_halt; +pub use self::interpreter_halt::*; + /// Contains the Package error definitions. pub mod package; pub use self::package::*; @@ -45,6 +48,10 @@ pub use self::package::*; pub mod parser; pub use self::parser::*; +/// Contains the Static Analyzer error definitions. +pub mod static_analyzer; +pub use self::static_analyzer::*; + /// Contains the Type Checker error definitions. pub mod type_checker; pub use self::type_checker::*; @@ -66,12 +73,17 @@ pub enum LeoError { /// Represents a Compiler Error in a Leo Error. #[error(transparent)] CompilerError(#[from] CompilerError), + #[error(transparent)] + InterpreterHalt(#[from] InterpreterHalt), /// Represents a Package Error in a Leo Error. #[error(transparent)] PackageError(#[from] PackageError), /// Represents a Parser Error in a Leo Error. #[error(transparent)] ParserError(#[from] ParserError), + /// Represents a Static Analyzer Error in a Leo Error. + #[error(transparent)] + StaticAnalyzerError(#[from] StaticAnalyzerError), /// Represents a Type Checker Error in a Leo Error. #[error(transparent)] TypeCheckerError(#[from] TypeCheckerError), @@ -104,12 +116,14 @@ impl LeoError { CliError(error) => error.error_code(), ParserError(error) => error.error_code(), PackageError(error) => error.error_code(), + StaticAnalyzerError(error) => error.error_code(), TypeCheckerError(error) => error.error_code(), LoopUnrollerError(error) => error.error_code(), FlattenError(error) => error.error_code(), UtilError(error) => error.error_code(), LastErrorCode(_) => unreachable!(), Anyhow(_) => "SnarkVM Error".to_string(), // todo: implement error codes for snarkvm errors. + InterpreterHalt(_) => "Interpreter Halt".to_string(), } } @@ -123,12 +137,14 @@ impl LeoError { CliError(error) => error.exit_code(), ParserError(error) => error.exit_code(), PackageError(error) => error.exit_code(), + StaticAnalyzerError(error) => error.exit_code(), TypeCheckerError(error) => error.exit_code(), LoopUnrollerError(error) => error.exit_code(), FlattenError(error) => error.exit_code(), UtilError(error) => error.exit_code(), LastErrorCode(code) => *code, Anyhow(_) => 11000, // todo: implement exit codes for snarkvm errors. + InterpreterHalt(_) => 1, } } } @@ -140,6 +156,9 @@ pub enum LeoWarning { /// Represents an Parser Warning in a Leo Warning. #[error(transparent)] ParserWarning(#[from] ParserWarning), + /// Represents a Static Analyzer Warning in a Leo Warning. + #[error(transparent)] + StaticAnalyzerWarning(#[from] StaticAnalyzerWarning), /// Represents a Type Checker Warning in a Leo Warning. #[error(transparent)] TypeCheckerWarning(#[from] TypeCheckerWarning), @@ -153,6 +172,7 @@ impl LeoWarning { match self { ParserWarning(warning) => warning.warning_code(), TypeCheckerWarning(warning) => warning.warning_code(), + StaticAnalyzerWarning(warning) => warning.warning_code(), } } } diff --git a/errors/src/errors/package/package_errors.rs b/errors/src/errors/package/package_errors.rs index 7f1a12fa9a..d9cf21d14c 100644 --- a/errors/src/errors/package/package_errors.rs +++ b/errors/src/errors/package/package_errors.rs @@ -432,4 +432,11 @@ create_messages!( msg: format!("Failed to load leo project at path {path}"), help: Some("Make sure that the path is correct and that the project exists.".to_string()), } + + @backtraced + failed_to_create_test_directory { + args: (error: impl ErrorArg), + msg: format!("Failed to create test directory {error}."), + help: None, + } ); diff --git a/errors/src/errors/parser/parser_errors.rs b/errors/src/errors/parser/parser_errors.rs index 04a3707cfa..2f15a54a7c 100644 --- a/errors/src/errors/parser/parser_errors.rs +++ b/errors/src/errors/parser/parser_errors.rs @@ -372,4 +372,11 @@ create_messages!( msg: format!("Identifier {ident} is too long ({length} bytes; maximum is {max_length})"), help: None, } + + @formatted + expected_string_literal_in_annotation { + args: (), + msg: format!("Expected a string literal in annotation body"), + help: Some("The body of an annotation can either be an identifier or an identifier-string par. For example, `foo`, `foo = \"bar\"`".to_string()), + } ); diff --git a/errors/src/errors/static_analyzer/mod.rs b/errors/src/errors/static_analyzer/mod.rs new file mode 100644 index 0000000000..2fda26a5f9 --- /dev/null +++ b/errors/src/errors/static_analyzer/mod.rs @@ -0,0 +1,22 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +/// This module contains the static analysis error definitions. +pub mod static_analyzer_error; +pub use self::static_analyzer_error::*; + +pub mod static_analyzer_warning; +pub use self::static_analyzer_warning::*; diff --git a/errors/src/errors/static_analyzer/static_analyzer_error.rs b/errors/src/errors/static_analyzer/static_analyzer_error.rs new file mode 100644 index 0000000000..20f5817d44 --- /dev/null +++ b/errors/src/errors/static_analyzer/static_analyzer_error.rs @@ -0,0 +1,69 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::create_messages; +use std::fmt::{Debug, Display}; + +// TODO: Consolidate errors. + +create_messages!( + /// StaticAnalyzer enum that represents all the errors for static analysis. + StaticAnalyzerError, + code_mask: 4000i32, + code_prefix: "SAZ", + + @formatted + no_path_awaits_all_futures_exactly_once { + args: (num_total_paths: impl Display), + msg: format!("Futures must be awaited exactly once. Out of `{num_total_paths}`, there does not exist a single path in which all futures are awaited exactly once."), + help: Some("Ex: for `f: Future` call `f.await()` to await a future. Remove duplicate future await redundancies, and add future awaits for un-awaited futures.".to_string()), + } + + @formatted + future_awaits_missing { + args: (unawaited: impl Display), + msg: format!("The following futures were never awaited: {unawaited}"), + help: Some("Ex: for `f: Future` call `f.await()` to await a future.".to_string()), + } + + @formatted + invalid_await_call { + args: (), + msg: "Not a valid await call.".to_string(), + help: Some("Ex: for `f: Future` call `f.await()` or `Future::await(f)` to await a future.".to_string()), + } + + @formatted + expected_future { + args: (type_: impl Display), + msg: format!("Expected a future, but found `{type_}`"), + help: Some("Only futures can be awaited.".to_string()), + } + + @formatted + async_transition_call_with_future_argument { + args: (function_name: impl Display), + msg: format!("The call to {function_name} will result in failed executions on-chain."), + help: Some("There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.".to_string()), + } + + @formatted + misplaced_future { + args: (), + msg: "A future may not be used in this way".to_string(), + help: Some("Futures should be created, assigned to a variable, and consumed without being moved or reassigned.".to_string()), + } +); diff --git a/errors/src/errors/static_analyzer/static_analyzer_warning.rs b/errors/src/errors/static_analyzer/static_analyzer_warning.rs new file mode 100644 index 0000000000..316665d4df --- /dev/null +++ b/errors/src/errors/static_analyzer/static_analyzer_warning.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::create_messages; + +use std::fmt::Display; + +create_messages!( + /// ParserWarning enum that represents all the warnings for static analysis + StaticAnalyzerWarning, + code_mask: 4000i32, + code_prefix: "SAZ", + + @formatted + some_paths_do_not_await_all_futures { + args: (num_total_paths: impl Display, num_unawaited_paths: impl Display), + msg: format!("Not all paths through the function await all futures. {num_unawaited_paths}/{num_total_paths} paths contain at least one future that is never awaited."), + help: Some("Ex: `f.await()` to await a future. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.".to_string()), + } + + @formatted + some_paths_contain_duplicate_future_awaits { + args: (num_total_paths: impl Display, num_duplicate_await_paths: impl Display), + msg: format!("Some paths through the function contain duplicate future awaits. {num_duplicate_await_paths}/{num_total_paths} paths contain at least one future that is awaited more than once."), + help: Some("Look at the times `.await()` is called, and try to reduce redundancies. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.".to_string()), + } + + @formatted + max_conditional_block_depth_exceeded { + args: (max: impl Display), + msg: format!("The type checker has exceeded the max depth of nested conditional blocks: {max}."), + help: Some("Re-run with a larger maximum depth using the `--conditional_block_max_depth` build option. Ex: `leo run main --conditional_block_max_depth 25`.".to_string()), + } + + @formatted + future_not_awaited_in_order { + args: (future_name: impl Display), + msg: format!("The future `{}` is not awaited in the order in which they were passed in to the `async` function.", future_name), + help: Some("While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.".to_string()), + } +); diff --git a/errors/src/errors/type_checker/type_checker_error.rs b/errors/src/errors/type_checker/type_checker_error.rs index 14989fe86e..8093672711 100644 --- a/errors/src/errors/type_checker/type_checker_error.rs +++ b/errors/src/errors/type_checker/type_checker_error.rs @@ -269,7 +269,7 @@ create_messages!( help: Some("Remove the code in the loop body that always returns.".to_string()), } - // TODO: Consider emitting a warning instead of an error. + // TODO This error is unused. Remove it in a future version. @formatted unknown_annotation { args: (annotation: impl Display), @@ -419,7 +419,6 @@ create_messages!( } // TODO: Consider changing this to a warning. - @formatted assign_unit_expression_to_variable { args: (), @@ -742,6 +741,7 @@ create_messages!( help: Some(" Future arguments must be addressed by their index. Ex: `f.1.3`.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted no_path_awaits_all_futures_exactly_once { args: (num_total_paths: impl Display), @@ -749,6 +749,7 @@ create_messages!( help: Some("Ex: for `f: Future` call `f.await()` to await a future. Remove duplicate future await redundancies, and add future awaits for un-awaited futures.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted future_awaits_missing { args: (unawaited: impl Display), @@ -763,6 +764,7 @@ create_messages!( help: Some("Futures can only be defined as the result of async calls.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted invalid_await_call { args: (), @@ -777,6 +779,7 @@ create_messages!( help: Some("Ex: for `f: Future` call `f.await()` or `Future::await(f)` to await a future.".to_string()), } + // TODO: This error is deprecated. Remove. @formatted expected_future { args: (type_: impl Display), @@ -895,6 +898,27 @@ create_messages!( help: None, } + @formatted + composite_data_type_cannot_contain_future { + args: (data_type: impl Display), + msg: format!("A {data_type} cannot contain a future."), + help: None, + } + + @formatted + array_element_cannot_be_future { + args: (), + msg: format!("An array cannot have a future as an element type."), + help: None, + } + + @formatted + no_future_parameters { + args: (), + msg: format!("Futures may only appear as parameters to async functions."), + help: None, + } + @formatted interpret_outside_test { args: (), diff --git a/errors/src/errors/type_checker/type_checker_warning.rs b/errors/src/errors/type_checker/type_checker_warning.rs index 2ec2fa53ca..e33587cd74 100644 --- a/errors/src/errors/type_checker/type_checker_warning.rs +++ b/errors/src/errors/type_checker/type_checker_warning.rs @@ -38,6 +38,7 @@ create_messages!( help: Some("Look at the times `.await()` is called, and try to reduce redundancies. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.".to_string()), } + // TODO: This warning is deprecated, remove it in the future. @formatted async_function_is_never_called_by_transition_function { args: (name: impl Display), @@ -51,4 +52,39 @@ create_messages!( msg: format!("The type checker has exceeded the max depth of nested conditional blocks: {max}."), help: Some("Re-run with a larger maximum depth using the `--conditional_block_max_depth` build option. Ex: `leo run main --conditional_block_max_depth 25`.".to_string()), } + + @formatted + unknown_annotation { + args: (annotation: impl Display), + msg: format!("Unknown annotation: `{annotation}`."), + help: None, + } + + @formatted + unknown_annotation_key { + args: (annotation: impl Display, key: impl Display), + msg: format!("Unknown key `{key}` in annotation `{annotation}`."), + help: None, + } + + @formatted + missing_annotation_value { + args: (annotation: impl Display, key: impl Display), + msg: format!("Missing value for key `{key}` in annotation `{annotation}`."), + help: None, + } + + @formatted + invalid_annotation_value { + args: (annotation: impl Display, key: impl Display, value: impl Display, error: impl Display), + msg: format!("Invalid value `{value}` for key `{key}` in annotation `{annotation}`. Error: {error}"), + help: None, + } + + @formatted + unexpected_annotation_value { + args: (annotation: impl Display, key: impl Display, value: impl Display), + msg: format!("Unexpected value `{value}` for key `{key}` in annotation `{annotation}`."), + help: None, + } ); diff --git a/examples b/examples index b75bb77851..155f8410fb 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit b75bb77851667f6ece0af4cacfc27715aa7186aa +Subproject commit 155f8410fb199c971b50950882a74ff593f8f5d9 diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml new file mode 100644 index 0000000000..0e45902f41 --- /dev/null +++ b/interpreter/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "leo-interpreter" +version = "2.4.1" +authors = [ "The Leo Team " ] +description = "Interpreter for the Leo programming language" +homepage = "https://leo-lang.org" +repository = "https://github.com/ProvableHQ/leo" +keywords = [ + "aleo", + "cryptography", + "leo", + "programming-language", + "zero-knowledge" +] +categories = [ "compilers", "cryptography", "web-programming" ] +include = [ "Cargo.toml", "src", "README.md", "LICENSE.md" ] +license = "GPL-3.0" +edition = "2021" +rust-version = "1.82.0" + +[dependencies.snarkvm] +workspace = true + +[dependencies.snarkvm-circuit] +version = "1.0.0" + +[dependencies.snarkvm-synthesizer-program] +version = "1.0.0" + +[dependencies.leo-ast] +workspace = true + +[dependencies.leo-passes] +workspace = true + +[dependencies.leo-errors] +workspace = true + +[dependencies.leo-package] +workspace = true + +[dependencies.leo-parser] +workspace = true + +[dependencies.leo-span] +workspace = true + +[dependencies.colored] +workspace = true + +[dependencies.crossterm] +version = "0.28.1" + +[dependencies.indexmap] +workspace = true + +[dependencies.dialoguer] +version = "0.11.0" +features = [ "history" ] + +[dependencies.rand] +workspace = true + +[dependencies.rand_chacha] +workspace = true + +[dependencies.ratatui] +version = "0.29.0" + +[dependencies.toml] +workspace = true + +[dependencies.tui-input] +version = "0.11.1" + +[dev-dependencies.leo-test-framework] +path = "../tests/test-framework" + +[dev-dependencies.serial_test] +version = "3.1.1" + +[dev-dependencies.tempfile] +workspace = true diff --git a/interpreter/src/cursor.rs b/interpreter/src/cursor.rs new file mode 100644 index 0000000000..624e7dd209 --- /dev/null +++ b/interpreter/src/cursor.rs @@ -0,0 +1,2756 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; + +use leo_ast::{ + AccessExpression, + AssertVariant, + BinaryOperation, + Block, + CoreConstant, + CoreFunction, + Expression, + Function, + GroupLiteral, + IntegerType, + Literal, + Statement, + Type, + UnaryOperation, + Variant, +}; +use leo_errors::{InterpreterHalt, Result}; +use leo_span::{Span, Symbol, sym}; + +use snarkvm::prelude::{ + CastLossy as _, + Closure as SvmClosure, + Double as _, + Finalize as SvmFinalize, + Function as SvmFunctionParam, + Inverse as _, + Network as _, + Pow as _, + ProgramID, + Square as _, + SquareRoot as _, + TestnetV0, + ToBits, +}; + +use indexmap::{IndexMap, IndexSet}; +use rand::Rng as _; +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; +use std::{cmp::Ordering, collections::HashMap, fmt, mem, str::FromStr as _}; + +pub type Closure = SvmClosure; +pub type Finalize = SvmFinalize; +pub type SvmFunction = SvmFunctionParam; + +/// Names associated to values in a function being executed. +#[derive(Clone, Debug)] +pub struct FunctionContext { + program: Symbol, + pub caller: SvmAddress, + names: HashMap, + accumulated_futures: Future, + is_async: bool, +} + +/// A stack of contexts, building with the function call stack. +#[derive(Clone, Debug, Default)] +pub struct ContextStack { + contexts: Vec, + current_len: usize, +} + +impl ContextStack { + fn len(&self) -> usize { + self.current_len + } + + fn push(&mut self, program: Symbol, caller: SvmAddress, is_async: bool) { + if self.current_len == self.contexts.len() { + self.contexts.push(FunctionContext { + program, + caller, + names: HashMap::new(), + accumulated_futures: Default::default(), + is_async, + }); + } + self.contexts[self.current_len].accumulated_futures.0.clear(); + self.contexts[self.current_len].names.clear(); + self.contexts[self.current_len].caller = caller; + self.contexts[self.current_len].program = program; + self.contexts[self.current_len].is_async = is_async; + self.current_len += 1; + } + + pub fn pop(&mut self) { + // We never actually pop the underlying Vec + // so we can reuse the storage of the hash + // tables. + assert!(self.len() > 0); + self.current_len -= 1; + self.contexts[self.current_len].names.clear(); + } + + /// Get the future accumulated by awaiting futures in the current function call. + /// + /// If the current code being interpreted is not in an async function, this + /// will of course be empty. + fn get_future(&mut self) -> Future { + assert!(self.len() > 0); + mem::take(&mut self.contexts[self.current_len - 1].accumulated_futures) + } + + fn set(&mut self, symbol: Symbol, value: Value) { + assert!(self.current_len > 0); + self.last_mut().unwrap().names.insert(symbol, value); + } + + pub fn add_future(&mut self, future: Future) { + assert!(self.current_len > 0); + self.contexts[self.current_len - 1].accumulated_futures.0.extend(future.0); + } + + /// Are we currently in an async function? + fn is_async(&self) -> bool { + assert!(self.current_len > 0); + self.last().unwrap().is_async + } + + pub fn current_program(&self) -> Option { + self.last().map(|c| c.program) + } + + pub fn last(&self) -> Option<&FunctionContext> { + self.len().checked_sub(1).and_then(|i| self.contexts.get(i)) + } + + fn last_mut(&mut self) -> Option<&mut FunctionContext> { + self.len().checked_sub(1).and_then(|i| self.contexts.get_mut(i)) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum AleoContext<'a> { + Closure(&'a Closure), + Function(&'a SvmFunction), + Finalize(&'a Finalize), +} + +/// A Leo construct to be evauated. +#[derive(Clone, Debug)] +pub enum Element<'a> { + /// A Leo statement. + Statement(&'a Statement), + + /// A Leo expression. + Expression(&'a Expression), + + /// A Leo block. + /// + /// We have a separate variant for Leo blocks for two reasons: + /// 1. In a ConditionalExpression, the `then` block is stored + /// as just a Block with no statement, and + /// 2. We need to remember if a Block came from a function body, + /// so that if such a block ends, we know to push a `Unit` to + /// the values stack. + Block { + block: &'a Block, + function_body: bool, + }, + + AleoExecution { + context: AleoContext<'a>, + registers: IndexMap, + instruction_index: usize, + }, + + DelayedCall(GlobalId), +} + +impl Element<'_> { + pub fn span(&self) -> Span { + use Element::*; + match self { + Statement(statement) => statement.span(), + Expression(expression) => expression.span(), + Block { block, .. } => block.span(), + AleoExecution { .. } | DelayedCall(..) => Default::default(), + } + } +} + +/// A frame of execution, keeping track of the Element next to +/// be executed and the number of steps we've done so far. +#[derive(Clone, Debug)] +pub struct Frame<'a> { + pub step: usize, + pub element: Element<'a>, + pub user_initiated: bool, +} + +/// Global values - such as mappings, functions, etc - +/// are identified by program and name. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct GlobalId { + pub program: Symbol, + pub name: Symbol, +} + +impl fmt::Display for GlobalId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.program, self.name) + } +} + +#[derive(Clone, Debug)] +pub enum FunctionVariant<'a> { + Leo(&'a Function), + AleoClosure(&'a Closure), + AleoFunction(&'a SvmFunction), +} + +/// Tracks the current execution state - a cursor into the running program. +#[derive(Clone, Debug)] +pub struct Cursor<'a> { + /// Stack of execution frames, with the one currently to be executed on top. + pub frames: Vec>, + + /// Stack of values from evaluated expressions. + /// + /// Each time an expression completes evaluation, a value is pushed here. + pub values: Vec, + + /// All functions (or transitions or inlines) in any program being interpreted. + pub functions: HashMap>, + + /// Consts are stored here. + pub globals: HashMap, + + pub user_values: HashMap, + + pub mappings: HashMap>, + + /// For each struct type, we only need to remember the names of its members, in order. + pub structs: HashMap>, + + pub futures: Vec, + + pub contexts: ContextStack, + + pub signer: SvmAddress, + + pub rng: ChaCha20Rng, + + pub block_height: u32, + + pub really_async: bool, + + pub program: Option, +} + +impl<'a> Cursor<'a> { + /// `really_async` indicates we should really delay execution of async function calls until the user runs them. + pub fn new(really_async: bool, signer: SvmAddress, block_height: u32) -> Self { + Cursor { + frames: Default::default(), + values: Default::default(), + functions: Default::default(), + globals: Default::default(), + user_values: Default::default(), + mappings: Default::default(), + structs: Default::default(), + contexts: Default::default(), + futures: Default::default(), + rng: ChaCha20Rng::from_entropy(), + signer, + block_height, + really_async, + program: None, + } + } + + pub fn set_program(&mut self, program: &str) { + let p = program.strip_suffix(".aleo").unwrap_or(program); + self.program = Some(Symbol::intern(p)); + } + + pub fn current_program(&self) -> Option { + self.contexts.current_program().or(self.program) + } + + pub fn increment_step(&mut self) { + let Some(Frame { step, .. }) = self.frames.last_mut() else { + panic!("frame expected"); + }; + *step += 1; + } + + fn new_caller(&self) -> SvmAddress { + if let Some(function_context) = self.contexts.last() { + let program_id = ProgramID::::from_str(&format!("{}.aleo", function_context.program)) + .expect("should be able to create ProgramID"); + program_id.to_address().expect("should be able to convert to address") + } else { + self.signer + } + } + + fn pop_value(&mut self) -> Result { + match self.values.pop() { + Some(v) => Ok(v), + None => { + Err(InterpreterHalt::new("value expected - this may be a bug in the Leo interpreter".to_string()) + .into()) + } + } + } + + fn lookup(&self, name: Symbol) -> Option { + if let Some(context) = self.contexts.last() { + let option_value = + context.names.get(&name).or_else(|| self.globals.get(&GlobalId { program: context.program, name })); + if option_value.is_some() { + return option_value.cloned(); + } + }; + + self.user_values.get(&name).cloned() + } + + pub fn lookup_mapping(&self, program: Option, name: Symbol) -> Option<&HashMap> { + let Some(program) = program.or_else(|| self.current_program()) else { + panic!("no program for mapping lookup"); + }; + self.mappings.get(&GlobalId { program, name }) + } + + pub fn lookup_mapping_mut(&mut self, program: Option, name: Symbol) -> Option<&mut HashMap> { + let Some(program) = program.or_else(|| self.current_program()) else { + panic!("no program for mapping lookup"); + }; + self.mappings.get_mut(&GlobalId { program, name }) + } + + fn lookup_function(&self, program: Symbol, name: Symbol) -> Option> { + self.functions.get(&GlobalId { program, name }).cloned() + } + + fn set_variable(&mut self, symbol: Symbol, value: Value) { + if self.contexts.len() > 0 { + self.contexts.set(symbol, value); + } else { + self.user_values.insert(symbol, value); + } + } + + fn set_block_height(&mut self, block_height: u32) { + self.block_height = block_height; + } + + /// Execute the whole step of the current Element. + /// + /// That is, perform a step, and then finish all statements and expressions that have been pushed, + /// until we're ready for the next step of the current Element (if there is one). + pub fn whole_step(&mut self) -> Result { + let frames_len = self.frames.len(); + let initial_result = self.step()?; + if !initial_result.finished { + while self.frames.len() > frames_len { + self.step()?; + } + } + Ok(initial_result) + } + + /// Step `over` the current Element. + /// + /// That is, continue executing until the current Element is finished. + pub fn over(&mut self) -> Result { + let frames_len = self.frames.len(); + loop { + match self.frames.len().cmp(&frames_len) { + Ordering::Greater => { + self.step()?; + } + Ordering::Equal => { + let result = self.step()?; + if result.finished { + return Ok(result); + } + } + Ordering::Less => { + // This can happen if, for instance, a `return` was encountered, + // which means we exited the function we were evaluating and the + // frame stack was truncated. + return Ok(StepResult { finished: true, value: None }); + } + } + } + } + + pub fn step_block(&mut self, block: &'a Block, function_body: bool, step: usize) -> bool { + let len = self.frames.len(); + + let done = match step { + 0 => { + for statement in block.statements.iter().rev() { + self.frames.push(Frame { element: Element::Statement(statement), step: 0, user_initiated: false }); + } + false + } + 1 if function_body => { + self.values.push(Value::Unit); + self.contexts.pop(); + true + } + 1 => true, + _ => unreachable!(), + }; + + if done { + assert_eq!(len, self.frames.len()); + self.frames.pop(); + } else { + self.frames[len - 1].step += 1; + } + + done + } + + fn step_statement(&mut self, statement: &'a Statement, step: usize) -> Result { + let len = self.frames.len(); + + let mut push = |expression| { + self.frames.push(Frame { element: Element::Expression(expression), step: 0, user_initiated: false }) + }; + + let done = match statement { + Statement::Assert(assert) if step == 0 => { + match &assert.variant { + AssertVariant::Assert(x) => push(x), + AssertVariant::AssertEq(x, y) | AssertVariant::AssertNeq(x, y) => { + push(y); + push(x); + } + }; + false + } + Statement::Assert(assert) if step == 1 => { + match &assert.variant { + AssertVariant::Assert(..) => { + let value = self.pop_value()?; + match value { + Value::Bool(true) => {} + Value::Bool(false) => halt!(assert.span(), "assert failure"), + _ => tc_fail!(), + } + } + AssertVariant::AssertEq(..) | AssertVariant::AssertNeq(..) => { + let x = self.pop_value()?; + let y = self.pop_value()?; + let b = + if matches!(assert.variant, AssertVariant::AssertEq(..)) { x.eq(&y)? } else { x.neq(&y)? }; + if !b { + halt!(assert.span(), "assert failure"); + } + } + }; + true + } + Statement::Assign(assign) if step == 0 => { + push(&assign.value); + false + } + Statement::Assign(assign) if step == 1 => { + let value = self.values.pop().unwrap(); + let Expression::Identifier(id) = &assign.place else { tc_fail!() }; + self.set_variable(id.name, value); + true + } + Statement::Block(block) => return Ok(self.step_block(block, false, step)), + Statement::Conditional(conditional) if step == 0 => { + push(&conditional.condition); + false + } + Statement::Conditional(conditional) if step == 1 => { + match self.pop_value()? { + Value::Bool(true) => self.frames.push(Frame { + step: 0, + element: Element::Block { block: &conditional.then, function_body: false }, + user_initiated: false, + }), + Value::Bool(false) => { + if let Some(otherwise) = conditional.otherwise.as_ref() { + self.frames.push(Frame { + step: 0, + element: Element::Statement(otherwise), + user_initiated: false, + }) + } + } + _ => tc_fail!(), + }; + false + } + Statement::Conditional(_) if step == 2 => true, + Statement::Console(_) => todo!(), + Statement::Const(const_) if step == 0 => { + push(&const_.value); + false + } + Statement::Const(const_) if step == 1 => { + let value = self.pop_value()?; + self.set_variable(const_.place.name, value); + true + } + Statement::Definition(definition) if step == 0 => { + push(&definition.value); + false + } + Statement::Definition(definition) if step == 1 => { + let value = self.pop_value()?; + match &definition.place { + Expression::Identifier(id) => self.set_variable(id.name, value), + Expression::Tuple(tuple) => { + let Value::Tuple(rhs) = value else { + tc_fail!(); + }; + for (name, val) in tuple.elements.iter().zip(rhs.into_iter()) { + let Expression::Identifier(id) = name else { + tc_fail!(); + }; + self.set_variable(id.name, val); + } + } + _ => tc_fail!(), + } + true + } + Statement::Expression(expression) if step == 0 => { + push(&expression.expression); + false + } + Statement::Expression(_) if step == 1 => { + self.values.pop(); + true + } + Statement::Iteration(iteration) if step == 0 => { + assert!(!iteration.inclusive); + push(&iteration.stop); + push(&iteration.start); + false + } + Statement::Iteration(iteration) => { + // Currently there actually isn't a syntax in Leo for inclusive ranges. + let stop = self.pop_value()?; + let start = self.pop_value()?; + if start.eq(&stop)? { + true + } else { + let new_start = start.inc_wrapping(); + self.set_variable(iteration.variable.name, start); + self.frames.push(Frame { + step: 0, + element: Element::Block { block: &iteration.block, function_body: false }, + user_initiated: false, + }); + self.values.push(new_start); + self.values.push(stop); + false + } + } + Statement::Return(return_) if step == 0 => { + push(&return_.expression); + false + } + Statement::Return(_) if step == 1 => loop { + let last_frame = self.frames.last().expect("a frame should be present"); + match last_frame.element { + Element::Expression(Expression::Call(_)) | Element::DelayedCall(_) => { + if self.contexts.is_async() { + // Get rid of the Unit we previously pushed, and replace it with a Future. + self.values.pop(); + self.values.push(Value::Future(self.contexts.get_future())); + } + self.contexts.pop(); + return Ok(true); + } + _ => { + self.frames.pop(); + } + } + }, + _ => unreachable!(), + }; + + if done { + assert_eq!(len, self.frames.len()); + self.frames.pop(); + } else { + self.frames[len - 1].step += 1; + } + + Ok(done) + } + + fn step_expression(&mut self, expression: &'a Expression, step: usize) -> Result { + let len = self.frames.len(); + + macro_rules! push { + () => { + |expression| { + self.frames.push(Frame { element: Element::Expression(expression), step: 0, user_initiated: false }) + } + }; + } + + if let Some(value) = match expression { + Expression::Access(AccessExpression::Array(array)) if step == 0 => { + push!()(&*array.index); + push!()(&*array.array); + None + } + Expression::Access(AccessExpression::Array(array)) if step == 1 => { + let span = array.span(); + let array = self.pop_value()?; + let index = self.pop_value()?; + + let index_usize: usize = match index { + Value::U8(x) => x.into(), + Value::U16(x) => x.into(), + Value::U32(x) => x.try_into().expect_tc(span)?, + Value::U64(x) => x.try_into().expect_tc(span)?, + Value::U128(x) => x.try_into().expect_tc(span)?, + Value::I8(x) => x.try_into().expect_tc(span)?, + Value::I16(x) => x.try_into().expect_tc(span)?, + Value::I32(x) => x.try_into().expect_tc(span)?, + Value::I64(x) => x.try_into().expect_tc(span)?, + Value::I128(x) => x.try_into().expect_tc(span)?, + _ => halt!(expression.span(), "invalid array index {index}"), + }; + let Value::Array(vec_array) = array else { tc_fail!() }; + Some(vec_array.get(index_usize).expect_tc(span)?.clone()) + } + Expression::Access(AccessExpression::AssociatedConstant(constant)) if step == 0 => { + let Type::Identifier(type_ident) = constant.ty else { + tc_fail!(); + }; + let Some(core_constant) = CoreConstant::from_symbols(type_ident.name, constant.name.name) else { + halt!(constant.span(), "Unknown constant {constant}"); + }; + match core_constant { + CoreConstant::GroupGenerator => Some(Value::generator()), + } + } + Expression::Access(AccessExpression::Member(access)) => match &*access.inner { + Expression::Identifier(identifier) if identifier.name == sym::SelfLower => match access.name.name { + sym::signer => Some(Value::Address(self.signer)), + sym::caller => { + if let Some(function_context) = self.contexts.last() { + Some(Value::Address(function_context.caller)) + } else { + Some(Value::Address(self.signer)) + } + } + _ => halt!(access.span(), "unknown member of self"), + }, + Expression::Identifier(identifier) if identifier.name == sym::block => match access.name.name { + sym::height => Some(Value::U32(self.block_height)), + _ => halt!(access.span(), "unknown member of block"), + }, + + // Otherwise, we just have a normal struct member access. + _ if step == 0 => { + push!()(&*access.inner); + None + } + _ if step == 1 => { + let Some(Value::Struct(struct_)) = self.values.pop() else { + tc_fail!(); + }; + let value = struct_.contents.get(&access.name.name).cloned(); + if value.is_none() { + tc_fail!(); + } + value + } + _ => unreachable!("we've actually covered all possible patterns above"), + }, + Expression::Access(AccessExpression::AssociatedFunction(function)) if step == 0 => { + let Some(core_function) = CoreFunction::from_symbols(function.variant.name, function.name.name) else { + halt!(function.span(), "Unkown core function {function}"); + }; + + // We want to push expressions for each of the arguments... except for mappings, + // because we don't look them up as Values. + match core_function { + CoreFunction::MappingGet | CoreFunction::MappingRemove | CoreFunction::MappingContains => { + push!()(&function.arguments[1]); + } + CoreFunction::MappingGetOrUse | CoreFunction::MappingSet => { + push!()(&function.arguments[2]); + push!()(&function.arguments[1]); + } + CoreFunction::CheatCodePrintMapping => { + // Do nothing, as we don't need to evaluate the mapping. + } + _ => function.arguments.iter().rev().for_each(push!()), + } + None + } + Expression::Access(AccessExpression::AssociatedFunction(function)) if step == 1 => { + let Some(core_function) = CoreFunction::from_symbols(function.variant.name, function.name.name) else { + halt!(function.span(), "Unkown core function {function}"); + }; + + let span = function.span(); + + let value = self.evaluate_core_function(core_function.clone(), &function.arguments, span)?; + + if let CoreFunction::FutureAwait = core_function { + // For an await, we have one extra step - first we must evaluate the delayed call. + None + } else { + Some(value) + } + } + Expression::Access(AccessExpression::AssociatedFunction(function)) if step == 2 => { + let Some(core_function) = CoreFunction::from_symbols(function.variant.name, function.name.name) else { + halt!(function.span(), "Unkown core function {function}"); + }; + assert!(core_function == CoreFunction::FutureAwait); + Some(Value::Unit) + } + Expression::Access(AccessExpression::Tuple(tuple_access)) if step == 0 => { + push!()(&*tuple_access.tuple); + None + } + Expression::Access(AccessExpression::Tuple(tuple_access)) if step == 1 => { + let Some(value) = self.values.pop() else { tc_fail!() }; + let Value::Tuple(tuple) = value else { + halt!(tuple_access.span(), "Type error"); + }; + if let Some(result) = tuple.get(tuple_access.index.value()) { + Some(result.clone()) + } else { + halt!(tuple_access.span(), "Tuple index out of range"); + } + } + Expression::Array(array) if step == 0 => { + array.elements.iter().rev().for_each(push!()); + None + } + Expression::Array(array) if step == 1 => { + let len = self.values.len(); + let array_values = self.values.drain(len - array.elements.len()..).collect(); + Some(Value::Array(array_values)) + } + Expression::Binary(binary) if step == 0 => { + push!()(&binary.right); + push!()(&binary.left); + None + } + Expression::Binary(binary) if step == 1 => { + let rhs = self.pop_value()?; + let lhs = self.pop_value()?; + Some(evaluate_binary(binary.span, binary.op, lhs, rhs)?) + } + Expression::Call(call) if step == 0 => { + call.arguments.iter().rev().for_each(push!()); + None + } + Expression::Call(call) if step == 1 => { + let len = self.values.len(); + let (program, name) = match &*call.function { + Expression::Identifier(id) => { + let maybe_program = call.program.or_else(|| self.current_program()); + if let Some(program) = maybe_program { + (program, id.name) + } else { + halt!(call.span, "No current program"); + } + } + Expression::Locator(locator) => (locator.program.name.name, locator.name), + _ => tc_fail!(), + }; + // It's a bit cheesy to collect the arguments into a Vec first, but it's the easiest way + // to handle lifetimes here. + let arguments: Vec = self.values.drain(len - call.arguments.len()..).collect(); + self.do_call( + program, + name, + arguments.into_iter(), + false, // finalize + call.span(), + )?; + None + } + Expression::Call(_call) if step == 2 => Some(self.pop_value()?), + Expression::Cast(cast) if step == 0 => { + push!()(&*cast.expression); + None + } + Expression::Cast(cast) if step == 1 => { + let span = cast.span(); + let arg = self.pop_value()?; + match arg.cast(&cast.type_) { + Some(value) => Some(value), + None => return Err(InterpreterHalt::new_spanned("cast failure".to_string(), span).into()), + } + } + Expression::Err(_) => todo!(), + Expression::Identifier(identifier) if step == 0 => { + Some(self.lookup(identifier.name).expect_tc(identifier.span())?) + } + Expression::Literal(literal) if step == 0 => Some(match literal { + Literal::Boolean(b, ..) => Value::Bool(*b), + Literal::Integer(IntegerType::U8, s, ..) => Value::U8(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U16, s, ..) => Value::U16(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U32, s, ..) => Value::U32(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U64, s, ..) => Value::U64(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U128, s, ..) => Value::U128(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I8, s, ..) => Value::I8(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I16, s, ..) => Value::I16(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I32, s, ..) => Value::I32(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I64, s, ..) => Value::I64(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I128, s, ..) => Value::I128(s.parse().expect_tc(literal.span())?), + Literal::Field(s, ..) => Value::Field(format!("{s}field").parse().expect_tc(literal.span())?), + Literal::Group(group_literal) => match &**group_literal { + GroupLiteral::Single(s, ..) => Value::Group(format!("{s}group").parse().expect_tc(literal.span())?), + GroupLiteral::Tuple(_group_tuple) => todo!(), + }, + Literal::Address(s, ..) => { + if s.ends_with(".aleo") { + let program_id = ProgramID::from_str(s)?; + Value::Address(program_id.to_address()?) + } else { + Value::Address(s.parse().expect_tc(literal.span())?) + } + } + Literal::Scalar(s, ..) => Value::Scalar(format!("{s}scalar").parse().expect_tc(literal.span())?), + Literal::String(..) => tc_fail!(), + }), + Expression::Locator(_locator) => todo!(), + Expression::Struct(struct_) if step == 0 => { + struct_.members.iter().flat_map(|init| init.expression.as_ref()).for_each(push!()); + None + } + Expression::Struct(struct_) if step == 1 => { + // Collect all the key/value pairs into a HashMap. + let mut contents_tmp = HashMap::with_capacity(struct_.members.len()); + for initializer in struct_.members.iter() { + let name = initializer.identifier.name; + let value = if initializer.expression.is_some() { + self.pop_value()? + } else { + self.lookup(name).expect_tc(struct_.span())? + }; + contents_tmp.insert(name, value); + } + + // And now put them into an IndexMap in the correct order. + let program = self.current_program().expect("there should be a current program"); + let id = GlobalId { program, name: struct_.name.name }; + let struct_type = self.structs.get(&id).expect_tc(struct_.span())?; + let contents = struct_type + .iter() + .map(|sym| (*sym, contents_tmp.remove(sym).expect("we just inserted this"))) + .collect(); + + Some(Value::Struct(StructContents { name: struct_.name.name, contents })) + } + Expression::Ternary(ternary) if step == 0 => { + push!()(&*ternary.condition); + None + } + Expression::Ternary(ternary) if step == 1 => { + let condition = self.pop_value()?; + match condition { + Value::Bool(true) => push!()(&*ternary.if_true), + Value::Bool(false) => push!()(&*ternary.if_false), + _ => halt!(ternary.span(), "Invalid type for ternary expression {ternary}"), + } + None + } + Expression::Ternary(_) if step == 2 => Some(self.pop_value()?), + Expression::Tuple(tuple) if step == 0 => { + tuple.elements.iter().rev().for_each(push!()); + None + } + Expression::Tuple(tuple) if step == 1 => { + let len = self.values.len(); + let tuple_values = self.values.drain(len - tuple.elements.len()..).collect(); + Some(Value::Tuple(tuple_values)) + } + Expression::Unary(unary) if step == 0 => { + push!()(&*unary.receiver); + None + } + Expression::Unary(unary) if step == 1 => { + let value = self.pop_value()?; + Some(evaluate_unary(unary.span, unary.op, value)?) + } + Expression::Unit(_) if step == 0 => Some(Value::Unit), + x => unreachable!("Unexpected expression {x}"), + } { + assert_eq!(self.frames.len(), len); + self.frames.pop(); + self.values.push(value); + Ok(true) + } else { + self.frames[len - 1].step += 1; + Ok(false) + } + } + + /// Execute one step of the current element. + /// + /// Many Leo constructs require multiple steps. For instance, when executing a conditional, + /// the first step will push the condition expression to the stack. Once that has executed + /// and we've returned to the conditional, we push the `then` or `otherwise` block to the + /// stack. Once that has executed and we've returned to the conditional, the final step + /// does nothing. + pub fn step(&mut self) -> Result { + if self.frames.is_empty() { + return Err(InterpreterHalt::new("no execution frames available".into()).into()); + } + + let Frame { element, step, user_initiated } = self.frames.last().expect("there should be a frame"); + let user_initiated = *user_initiated; + match element { + Element::Block { block, function_body } => { + let finished = self.step_block(block, *function_body, *step); + Ok(StepResult { finished, value: None }) + } + Element::Statement(statement) => { + let finished = self.step_statement(statement, *step)?; + Ok(StepResult { finished, value: None }) + } + Element::Expression(expression) => { + let finished = self.step_expression(expression, *step)?; + let value = match (finished, user_initiated) { + (false, _) => None, + (true, false) => self.values.last().cloned(), + (true, true) => self.values.pop(), + }; + let maybe_future = if let Some(Value::Tuple(vals)) = &value { vals.last() } else { value.as_ref() }; + + if let Some(Value::Future(future)) = &maybe_future { + if user_initiated && !future.0.is_empty() { + self.futures.push(future.clone()); + } + } + Ok(StepResult { finished, value }) + } + Element::AleoExecution { .. } => { + self.step_aleo()?; + Ok(StepResult { finished: true, value: None }) + } + Element::DelayedCall(gid) if *step == 0 => { + match self.lookup_function(gid.program, gid.name).expect("function should exist") { + FunctionVariant::Leo(function) => { + assert!(function.variant == Variant::AsyncFunction); + let len = self.values.len(); + let values: Vec = self.values.drain(len - function.input.len()..).collect(); + self.contexts.push( + gid.program, + self.signer, + true, // is_async + ); + let param_names = function.input.iter().map(|input| input.identifier.name); + for (name, value) in param_names.zip(values) { + self.set_variable(name, value); + } + self.frames.last_mut().unwrap().step = 1; + self.frames.push(Frame { + step: 0, + element: Element::Block { block: &function.block, function_body: true }, + user_initiated: false, + }); + Ok(StepResult { finished: false, value: None }) + } + FunctionVariant::AleoFunction(function) => { + let Some(finalize_f) = function.finalize_logic() else { + panic!("must have finalize logic for a delayed call"); + }; + let len = self.values.len(); + let values_iter = self.values.drain(len - finalize_f.inputs().len()..); + self.contexts.push( + gid.program, + self.signer, + true, // is_async + ); + self.frames.last_mut().unwrap().step = 1; + self.frames.push(Frame { + step: 0, + element: Element::AleoExecution { + context: AleoContext::Finalize(finalize_f), + registers: values_iter.enumerate().map(|(i, v)| (i as u64, v)).collect(), + instruction_index: 0, + }, + user_initiated: false, + }); + Ok(StepResult { finished: false, value: None }) + } + FunctionVariant::AleoClosure(..) => panic!("A call to a closure can't be delayed"), + } + } + Element::DelayedCall(_gid) => { + assert_eq!(*step, 1); + let value = self.values.pop(); + self.frames.pop(); + Ok(StepResult { finished: true, value }) + } + } + } + + pub fn do_call( + &mut self, + function_program: Symbol, + function_name: Symbol, + arguments: impl Iterator, + finalize: bool, + span: Span, + ) -> Result<()> { + let Some(function_variant) = self.lookup_function(function_program, function_name) else { + halt!(span, "unknown function {function_program}.aleo/{function_name}"); + }; + match function_variant { + FunctionVariant::Leo(function) => { + let caller = if matches!(function.variant, Variant::Transition | Variant::AsyncTransition) { + self.new_caller() + } else { + self.signer + }; + if self.really_async && function.variant == Variant::AsyncFunction { + // Don't actually run the call now. + let async_ex = AsyncExecution { + function: GlobalId { name: function_name, program: function_program }, + arguments: arguments.collect(), + }; + self.values.push(Value::Future(Future(vec![async_ex]))); + } else { + let is_async = function.variant == Variant::AsyncFunction; + self.contexts.push(function_program, caller, is_async); + let param_names = function.input.iter().map(|input| input.identifier.name); + for (name, value) in param_names.zip(arguments) { + self.set_variable(name, value); + } + self.frames.push(Frame { + step: 0, + element: Element::Block { block: &function.block, function_body: true }, + user_initiated: false, + }); + } + } + FunctionVariant::AleoClosure(closure) => { + self.contexts.push(function_program, self.signer, false); + let context = AleoContext::Closure(closure); + self.frames.push(Frame { + step: 0, + element: Element::AleoExecution { + context, + registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(), + instruction_index: 0, + }, + user_initiated: false, + }); + } + FunctionVariant::AleoFunction(function) => { + let caller = self.new_caller(); + self.contexts.push(function_program, caller, false); + let context = if finalize { + let Some(finalize_f) = function.finalize_logic() else { + panic!("finalize call with no finalize logic"); + }; + AleoContext::Finalize(finalize_f) + } else { + AleoContext::Function(function) + }; + self.frames.push(Frame { + step: 0, + element: Element::AleoExecution { + context, + registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(), + instruction_index: 0, + }, + user_initiated: false, + }); + } + } + + Ok(()) + } + + pub fn evaluate_core_function( + &mut self, + core_function: CoreFunction, + arguments: &[Expression], + span: Span, + ) -> Result { + macro_rules! apply { + ($func: expr, $value: ident, $to: ident) => {{ + let v = self.pop_value()?; + let bits = v.$to(); + Value::$value($func(&bits).expect_tc(span)?) + }}; + } + + macro_rules! apply_cast { + ($func: expr, $value: ident, $to: ident) => {{ + let v = self.pop_value()?; + let bits = v.$to(); + let group = $func(&bits).expect_tc(span)?; + let x = group.to_x_coordinate(); + Value::$value(x.cast_lossy()) + }}; + } + + macro_rules! apply_cast_int { + ($func: expr, $value: ident, $int_ty: ident, $to: ident) => {{ + let v = self.pop_value()?; + let bits = v.$to(); + let group = $func(&bits).expect_tc(span)?; + let x = group.to_x_coordinate(); + let bits = x.to_bits_le(); + let mut result: $int_ty = 0; + for bit in 0..std::cmp::min($int_ty::BITS as usize, bits.len()) { + let setbit = (if bits[bit] { 1 } else { 0 }) << bit; + result |= setbit; + } + Value::$value(result) + }}; + } + + macro_rules! apply_cast2 { + ($func: expr, $value: ident) => {{ + let Value::Scalar(randomizer) = self.pop_value()? else { + tc_fail!(); + }; + let v = self.pop_value()?; + let bits = v.to_bits_le(); + let group = $func(&bits, &randomizer).expect_tc(span)?; + let x = group.to_x_coordinate(); + Value::$value(x.cast_lossy()) + }}; + } + + let value = match core_function { + CoreFunction::BHP256CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp256, Address) + } + CoreFunction::BHP256CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp256, Field) + } + CoreFunction::BHP256CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp256, Group) + } + CoreFunction::BHP256HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp256, Address, to_bits_le) + } + CoreFunction::BHP256HashToField => apply!(TestnetV0::hash_bhp256, Field, to_bits_le), + CoreFunction::BHP256HashToGroup => apply!(TestnetV0::hash_to_group_bhp256, Group, to_bits_le), + CoreFunction::BHP256HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I8, i8, to_bits_le) + } + CoreFunction::BHP256HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I16, i16, to_bits_le) + } + CoreFunction::BHP256HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I32, i32, to_bits_le) + } + CoreFunction::BHP256HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I64, i64, to_bits_le) + } + CoreFunction::BHP256HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I128, i128, to_bits_le) + } + CoreFunction::BHP256HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U8, u8, to_bits_le) + } + CoreFunction::BHP256HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U16, u16, to_bits_le) + } + CoreFunction::BHP256HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U32, u32, to_bits_le) + } + CoreFunction::BHP256HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U64, u64, to_bits_le) + } + CoreFunction::BHP256HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U128, u128, to_bits_le) + } + CoreFunction::BHP256HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp256, Scalar, to_bits_le) + } + CoreFunction::BHP512CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp512, Address) + } + CoreFunction::BHP512CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp512, Field) + } + CoreFunction::BHP512CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp512, Group) + } + CoreFunction::BHP512HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp512, Address, to_bits_le) + } + CoreFunction::BHP512HashToField => apply!(TestnetV0::hash_bhp512, Field, to_bits_le), + CoreFunction::BHP512HashToGroup => apply!(TestnetV0::hash_to_group_bhp512, Group, to_bits_le), + CoreFunction::BHP512HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I8, i8, to_bits_le) + } + CoreFunction::BHP512HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I16, i16, to_bits_le) + } + CoreFunction::BHP512HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I32, i32, to_bits_le) + } + CoreFunction::BHP512HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I64, i64, to_bits_le) + } + CoreFunction::BHP512HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I128, i128, to_bits_le) + } + CoreFunction::BHP512HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U8, u8, to_bits_le) + } + CoreFunction::BHP512HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U16, u16, to_bits_le) + } + CoreFunction::BHP512HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U32, u32, to_bits_le) + } + CoreFunction::BHP512HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U64, u64, to_bits_le) + } + CoreFunction::BHP512HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U128, u128, to_bits_le) + } + CoreFunction::BHP512HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp512, Scalar, to_bits_le) + } + CoreFunction::BHP768CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp768, Address) + } + CoreFunction::BHP768CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp768, Field) + } + CoreFunction::BHP768CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp768, Group) + } + CoreFunction::BHP768HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp768, Address, to_bits_le) + } + CoreFunction::BHP768HashToField => apply!(TestnetV0::hash_bhp768, Field, to_bits_le), + CoreFunction::BHP768HashToGroup => apply!(TestnetV0::hash_to_group_bhp768, Group, to_bits_le), + CoreFunction::BHP768HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I8, i8, to_bits_le) + } + CoreFunction::BHP768HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I16, i16, to_bits_le) + } + CoreFunction::BHP768HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I32, i32, to_bits_le) + } + CoreFunction::BHP768HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I64, i64, to_bits_le) + } + CoreFunction::BHP768HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I128, i128, to_bits_le) + } + CoreFunction::BHP768HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U8, u8, to_bits_le) + } + CoreFunction::BHP768HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U16, u16, to_bits_le) + } + CoreFunction::BHP768HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U32, u32, to_bits_le) + } + CoreFunction::BHP768HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U64, u64, to_bits_le) + } + CoreFunction::BHP768HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U128, u128, to_bits_le) + } + CoreFunction::BHP768HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp768, Scalar, to_bits_le) + } + CoreFunction::BHP1024CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp1024, Address) + } + CoreFunction::BHP1024CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp1024, Field) + } + CoreFunction::BHP1024CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp1024, Group) + } + CoreFunction::BHP1024HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp1024, Address, to_bits_le) + } + CoreFunction::BHP1024HashToField => apply!(TestnetV0::hash_bhp1024, Field, to_bits_le), + CoreFunction::BHP1024HashToGroup => apply!(TestnetV0::hash_to_group_bhp1024, Group, to_bits_le), + CoreFunction::BHP1024HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I8, i8, to_bits_le) + } + CoreFunction::BHP1024HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I16, i16, to_bits_le) + } + CoreFunction::BHP1024HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I32, i32, to_bits_le) + } + CoreFunction::BHP1024HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I64, i64, to_bits_le) + } + CoreFunction::BHP1024HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I128, i128, to_bits_le) + } + CoreFunction::BHP1024HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U8, u8, to_bits_le) + } + CoreFunction::BHP1024HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U16, u16, to_bits_le) + } + CoreFunction::BHP1024HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U32, u32, to_bits_le) + } + CoreFunction::BHP1024HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U64, u64, to_bits_le) + } + CoreFunction::BHP1024HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U128, u128, to_bits_le) + } + CoreFunction::BHP1024HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp1024, Scalar, to_bits_le) + } + CoreFunction::ChaChaRandAddress => Value::Address(self.rng.gen()), + CoreFunction::ChaChaRandBool => Value::Bool(self.rng.gen()), + CoreFunction::ChaChaRandField => Value::Field(self.rng.gen()), + CoreFunction::ChaChaRandGroup => Value::Group(self.rng.gen()), + CoreFunction::ChaChaRandI8 => Value::I8(self.rng.gen()), + CoreFunction::ChaChaRandI16 => Value::I16(self.rng.gen()), + CoreFunction::ChaChaRandI32 => Value::I32(self.rng.gen()), + CoreFunction::ChaChaRandI64 => Value::I64(self.rng.gen()), + CoreFunction::ChaChaRandI128 => Value::I128(self.rng.gen()), + CoreFunction::ChaChaRandU8 => Value::U8(self.rng.gen()), + CoreFunction::ChaChaRandU16 => Value::U16(self.rng.gen()), + CoreFunction::ChaChaRandU32 => Value::U32(self.rng.gen()), + CoreFunction::ChaChaRandU64 => Value::U64(self.rng.gen()), + CoreFunction::ChaChaRandU128 => Value::U128(self.rng.gen()), + CoreFunction::ChaChaRandScalar => Value::Scalar(self.rng.gen()), + CoreFunction::Keccak256HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::Keccak256HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::Keccak256HashToGroup => { + apply!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Group, + to_bits_le + ) + } + CoreFunction::Keccak256HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::Keccak256HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + + CoreFunction::Keccak256HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::Keccak256HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::Keccak256HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::Keccak256HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::Keccak256HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::Keccak256HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::Keccak256HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::Keccak256HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::Keccak256HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::Keccak384HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::Keccak384HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::Keccak384HashToGroup => { + apply!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Group, + to_bits_le + ) + } + CoreFunction::Keccak384HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::Keccak384HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::Keccak384HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::Keccak384HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::Keccak384HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::Keccak384HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::Keccak384HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::Keccak384HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::Keccak384HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::Keccak384HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::Keccak384HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::Keccak512HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::Keccak512HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::Keccak512HashToGroup => { + apply!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Group, + to_bits_le + ) + } + CoreFunction::Keccak512HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::Keccak512HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::Keccak512HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::Keccak512HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::Keccak512HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::Keccak512HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::Keccak512HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::Keccak512HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::Keccak512HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::Keccak512HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::Keccak512HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::Pedersen64CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_ped64, Address) + } + CoreFunction::Pedersen64CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_ped64, Field) + } + CoreFunction::Pedersen64CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_ped64, Group) + } + CoreFunction::Pedersen64HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_ped64, Address, to_bits_le) + } + CoreFunction::Pedersen64HashToField => apply!(TestnetV0::hash_ped64, Field, to_bits_le), + CoreFunction::Pedersen64HashToGroup => apply!(TestnetV0::hash_to_group_ped64, Group, to_bits_le), + CoreFunction::Pedersen64HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I8, i8, to_bits_le) + } + CoreFunction::Pedersen64HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I16, i16, to_bits_le) + } + CoreFunction::Pedersen64HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I32, i32, to_bits_le) + } + CoreFunction::Pedersen64HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I64, i64, to_bits_le) + } + CoreFunction::Pedersen64HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I128, i128, to_bits_le) + } + CoreFunction::Pedersen64HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U8, u8, to_bits_le) + } + CoreFunction::Pedersen64HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U16, u16, to_bits_le) + } + CoreFunction::Pedersen64HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U32, u32, to_bits_le) + } + CoreFunction::Pedersen64HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U64, u64, to_bits_le) + } + CoreFunction::Pedersen64HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U128, u128, to_bits_le) + } + CoreFunction::Pedersen64HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_ped64, Scalar, to_bits_le) + } + CoreFunction::Pedersen128HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_ped128, Address, to_bits_le) + } + CoreFunction::Pedersen128HashToField => { + apply_cast!(TestnetV0::hash_to_group_ped128, Field, to_bits_le) + } + CoreFunction::Pedersen128HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_ped128, Group, to_bits_le) + } + CoreFunction::Pedersen128HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, I8, i8, to_bits_le) + } + CoreFunction::Pedersen128HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I16, i16, to_bits_le) + } + CoreFunction::Pedersen128HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, I32, i32, to_bits_le) + } + CoreFunction::Pedersen128HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I64, i64, to_bits_le) + } + CoreFunction::Pedersen128HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, I128, i128, to_bits_le) + } + CoreFunction::Pedersen128HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, U8, u8, to_bits_le) + } + CoreFunction::Pedersen128HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U16, u16, to_bits_le) + } + CoreFunction::Pedersen128HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, U32, u32, to_bits_le) + } + CoreFunction::Pedersen128HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U64, u64, to_bits_le) + } + CoreFunction::Pedersen128HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, U128, u128, to_bits_le) + } + CoreFunction::Pedersen128HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_ped128, Scalar, to_bits_le) + } + CoreFunction::Pedersen128CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_ped128, Address) + } + CoreFunction::Pedersen128CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_ped128, Field) + } + CoreFunction::Pedersen128CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_ped128, Group) + } + CoreFunction::Poseidon2HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_psd2, Address, to_fields) + } + CoreFunction::Poseidon2HashToField => { + apply!(TestnetV0::hash_psd2, Field, to_fields) + } + CoreFunction::Poseidon2HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_psd2, Group, to_fields) + } + CoreFunction::Poseidon2HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I8, i8, to_fields) + } + CoreFunction::Poseidon2HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I16, i16, to_fields) + } + CoreFunction::Poseidon2HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I32, i32, to_fields) + } + CoreFunction::Poseidon2HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I64, i64, to_fields) + } + CoreFunction::Poseidon2HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I128, i128, to_fields) + } + CoreFunction::Poseidon2HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U8, u8, to_fields) + } + CoreFunction::Poseidon2HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U16, u16, to_fields) + } + CoreFunction::Poseidon2HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U32, u32, to_fields) + } + CoreFunction::Poseidon2HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U64, u64, to_fields) + } + CoreFunction::Poseidon2HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U128, u128, to_fields) + } + CoreFunction::Poseidon2HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_psd4, Scalar, to_fields) + } + CoreFunction::Poseidon4HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_psd4, Address, to_fields) + } + CoreFunction::Poseidon4HashToField => { + apply!(TestnetV0::hash_psd4, Field, to_fields) + } + CoreFunction::Poseidon4HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_psd4, Group, to_fields) + } + CoreFunction::Poseidon4HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I8, i8, to_fields) + } + CoreFunction::Poseidon4HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I16, i16, to_fields) + } + CoreFunction::Poseidon4HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I32, i32, to_fields) + } + CoreFunction::Poseidon4HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I64, i64, to_fields) + } + CoreFunction::Poseidon4HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I128, i128, to_fields) + } + CoreFunction::Poseidon4HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U8, u8, to_fields) + } + CoreFunction::Poseidon4HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U16, u16, to_fields) + } + CoreFunction::Poseidon4HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U32, u32, to_fields) + } + CoreFunction::Poseidon4HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U64, u64, to_fields) + } + CoreFunction::Poseidon4HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U128, u128, to_fields) + } + CoreFunction::Poseidon4HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_psd4, Scalar, to_fields) + } + CoreFunction::Poseidon8HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_psd8, Address, to_fields) + } + CoreFunction::Poseidon8HashToField => { + apply!(TestnetV0::hash_psd8, Field, to_fields) + } + CoreFunction::Poseidon8HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_psd8, Group, to_fields) + } + CoreFunction::Poseidon8HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I8, i8, to_fields) + } + CoreFunction::Poseidon8HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I16, i16, to_fields) + } + CoreFunction::Poseidon8HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I32, i32, to_fields) + } + CoreFunction::Poseidon8HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I64, i64, to_fields) + } + CoreFunction::Poseidon8HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I128, i128, to_fields) + } + CoreFunction::Poseidon8HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U8, u8, to_fields) + } + CoreFunction::Poseidon8HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U16, u16, to_fields) + } + CoreFunction::Poseidon8HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U32, u32, to_fields) + } + CoreFunction::Poseidon8HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U64, u64, to_fields) + } + CoreFunction::Poseidon8HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U128, u128, to_fields) + } + CoreFunction::Poseidon8HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_psd8, Scalar, to_fields) + } + CoreFunction::SHA3_256HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::SHA3_256HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::SHA3_256HashToGroup => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Group, + to_bits_le + ), + CoreFunction::SHA3_256HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::SHA3_256HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::SHA3_256HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::SHA3_256HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::SHA3_256HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::SHA3_256HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::SHA3_256HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::SHA3_256HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::SHA3_256HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::SHA3_256HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::SHA3_256HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::SHA3_384HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::SHA3_384HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::SHA3_384HashToGroup => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Group, + to_bits_le + ), + CoreFunction::SHA3_384HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::SHA3_384HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::SHA3_384HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::SHA3_384HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::SHA3_384HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::SHA3_384HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::SHA3_384HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::SHA3_384HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::SHA3_384HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::SHA3_384HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::SHA3_384HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::SHA3_512HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::SHA3_512HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::SHA3_512HashToGroup => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Group, + to_bits_le + ), + CoreFunction::SHA3_512HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::SHA3_512HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::SHA3_512HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::SHA3_512HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::SHA3_512HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::SHA3_512HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::SHA3_512HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::SHA3_512HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::SHA3_512HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::SHA3_512HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::SHA3_512HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::MappingGet => { + let key = self.values.pop().expect_tc(span)?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + match self.lookup_mapping(program, name).and_then(|mapping| mapping.get(&key)) { + Some(v) => v.clone(), + None => halt!(span, "map lookup failure"), + } + } + CoreFunction::MappingGetOrUse => { + let use_value = self.values.pop().expect_tc(span)?; + let key = self.values.pop().expect_tc(span)?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + match self.lookup_mapping(program, name).and_then(|mapping| mapping.get(&key)) { + Some(v) => v.clone(), + None => use_value, + } + } + CoreFunction::MappingSet => { + let value = self.pop_value()?; + let key = self.pop_value()?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping_mut(program, name) { + mapping.insert(key, value); + } else { + tc_fail!(); + } + Value::Unit + } + CoreFunction::MappingRemove => { + let key = self.pop_value()?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping_mut(program, name) { + mapping.remove(&key); + } else { + tc_fail!(); + } + Value::Unit + } + CoreFunction::MappingContains => { + let key = self.pop_value()?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping_mut(program, name) { + Value::Bool(mapping.contains_key(&key)) + } else { + tc_fail!(); + } + } + CoreFunction::GroupToXCoordinate => { + let Value::Group(g) = self.pop_value()? else { + tc_fail!(); + }; + Value::Field(g.to_x_coordinate()) + } + CoreFunction::GroupToYCoordinate => { + let Value::Group(g) = self.pop_value()? else { + tc_fail!(); + }; + Value::Field(g.to_y_coordinate()) + } + CoreFunction::SignatureVerify => todo!(), + CoreFunction::FutureAwait => { + let value = self.pop_value()?; + let Value::Future(future) = value else { + halt!(span, "Invalid value for await: {value}"); + }; + for async_execution in future.0 { + self.values.extend(async_execution.arguments.into_iter()); + self.frames.push(Frame { + step: 0, + element: Element::DelayedCall(async_execution.function), + user_initiated: false, + }); + } + Value::Unit + } + CoreFunction::CheatCodePrintMapping => { + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping(program, name) { + // TODO: What is the appropriate way to print this to the console. + // Print the name of the mapping. + println!( + "Mapping: {}", + if let Some(program) = program { format!("{}/{}", program, name) } else { name.to_string() } + ); + // Print the contents of the mapping. + for (key, value) in mapping { + println!(" {} -> {}", key, value); + } + } else { + tc_fail!(); + } + Value::Unit + } + CoreFunction::CheatCodeSetBlockHeight => { + let Value::U32(height) = self.pop_value()? else { + tc_fail!(); + }; + self.set_block_height(height); + Value::Unit + } + }; + + Ok(value) + } +} + +#[derive(Clone, Debug)] +pub struct StepResult { + /// Has this element completely finished running? + pub finished: bool, + + /// If the element was an expression, here's its value. + pub value: Option, +} + +/// Evaluate a binary operation. +pub fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> Result { + let value = match op { + BinaryOperation::Add => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_add(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_add(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_add(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_add(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_add(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_add(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_add(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_add(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_add(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_add(y).map(Value::I128), + (Value::Group(x), Value::Group(y)) => Some(Value::Group(x + y)), + (Value::Field(x), Value::Field(y)) => Some(Value::Field(x + y)), + (Value::Scalar(x), Value::Scalar(y)) => Some(Value::Scalar(x + y)), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "add overflow"); + }; + value + } + BinaryOperation::AddWrapped => match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_add(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_add(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_add(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_add(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_add(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_add(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_add(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_add(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_add(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_add(y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::And => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x && y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::BitwiseAnd => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x & y), + (Value::U8(x), Value::U8(y)) => Value::U8(x & y), + (Value::U16(x), Value::U16(y)) => Value::U16(x & y), + (Value::U32(x), Value::U32(y)) => Value::U32(x & y), + (Value::U64(x), Value::U64(y)) => Value::U64(x & y), + (Value::U128(x), Value::U128(y)) => Value::U128(x & y), + (Value::I8(x), Value::I8(y)) => Value::I8(x & y), + (Value::I16(x), Value::I16(y)) => Value::I16(x & y), + (Value::I32(x), Value::I32(y)) => Value::I32(x & y), + (Value::I64(x), Value::I64(y)) => Value::I64(x & y), + (Value::I128(x), Value::I128(y)) => Value::I128(x & y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Div => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_div(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_div(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_div(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_div(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_div(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_div(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_div(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_div(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_div(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_div(y).map(Value::I128), + (Value::Field(x), Value::Field(y)) => y.inverse().map(|y| Value::Field(x * y)).ok(), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "div overflow"); + }; + value + } + BinaryOperation::DivWrapped => match (lhs, rhs) { + (Value::U8(_), Value::U8(0)) + | (Value::U16(_), Value::U16(0)) + | (Value::U32(_), Value::U32(0)) + | (Value::U64(_), Value::U64(0)) + | (Value::U128(_), Value::U128(0)) + | (Value::I8(_), Value::I8(0)) + | (Value::I16(_), Value::I16(0)) + | (Value::I32(_), Value::I32(0)) + | (Value::I64(_), Value::I64(0)) + | (Value::I128(_), Value::I128(0)) => halt!(span, "divide by 0"), + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_div(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_div(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_div(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_div(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_div(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_div(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_div(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_div(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_div(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_div(y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Eq => Value::Bool(lhs.eq(&rhs)?), + BinaryOperation::Gte => Value::Bool(lhs.gte(&rhs)?), + BinaryOperation::Gt => Value::Bool(lhs.gt(&rhs)?), + BinaryOperation::Lte => Value::Bool(lhs.lte(&rhs)?), + BinaryOperation::Lt => Value::Bool(lhs.lt(&rhs)?), + BinaryOperation::Mod => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_rem(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_rem(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_rem(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_rem(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_rem(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_rem(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_rem(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_rem(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_rem(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_rem(y).map(Value::I128), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "mod overflow"); + }; + value + } + BinaryOperation::Mul => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_mul(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_mul(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_mul(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_mul(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_mul(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_mul(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_mul(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_mul(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_mul(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_mul(y).map(Value::I128), + (Value::Field(x), Value::Field(y)) => Some(Value::Field(x * y)), + (Value::Group(x), Value::Scalar(y)) => Some(Value::Group(x * y)), + (Value::Scalar(x), Value::Group(y)) => Some(Value::Group(x * y)), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "mul overflow"); + }; + value + } + BinaryOperation::MulWrapped => match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_mul(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_mul(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_mul(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_mul(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_mul(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_mul(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_mul(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_mul(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_mul(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_mul(y)), + (Value::Field(_), Value::Field(_)) => todo!(), + _ => halt!(span, "Type error"), + }, + + BinaryOperation::Nand => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(!(x & y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Neq => Value::Bool(lhs.neq(&rhs)?), + BinaryOperation::Nor => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(!(x | y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Or => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x | y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::BitwiseOr => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x | y), + (Value::U8(x), Value::U8(y)) => Value::U8(x | y), + (Value::U16(x), Value::U16(y)) => Value::U16(x | y), + (Value::U32(x), Value::U32(y)) => Value::U32(x | y), + (Value::U64(x), Value::U64(y)) => Value::U64(x | y), + (Value::U128(x), Value::U128(y)) => Value::U128(x | y), + (Value::I8(x), Value::I8(y)) => Value::I8(x | y), + (Value::I16(x), Value::I16(y)) => Value::I16(x | y), + (Value::I32(x), Value::I32(y)) => Value::I32(x | y), + (Value::I64(x), Value::I64(y)) => Value::I64(x | y), + (Value::I128(x), Value::I128(y)) => Value::I128(x | y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Pow => { + if let (Value::Field(x), Value::Field(y)) = (&lhs, &rhs) { + Value::Field(x.pow(y)) + } else { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => tc_fail!(), + }; + + let Some(value) = (match lhs { + Value::U8(x) => x.checked_pow(rhs).map(Value::U8), + Value::U16(x) => x.checked_pow(rhs).map(Value::U16), + Value::U32(x) => x.checked_pow(rhs).map(Value::U32), + Value::U64(x) => x.checked_pow(rhs).map(Value::U64), + Value::U128(x) => x.checked_pow(rhs).map(Value::U128), + Value::I8(x) => x.checked_pow(rhs).map(Value::I8), + Value::I16(x) => x.checked_pow(rhs).map(Value::I16), + Value::I32(x) => x.checked_pow(rhs).map(Value::I32), + Value::I64(x) => x.checked_pow(rhs).map(Value::I64), + Value::I128(x) => x.checked_pow(rhs).map(Value::I128), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "pow overflow"); + }; + value + } + } + BinaryOperation::PowWrapped => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + + match lhs { + Value::U8(x) => Value::U8(x.wrapping_pow(rhs)), + Value::U16(x) => Value::U16(x.wrapping_pow(rhs)), + Value::U32(x) => Value::U32(x.wrapping_pow(rhs)), + Value::U64(x) => Value::U64(x.wrapping_pow(rhs)), + Value::U128(x) => Value::U128(x.wrapping_pow(rhs)), + Value::I8(x) => Value::I8(x.wrapping_pow(rhs)), + Value::I16(x) => Value::I16(x.wrapping_pow(rhs)), + Value::I32(x) => Value::I32(x.wrapping_pow(rhs)), + Value::I64(x) => Value::I64(x.wrapping_pow(rhs)), + Value::I128(x) => Value::I128(x.wrapping_pow(rhs)), + _ => halt!(span, "Type error"), + } + } + BinaryOperation::Rem => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_rem(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_rem(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_rem(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_rem(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_rem(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_rem(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_rem(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_rem(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_rem(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_rem(y).map(Value::I128), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "rem error"); + }; + value + } + BinaryOperation::RemWrapped => match (lhs, rhs) { + (Value::U8(_), Value::U8(0)) + | (Value::U16(_), Value::U16(0)) + | (Value::U32(_), Value::U32(0)) + | (Value::U64(_), Value::U64(0)) + | (Value::U128(_), Value::U128(0)) + | (Value::I8(_), Value::I8(0)) + | (Value::I16(_), Value::I16(0)) + | (Value::I32(_), Value::I32(0)) + | (Value::I64(_), Value::I64(0)) + | (Value::I128(_), Value::I128(0)) => halt!(span, "rem by 0"), + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_rem(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_rem(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_rem(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_rem(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_rem(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_rem(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_rem(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_rem(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_rem(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_rem(y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Shl => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + match lhs { + Value::U8(_) | Value::I8(_) if rhs >= 8 => halt!(span, "shl overflow"), + Value::U16(_) | Value::I16(_) if rhs >= 16 => halt!(span, "shl overflow"), + Value::U32(_) | Value::I32(_) if rhs >= 32 => halt!(span, "shl overflow"), + Value::U64(_) | Value::I64(_) if rhs >= 64 => halt!(span, "shl overflow"), + Value::U128(_) | Value::I128(_) if rhs >= 128 => halt!(span, "shl overflow"), + _ => {} + } + + // Aleo's shl halts if set bits are shifted out. + let shifted = lhs.simple_shl(rhs); + let reshifted = shifted.simple_shr(rhs); + if lhs.eq(&reshifted)? { + shifted + } else { + halt!(span, "shl overflow"); + } + } + + BinaryOperation::ShlWrapped => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + match lhs { + Value::U8(x) => Value::U8(x.wrapping_shl(rhs)), + Value::U16(x) => Value::U16(x.wrapping_shl(rhs)), + Value::U32(x) => Value::U32(x.wrapping_shl(rhs)), + Value::U64(x) => Value::U64(x.wrapping_shl(rhs)), + Value::U128(x) => Value::U128(x.wrapping_shl(rhs)), + Value::I8(x) => Value::I8(x.wrapping_shl(rhs)), + Value::I16(x) => Value::I16(x.wrapping_shl(rhs)), + Value::I32(x) => Value::I32(x.wrapping_shl(rhs)), + Value::I64(x) => Value::I64(x.wrapping_shl(rhs)), + Value::I128(x) => Value::I128(x.wrapping_shl(rhs)), + _ => halt!(span, "Type error"), + } + } + + BinaryOperation::Shr => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + + match lhs { + Value::U8(_) | Value::I8(_) if rhs >= 8 => halt!(span, "shr overflow"), + Value::U16(_) | Value::I16(_) if rhs >= 16 => halt!(span, "shr overflow"), + Value::U32(_) | Value::I32(_) if rhs >= 32 => halt!(span, "shr overflow"), + Value::U64(_) | Value::I64(_) if rhs >= 64 => halt!(span, "shr overflow"), + Value::U128(_) | Value::I128(_) if rhs >= 128 => halt!(span, "shr overflow"), + _ => {} + } + + lhs.simple_shr(rhs) + } + + BinaryOperation::ShrWrapped => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + + match lhs { + Value::U8(x) => Value::U8(x.wrapping_shr(rhs)), + Value::U16(x) => Value::U16(x.wrapping_shr(rhs)), + Value::U32(x) => Value::U32(x.wrapping_shr(rhs)), + Value::U64(x) => Value::U64(x.wrapping_shr(rhs)), + Value::U128(x) => Value::U128(x.wrapping_shr(rhs)), + Value::I8(x) => Value::I8(x.wrapping_shr(rhs)), + Value::I16(x) => Value::I16(x.wrapping_shr(rhs)), + Value::I32(x) => Value::I32(x.wrapping_shr(rhs)), + Value::I64(x) => Value::I64(x.wrapping_shr(rhs)), + Value::I128(x) => Value::I128(x.wrapping_shr(rhs)), + _ => halt!(span, "Type error"), + } + } + + BinaryOperation::Sub => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_sub(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_sub(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_sub(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_sub(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_sub(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_sub(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_sub(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_sub(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_sub(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_sub(y).map(Value::I128), + (Value::Group(x), Value::Group(y)) => Some(Value::Group(x - y)), + (Value::Field(x), Value::Field(y)) => Some(Value::Field(x - y)), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "sub overflow"); + }; + value + } + + BinaryOperation::SubWrapped => match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_sub(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_sub(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_sub(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_sub(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_sub(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_sub(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_sub(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_sub(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_sub(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_sub(y)), + _ => halt!(span, "Type error"), + }, + + BinaryOperation::Xor => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x ^ y), + (Value::U8(x), Value::U8(y)) => Value::U8(x ^ y), + (Value::U16(x), Value::U16(y)) => Value::U16(x ^ y), + (Value::U32(x), Value::U32(y)) => Value::U32(x ^ y), + (Value::U64(x), Value::U64(y)) => Value::U64(x ^ y), + (Value::U128(x), Value::U128(y)) => Value::U128(x ^ y), + (Value::I8(x), Value::I8(y)) => Value::I8(x ^ y), + (Value::I16(x), Value::I16(y)) => Value::I16(x ^ y), + (Value::I32(x), Value::I32(y)) => Value::I32(x ^ y), + (Value::I64(x), Value::I64(y)) => Value::I64(x ^ y), + (Value::I128(x), Value::I128(y)) => Value::I128(x ^ y), + _ => halt!(span, "Type error"), + }, + }; + Ok(value) +} + +/// Evaluate a unary operation. +pub fn evaluate_unary(span: Span, op: UnaryOperation, value: Value) -> Result { + let value_result = match op { + UnaryOperation::Abs => match value { + Value::I8(x) => { + if x == i8::MIN { + halt!(span, "abs overflow"); + } else { + Value::I8(x.abs()) + } + } + Value::I16(x) => { + if x == i16::MIN { + halt!(span, "abs overflow"); + } else { + Value::I16(x.abs()) + } + } + Value::I32(x) => { + if x == i32::MIN { + halt!(span, "abs overflow"); + } else { + Value::I32(x.abs()) + } + } + Value::I64(x) => { + if x == i64::MIN { + halt!(span, "abs overflow"); + } else { + Value::I64(x.abs()) + } + } + Value::I128(x) => { + if x == i128::MIN { + halt!(span, "abs overflow"); + } else { + Value::I128(x.abs()) + } + } + _ => halt!(span, "Type error"), + }, + UnaryOperation::AbsWrapped => match value { + Value::I8(x) => Value::I8(x.unsigned_abs() as i8), + Value::I16(x) => Value::I16(x.unsigned_abs() as i16), + Value::I32(x) => Value::I32(x.unsigned_abs() as i32), + Value::I64(x) => Value::I64(x.unsigned_abs() as i64), + Value::I128(x) => Value::I128(x.unsigned_abs() as i128), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Double => match value { + Value::Field(x) => Value::Field(x.double()), + Value::Group(x) => Value::Group(x.double()), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Inverse => match value { + Value::Field(x) => { + let Ok(y) = x.inverse() else { + halt!(span, "attempt to invert 0field"); + }; + Value::Field(y) + } + _ => halt!(span, "Can only invert fields"), + }, + UnaryOperation::Negate => match value { + Value::I8(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I8(y), + }, + Value::I16(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I16(y), + }, + Value::I32(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I32(y), + }, + Value::I64(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I64(y), + }, + Value::I128(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I128(y), + }, + Value::Group(x) => Value::Group(-x), + Value::Field(x) => Value::Field(-x), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Not => match value { + Value::Bool(x) => Value::Bool(!x), + Value::U8(x) => Value::U8(!x), + Value::U16(x) => Value::U16(!x), + Value::U32(x) => Value::U32(!x), + Value::U64(x) => Value::U64(!x), + Value::U128(x) => Value::U128(!x), + Value::I8(x) => Value::I8(!x), + Value::I16(x) => Value::I16(!x), + Value::I32(x) => Value::I32(!x), + Value::I64(x) => Value::I64(!x), + Value::I128(x) => Value::I128(!x), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Square => match value { + Value::Field(x) => Value::Field(x.square()), + _ => halt!(span, "Can only square fields"), + }, + UnaryOperation::SquareRoot => match value { + Value::Field(x) => { + let Ok(y) = x.square_root() else { + halt!(span, "square root failure"); + }; + Value::Field(y) + } + _ => halt!(span, "Can only apply square_root to fields"), + }, + UnaryOperation::ToXCoordinate => match value { + Value::Group(x) => Value::Field(x.to_x_coordinate()), + _ => tc_fail!(), + }, + UnaryOperation::ToYCoordinate => match value { + Value::Group(x) => Value::Field(x.to_y_coordinate()), + _ => tc_fail!(), + }, + }; + + Ok(value_result) +} diff --git a/interpreter/src/cursor_aleo.rs b/interpreter/src/cursor_aleo.rs new file mode 100644 index 0000000000..506117dc36 --- /dev/null +++ b/interpreter/src/cursor_aleo.rs @@ -0,0 +1,1030 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; + +use leo_ast::{BinaryOperation, CoreFunction, IntegerType, Type, UnaryOperation}; + +use snarkvm::{ + prelude::{ + Access, + Boolean, + Field, + Identifier, + Literal, + LiteralType, + Network as _, + PlaintextType, + Register, + TestnetV0, + ToBits as _, + ToBytes as _, + integers::Integer, + }, + synthesizer::{Command, Instruction}, +}; +use snarkvm_synthesizer_program::{CallOperator, CastType, Operand}; + +use rand::Rng as _; +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; +use std::mem; + +impl Cursor<'_> { + fn mapping_by_call_operator(&self, operator: &CallOperator) -> Option<&HashMap> { + let (program, name) = match operator { + CallOperator::Locator(locator) => { + (Some(snarkvm_identifier_to_symbol(locator.name())), snarkvm_identifier_to_symbol(locator.resource())) + } + CallOperator::Resource(id) => (None, snarkvm_identifier_to_symbol(id)), + }; + self.lookup_mapping(program, name) + } + + fn get_register(&self, reg: &Register) -> &Value { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last() else { + panic!(); + }; + match reg { + Register::Locator(index) => { + registers.get(index).expect("valid .aleo code doesn't access undefined registers") + } + Register::Access(index, accesses) => { + let mut current_value = + registers.get(index).expect("valid .aleo code doesn't access undefined registers"); + for access in accesses.iter() { + match access { + Access::Member(id) => { + if let Value::Struct(current_struct) = current_value { + let name = snarkvm_identifier_to_symbol(id); + current_value = current_struct.contents.get(&name).expect("struct missing member"); + } else { + tc_fail!(); + } + } + Access::Index(index) => { + if let Value::Array(current_array) = current_value { + current_value = current_array.get(**index as usize).expect("array index out of bounds"); + } else { + tc_fail!(); + } + } + } + } + + current_value + } + } + } + + fn set_register(&mut self, reg: Register, value: Value) { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last_mut() else { + panic!(); + }; + + match reg { + Register::Locator(index) => { + registers.insert(index, value); + } + Register::Access(_, _) => todo!(), + } + } + + fn instructions_len(&self) -> usize { + let Some(Frame { element: Element::AleoExecution { context, .. }, .. }) = self.frames.last() else { + panic!(); + }; + match context { + AleoContext::Closure(closure) => closure.instructions().len(), + AleoContext::Function(function) => function.instructions().len(), + AleoContext::Finalize(finalize) => finalize.commands().len(), + } + } + + fn increment_instruction_index(&mut self) { + let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last_mut() + else { + panic!(); + }; + *instruction_index += 1; + } + + fn execution_complete(&self) -> bool { + let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last() else { + panic!(); + }; + *instruction_index >= self.instructions_len() + } + + fn next_instruction(&self) -> Option<&Instruction> { + let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last() + else { + panic!(); + }; + match context { + AleoContext::Closure(closure) => closure.instructions().get(*instruction_index), + AleoContext::Function(function) => function.instructions().get(*instruction_index), + AleoContext::Finalize(_) => None, + } + } + + fn next_command(&self) -> Option<&Command> { + let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last() + else { + panic!(); + }; + match context { + AleoContext::Closure(_) | AleoContext::Function(_) => None, + AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index), + } + } + + fn operand_value(&self, operand: &Operand) -> Value { + match operand { + Operand::Literal(literal) => match literal { + Literal::Address(x) => Value::Address(*x), + Literal::Boolean(x) => Value::Bool(**x), + Literal::Field(x) => Value::Field(*x), + Literal::Group(x) => Value::Group(*x), + Literal::I8(x) => Value::I8(**x), + Literal::I16(x) => Value::I16(**x), + Literal::I32(x) => Value::I32(**x), + Literal::I64(x) => Value::I64(**x), + Literal::I128(x) => Value::I128(**x), + Literal::U8(x) => Value::U8(**x), + Literal::U16(x) => Value::U16(**x), + Literal::U32(x) => Value::U32(**x), + Literal::U64(x) => Value::U64(**x), + Literal::U128(x) => Value::U128(**x), + Literal::Scalar(x) => Value::Scalar(*x), + Literal::Signature(_) => todo!(), + Literal::String(_) => todo!(), + }, + Operand::Register(register) => self.get_register(register).clone(), + Operand::ProgramID(_) => todo!(), + Operand::Signer => Value::Address(self.signer), + Operand::Caller => { + if let Some(function_context) = self.contexts.last() { + Value::Address(function_context.caller) + } else { + Value::Address(self.signer) + } + } + Operand::BlockHeight => Value::U32(self.block_height), + Operand::NetworkID => todo!(), + } + } + + fn step_aleo_instruction(&mut self, instruction: Instruction) -> Result<()> { + // The Aleo VM code is a linear sequence of instructions, so we don't need to keep + // a stack of Elements (except for calls). Just run instructions in order. + use Instruction::*; + + let Some(Frame { step, .. }) = self.frames.last() else { + panic!("frame expected"); + }; + + macro_rules! unary { + ($svm_op: expr, $op: ident) => {{ + let operand = self.operand_value(&$svm_op.operands()[0]); + let value = evaluate_unary(Default::default(), UnaryOperation::$op, operand)?; + self.increment_instruction_index(); + (value, $svm_op.destinations()[0].clone()) + }}; + } + + macro_rules! binary { + ($svm_op: expr, $op: ident) => {{ + let operand0 = self.operand_value(&$svm_op.operands()[0]); + let operand1 = self.operand_value(&$svm_op.operands()[1]); + let value = evaluate_binary(Default::default(), BinaryOperation::$op, operand0, operand1)?; + self.increment_instruction_index(); + (value, $svm_op.destinations()[0].clone()) + }}; + } + + macro_rules! commit_function { + ($commit: expr, + $to_address: ident, + $to_field: ident, + $to_group: ident, + ) => {{ + let core_function = match $commit.destination_type() { + LiteralType::Address => CoreFunction::$to_address, + LiteralType::Field => CoreFunction::$to_field, + LiteralType::Group => CoreFunction::$to_group, + _ => panic!("invalid commit destination type"), + }; + + let randomizer_value = self.operand_value(&$commit.operands()[0]); + let operand_value = self.operand_value(&$commit.operands()[1]); + self.values.push(randomizer_value); + self.values.push(operand_value); + let value = self.evaluate_core_function(core_function, &[], Span::default())?; + self.increment_instruction_index(); + (value, $commit.destinations()[0].clone()) + }}; + } + + macro_rules! hash_function { + ($hash: expr, + $to_address: ident, + $to_field: ident, + $to_group: ident, + $to_i8: ident, + $to_i16: ident, + $to_i32: ident, + $to_i64: ident, + $to_i128: ident, + $to_u8: ident, + $to_u16: ident, + $to_u32: ident, + $to_u64: ident, + $to_u128: ident, + $to_scalar: ident, + ) => {{ + let core_function = match $hash.destination_type() { + PlaintextType::Literal(LiteralType::Address) => CoreFunction::$to_address, + PlaintextType::Literal(LiteralType::Field) => CoreFunction::$to_field, + PlaintextType::Literal(LiteralType::Group) => CoreFunction::$to_group, + PlaintextType::Literal(LiteralType::I8) => CoreFunction::$to_i8, + PlaintextType::Literal(LiteralType::I16) => CoreFunction::$to_i16, + PlaintextType::Literal(LiteralType::I32) => CoreFunction::$to_i32, + PlaintextType::Literal(LiteralType::I64) => CoreFunction::$to_i64, + PlaintextType::Literal(LiteralType::I128) => CoreFunction::$to_i128, + PlaintextType::Literal(LiteralType::U8) => CoreFunction::$to_u8, + PlaintextType::Literal(LiteralType::U16) => CoreFunction::$to_u16, + PlaintextType::Literal(LiteralType::U32) => CoreFunction::$to_u32, + PlaintextType::Literal(LiteralType::U64) => CoreFunction::$to_u64, + PlaintextType::Literal(LiteralType::U128) => CoreFunction::$to_u128, + PlaintextType::Literal(LiteralType::Scalar) => CoreFunction::$to_scalar, + _ => panic!("invalid hash destination type"), + }; + let operand_value = self.operand_value(&$hash.operands()[0]); + self.values.push(operand_value); + let value = self.evaluate_core_function(core_function, &[], Span::default())?; + self.increment_instruction_index(); + (value, $hash.destinations()[0].clone()) + }}; + } + + let (value, destination) = match instruction { + Abs(abs) => unary!(abs, Abs), + AbsWrapped(abs_wrapped) => unary!(abs_wrapped, AbsWrapped), + Add(add) => binary!(add, Add), + AddWrapped(add_wrapped) => binary!(add_wrapped, AddWrapped), + And(and) => binary!(and, BitwiseAnd), + AssertEq(assert_eq) => { + let operand0 = self.operand_value(&assert_eq.operands()[0]); + let operand1 = self.operand_value(&assert_eq.operands()[1]); + if operand0.neq(&operand1)? { + halt_no_span!("assertion failure: {operand0} != {operand1}"); + } + self.increment_instruction_index(); + return Ok(()); + } + AssertNeq(assert_neq) => { + let operand0 = self.operand_value(&assert_neq.operands()[0]); + let operand1 = self.operand_value(&assert_neq.operands()[1]); + if operand0.eq(&operand1)? { + halt_no_span!("assertion failure: {operand0} != {operand1}"); + } + self.increment_instruction_index(); + return Ok(()); + } + Async(async_) if *step == 0 => { + let program = self.contexts.current_program().expect("there should be a program"); + let name = snarkvm_identifier_to_symbol(async_.function_name()); + let arguments: Vec = async_.operands().iter().map(|op| self.operand_value(op)).collect(); + if self.really_async { + self.increment_instruction_index(); + let async_ex = AsyncExecution { function: GlobalId { name, program }, arguments }; + (Value::Future(Future(vec![async_ex])), async_.destinations()[0].clone()) + } else { + self.do_call( + program, + name, + arguments.into_iter(), + true, // finalize + Span::default(), + )?; + self.increment_step(); + return Ok(()); + } + } + Call(call) if *step == 0 => { + let (program, name) = match call.operator() { + CallOperator::Locator(locator) => ( + snarkvm_identifier_to_symbol(locator.resource()), + snarkvm_identifier_to_symbol(locator.program_id().name()), + ), + CallOperator::Resource(id) => ( + snarkvm_identifier_to_symbol(id), + self.contexts.current_program().expect("there should be a program"), + ), + }; + let arguments: Vec = call.operands().iter().map(|op| self.operand_value(op)).collect(); + self.do_call( + program, + name, + arguments.into_iter(), + false, // finalize + Span::default(), + )?; + self.increment_step(); + return Ok(()); + } + Async(async_) if *step == 1 => { + // We've done a call, and the result is on the value stack. + self.values.pop(); + self.set_register(async_.destinations()[0].clone(), Value::Future(Future(Vec::new()))); + self.increment_instruction_index(); + return Ok(()); + } + Call(call) if *step == 1 => { + // We've done a call, and the result is on the value stack. + let Some(result) = self.values.pop() else { + panic!("should have a result"); + }; + if call.destinations().len() == 1 { + self.set_register(call.destinations()[0].clone(), result); + } else { + let Value::Tuple(tuple) = result else { + panic!("function returning multiple values should create a tuple"); + }; + for (dest, value) in call.destinations().iter().zip(tuple.into_iter()) { + self.set_register(dest.clone(), value); + } + } + self.increment_instruction_index(); + return Ok(()); + } + Call(_) | Async(_) => unreachable!("all cases covered above"), + Cast(cast) => { + let destination = cast.destinations()[0].clone(); + + self.increment_instruction_index(); + + let make_struct = |program, name_identifier| { + let name = snarkvm_identifier_to_symbol(name_identifier); + let id = GlobalId { program, name }; + let struct_type = self.structs.get(&id).expect("struct type should exist"); + let operands = cast.operands().iter().map(|op| self.operand_value(op)); + Value::Struct(StructContents { + name, + contents: struct_type.iter().cloned().zip(operands).collect(), + }) + }; + + match cast.cast_type() { + CastType::GroupXCoordinate => { + let Value::Group(g) = self.operand_value(&cast.operands()[0]) else { + tc_fail!(); + }; + let value = Value::Field(g.to_x_coordinate()); + (value, destination) + } + CastType::GroupYCoordinate => { + let Value::Group(g) = self.operand_value(&cast.operands()[0]) else { + tc_fail!(); + }; + let value = Value::Field(g.to_y_coordinate()); + (value, destination) + } + CastType::Plaintext(PlaintextType::Array(_array)) => { + let value = Value::Array(cast.operands().iter().map(|op| self.operand_value(op)).collect()); + (value, destination) + } + CastType::Plaintext(PlaintextType::Literal(literal_type)) => { + let operand = self.operand_value(&cast.operands()[0]); + let value = match operand.cast(&snarkvm_literal_type_to_type(*literal_type)) { + Some(value) => value, + None => halt_no_span!("cast failure"), + }; + (value, destination) + } + CastType::Record(struct_name) | CastType::Plaintext(PlaintextType::Struct(struct_name)) => { + let program = self.contexts.current_program().expect("there should be a current program"); + let value = make_struct(program, struct_name); + (value, destination) + } + CastType::ExternalRecord(locator) => { + let program = snarkvm_identifier_to_symbol(locator.program_id().name()); + let value = make_struct(program, locator.name()); + (value, destination) + } + } + } + CastLossy(cast_lossy) => { + match cast_lossy.cast_type() { + CastType::Plaintext(PlaintextType::Literal(literal_type)) => { + // This is the only variant supported for lossy casts. + let operand = self.operand_value(&cast_lossy.operands()[0]); + let operand_literal = value_to_snarkvm_literal(operand); + let result_literal = match operand_literal.cast_lossy(*literal_type) { + Ok(result_literal) => result_literal, + Err(_) => halt_no_span!("cast failure"), + }; + let destination = cast_lossy.destinations()[0].clone(); + self.increment_instruction_index(); + (snarkvm_literal_to_value(result_literal), destination) + } + _ => tc_fail!(), + } + } + CommitBHP256(commit) => { + commit_function!(commit, BHP256CommitToAddress, BHP256CommitToField, BHP256CommitToGroup,) + } + CommitBHP512(commit) => { + commit_function!(commit, BHP512CommitToAddress, BHP512CommitToField, BHP512CommitToGroup,) + } + CommitBHP768(commit) => { + commit_function!(commit, BHP768CommitToAddress, BHP768CommitToField, BHP768CommitToGroup,) + } + CommitBHP1024(commit) => { + commit_function!(commit, BHP1024CommitToAddress, BHP1024CommitToField, BHP1024CommitToGroup,) + } + CommitPED64(commit) => { + commit_function!(commit, Pedersen64CommitToAddress, Pedersen64CommitToField, Pedersen64CommitToGroup,) + } + CommitPED128(commit) => { + commit_function!(commit, Pedersen128CommitToAddress, Pedersen128CommitToField, Pedersen128CommitToGroup,) + } + Div(div) => binary!(div, Div), + DivWrapped(div_wrapped) => binary!(div_wrapped, DivWrapped), + Double(double) => unary!(double, Double), + GreaterThan(gt) => binary!(gt, Gt), + GreaterThanOrEqual(gte) => binary!(gte, Gte), + HashBHP256(hash) => hash_function!( + hash, + BHP256HashToAddress, + BHP256HashToField, + BHP256HashToGroup, + BHP256HashToI8, + BHP256HashToI16, + BHP256HashToI32, + BHP256HashToI64, + BHP256HashToI128, + BHP256HashToU8, + BHP256HashToU16, + BHP256HashToU32, + BHP256HashToU64, + BHP256HashToU128, + BHP256HashToScalar, + ), + HashBHP512(hash) => hash_function!( + hash, + BHP512HashToAddress, + BHP512HashToField, + BHP512HashToGroup, + BHP512HashToI8, + BHP512HashToI16, + BHP512HashToI32, + BHP512HashToI64, + BHP512HashToI128, + BHP512HashToU8, + BHP512HashToU16, + BHP512HashToU32, + BHP512HashToU64, + BHP512HashToU128, + BHP512HashToScalar, + ), + HashBHP768(hash) => hash_function!( + hash, + BHP768HashToAddress, + BHP768HashToField, + BHP768HashToGroup, + BHP768HashToI8, + BHP768HashToI16, + BHP768HashToI32, + BHP768HashToI64, + BHP768HashToI128, + BHP768HashToU8, + BHP768HashToU16, + BHP768HashToU32, + BHP768HashToU64, + BHP768HashToU128, + BHP768HashToScalar, + ), + HashBHP1024(hash) => hash_function!( + hash, + BHP1024HashToAddress, + BHP1024HashToField, + BHP1024HashToGroup, + BHP1024HashToI8, + BHP1024HashToI16, + BHP1024HashToI32, + BHP1024HashToI64, + BHP1024HashToI128, + BHP1024HashToU8, + BHP1024HashToU16, + BHP1024HashToU32, + BHP1024HashToU64, + BHP1024HashToU128, + BHP1024HashToScalar, + ), + HashKeccak256(hash) => hash_function!( + hash, + Keccak256HashToAddress, + Keccak256HashToField, + Keccak256HashToGroup, + Keccak256HashToI8, + Keccak256HashToI16, + Keccak256HashToI32, + Keccak256HashToI64, + Keccak256HashToI128, + Keccak256HashToU8, + Keccak256HashToU16, + Keccak256HashToU32, + Keccak256HashToU64, + Keccak256HashToU128, + Keccak256HashToScalar, + ), + HashKeccak384(hash) => hash_function!( + hash, + Keccak384HashToAddress, + Keccak384HashToField, + Keccak384HashToGroup, + Keccak384HashToI8, + Keccak384HashToI16, + Keccak384HashToI32, + Keccak384HashToI64, + Keccak384HashToI128, + Keccak384HashToU8, + Keccak384HashToU16, + Keccak384HashToU32, + Keccak384HashToU64, + Keccak384HashToU128, + Keccak384HashToScalar, + ), + HashKeccak512(hash) => hash_function!( + hash, + Keccak512HashToAddress, + Keccak512HashToField, + Keccak512HashToGroup, + Keccak512HashToI8, + Keccak512HashToI16, + Keccak512HashToI32, + Keccak512HashToI64, + Keccak512HashToI128, + Keccak512HashToU8, + Keccak512HashToU16, + Keccak512HashToU32, + Keccak512HashToU64, + Keccak512HashToU128, + Keccak512HashToScalar, + ), + HashPED64(hash) => hash_function!( + hash, + Pedersen64HashToAddress, + Pedersen64HashToField, + Pedersen64HashToGroup, + Pedersen64HashToI8, + Pedersen64HashToI16, + Pedersen64HashToI32, + Pedersen64HashToI64, + Pedersen64HashToI128, + Pedersen64HashToU8, + Pedersen64HashToU16, + Pedersen64HashToU32, + Pedersen64HashToU64, + Pedersen64HashToU128, + Pedersen64HashToScalar, + ), + HashPED128(hash) => hash_function!( + hash, + Pedersen128HashToAddress, + Pedersen128HashToField, + Pedersen128HashToGroup, + Pedersen128HashToI8, + Pedersen128HashToI16, + Pedersen128HashToI32, + Pedersen128HashToI64, + Pedersen128HashToI128, + Pedersen128HashToU8, + Pedersen128HashToU16, + Pedersen128HashToU32, + Pedersen128HashToU64, + Pedersen128HashToU128, + Pedersen128HashToScalar, + ), + HashPSD2(hash) => hash_function!( + hash, + Poseidon2HashToAddress, + Poseidon2HashToField, + Poseidon2HashToGroup, + Poseidon2HashToI8, + Poseidon2HashToI16, + Poseidon2HashToI32, + Poseidon2HashToI64, + Poseidon2HashToI128, + Poseidon2HashToU8, + Poseidon2HashToU16, + Poseidon2HashToU32, + Poseidon2HashToU64, + Poseidon2HashToU128, + Poseidon2HashToScalar, + ), + HashPSD4(hash) => hash_function!( + hash, + Poseidon4HashToAddress, + Poseidon4HashToField, + Poseidon4HashToGroup, + Poseidon4HashToI8, + Poseidon4HashToI16, + Poseidon4HashToI32, + Poseidon4HashToI64, + Poseidon4HashToI128, + Poseidon4HashToU8, + Poseidon4HashToU16, + Poseidon4HashToU32, + Poseidon4HashToU64, + Poseidon4HashToU128, + Poseidon4HashToScalar, + ), + HashPSD8(hash) => hash_function!( + hash, + Poseidon8HashToAddress, + Poseidon8HashToField, + Poseidon8HashToGroup, + Poseidon8HashToI8, + Poseidon8HashToI16, + Poseidon8HashToI32, + Poseidon8HashToI64, + Poseidon8HashToI128, + Poseidon8HashToU8, + Poseidon8HashToU16, + Poseidon8HashToU32, + Poseidon8HashToU64, + Poseidon8HashToU128, + Poseidon8HashToScalar, + ), + HashSha3_256(hash) => hash_function!( + hash, + SHA3_256HashToAddress, + SHA3_256HashToField, + SHA3_256HashToGroup, + SHA3_256HashToI8, + SHA3_256HashToI16, + SHA3_256HashToI32, + SHA3_256HashToI64, + SHA3_256HashToI128, + SHA3_256HashToU8, + SHA3_256HashToU16, + SHA3_256HashToU32, + SHA3_256HashToU64, + SHA3_256HashToU128, + SHA3_256HashToScalar, + ), + HashSha3_384(hash) => hash_function!( + hash, + SHA3_384HashToAddress, + SHA3_384HashToField, + SHA3_384HashToGroup, + SHA3_384HashToI8, + SHA3_384HashToI16, + SHA3_384HashToI32, + SHA3_384HashToI64, + SHA3_384HashToI128, + SHA3_384HashToU8, + SHA3_384HashToU16, + SHA3_384HashToU32, + SHA3_384HashToU64, + SHA3_384HashToU128, + SHA3_384HashToScalar, + ), + HashSha3_512(hash) => hash_function!( + hash, + SHA3_512HashToAddress, + SHA3_512HashToField, + SHA3_512HashToGroup, + SHA3_512HashToI8, + SHA3_512HashToI16, + SHA3_512HashToI32, + SHA3_512HashToI64, + SHA3_512HashToI128, + SHA3_512HashToU8, + SHA3_512HashToU16, + SHA3_512HashToU32, + SHA3_512HashToU64, + SHA3_512HashToU128, + SHA3_512HashToScalar, + ), + HashManyPSD2(_) | HashManyPSD4(_) | HashManyPSD8(_) => panic!("these instructions don't exist yet"), + Inv(inv) => unary!(inv, Inverse), + IsEq(eq) => binary!(eq, Eq), + IsNeq(neq) => binary!(neq, Neq), + LessThan(lt) => binary!(lt, Lt), + LessThanOrEqual(lte) => binary!(lte, Lte), + Modulo(modulo) => binary!(modulo, Mod), + Mul(mul) => binary!(mul, Mul), + MulWrapped(mul_wrapped) => binary!(mul_wrapped, MulWrapped), + Nand(nand) => binary!(nand, Nand), + Neg(neg) => unary!(neg, Negate), + Nor(nor) => binary!(nor, Nor), + Not(not) => unary!(not, Not), + Or(or) => binary!(or, BitwiseOr), + Pow(pow) => binary!(pow, Pow), + PowWrapped(pow_wrapped) => binary!(pow_wrapped, PowWrapped), + Rem(rem) => binary!(rem, Rem), + RemWrapped(rem_wrapped) => binary!(rem_wrapped, RemWrapped), + Shl(shl) => binary!(shl, Shl), + ShlWrapped(shl_wrapped) => binary!(shl_wrapped, ShlWrapped), + Shr(shr) => binary!(shr, Shr), + ShrWrapped(shr_wrapped) => binary!(shr_wrapped, ShrWrapped), + SignVerify(_) => todo!(), + Square(square) => unary!(square, Square), + SquareRoot(sqrt) => unary!(sqrt, SquareRoot), + Sub(sub) => binary!(sub, Sub), + SubWrapped(sub_wrapped) => binary!(sub_wrapped, SubWrapped), + Ternary(ternary) => { + let condition = self.operand_value(&ternary.operands()[0]); + let result = match condition { + Value::Bool(true) => &ternary.operands()[1], + Value::Bool(false) => &ternary.operands()[2], + _ => panic!(), + }; + self.increment_instruction_index(); + (self.operand_value(result), ternary.destinations()[0].clone()) + } + Xor(xor) => binary!(xor, Xor), + }; + + self.set_register(destination, value); + + Ok(()) + } + + fn outputs(&self) -> Vec { + let Some(Frame { element, .. }) = self.frames.last() else { + panic!("frame expected"); + }; + let Element::AleoExecution { context, .. } = element else { + panic!("aleo execution expected"); + }; + + let mut result = match context { + AleoContext::Closure(closure) => { + closure.outputs().iter().map(|output| self.operand_value(output.operand())).collect() + } + AleoContext::Function(function) => { + function.outputs().iter().map(|output| self.operand_value(output.operand())).collect() + } + AleoContext::Finalize(_finalize) => Vec::new(), + }; + + if result.is_empty() { + result.push(Value::Unit); + } + result + } + + fn step_aleo_command(&mut self, command: Command) -> Result<()> { + use Command::*; + + let (value, destination) = match command { + Instruction(instruction) => { + self.step_aleo_instruction(instruction)?; + return Ok(()); + } + Await(await_) => { + let Value::Future(future) = self.get_register(await_.register()) else { + halt_no_span!("attempted to await a non-future"); + }; + self.contexts.add_future(future.clone()); + self.increment_instruction_index(); + return Ok(()); + } + Contains(contains) => { + let mapping = self.mapping_by_call_operator(contains.mapping()).expect("mapping should be present"); + let key = self.operand_value(contains.key()); + let result = Value::Bool(mapping.contains_key(&key)); + self.increment_instruction_index(); + (result, contains.destination().clone()) + } + Get(get) => { + let key = self.operand_value(get.key()); + let value = self.mapping_by_call_operator(get.mapping()).and_then(|mapping| mapping.get(&key)).cloned(); + self.increment_instruction_index(); + + match value { + Some(v) => (v, get.destination().clone()), + None => halt_no_span!("map access failure: {key}"), + } + } + GetOrUse(get_or_use) => { + let key = self.operand_value(get_or_use.key()); + let value = + self.mapping_by_call_operator(get_or_use.mapping()).and_then(|mapping| mapping.get(&key)).cloned(); + + let use_value = value.unwrap_or_else(|| self.operand_value(get_or_use.default())); + self.increment_instruction_index(); + + (use_value, get_or_use.destination().clone()) + } + Remove(remove) => { + let key = self.operand_value(remove.key()); + let mapping_name = snarkvm_identifier_to_symbol(remove.mapping_name()); + let maybe_mapping = self.lookup_mapping_mut(None, mapping_name); + match maybe_mapping { + None => halt_no_span!("no such mapping {mapping_name}"), + Some(mapping) => { + mapping.remove(&key); + } + } + self.increment_instruction_index(); + return Ok(()); + } + Set(set) => { + let key = self.operand_value(set.key()); + let value = self.operand_value(set.value()); + let mapping_name = snarkvm_identifier_to_symbol(set.mapping_name()); + let maybe_mapping = self.lookup_mapping_mut(None, mapping_name); + match maybe_mapping { + None => halt_no_span!("no such mapping {mapping_name}"), + Some(mapping) => { + mapping.insert(key, value); + } + } + self.increment_instruction_index(); + return Ok(()); + } + RandChaCha(rand) => { + // If there are operands, they are additional seeds. + let mut bits = Vec::new(); + for value in rand.operands().iter().map(|op| self.operand_value(op)) { + value.write_bits_le(&mut bits); + } + let field: Field = self.rng.gen(); + field.write_bits_le(&mut bits); + let seed_vec = TestnetV0::hash_bhp1024(&bits)?.to_bytes_le()?; + let mut seed = [0u8; 32]; + seed.copy_from_slice(&seed_vec[..32]); + let mut rng = ChaCha20Rng::from_seed(seed); + let value = match rand.destination_type() { + LiteralType::Address => Value::Address(rng.gen()), + LiteralType::Boolean => Value::Bool(rng.gen()), + LiteralType::Field => Value::Field(rng.gen()), + LiteralType::Group => Value::Group(rng.gen()), + LiteralType::I8 => Value::I8(rng.gen()), + LiteralType::I16 => Value::I16(rng.gen()), + LiteralType::I32 => Value::I32(rng.gen()), + LiteralType::I64 => Value::I64(rng.gen()), + LiteralType::I128 => Value::I128(rng.gen()), + LiteralType::U8 => Value::U8(rng.gen()), + LiteralType::U16 => Value::U16(rng.gen()), + LiteralType::U32 => Value::U32(rng.gen()), + LiteralType::U64 => Value::U64(rng.gen()), + LiteralType::U128 => Value::U128(rng.gen()), + LiteralType::Scalar => Value::Scalar(rng.gen()), + LiteralType::Signature => halt_no_span!("Cannot create a random signature"), + LiteralType::String => halt_no_span!("Cannot create a random string"), + }; + self.increment_instruction_index(); + (value, rand.destination().clone()) + } + BranchEq(branch_eq) => { + let first = self.operand_value(branch_eq.first()); + let second = self.operand_value(branch_eq.second()); + if first.eq(&second)? { + self.branch(branch_eq.position()); + } else { + self.increment_instruction_index(); + } + return Ok(()); + } + BranchNeq(branch_neq) => { + let first = self.operand_value(branch_neq.first()); + let second = self.operand_value(branch_neq.second()); + if first.neq(&second)? { + self.branch(branch_neq.position()); + } else { + self.increment_instruction_index(); + } + return Ok(()); + } + Position(_) => return Ok(()), + }; + + self.set_register(destination, value); + + Ok(()) + } + + fn branch(&mut self, label: &Identifier) { + let Some(Frame { + element: Element::AleoExecution { instruction_index, context: AleoContext::Finalize(finalize), .. }, + .. + }) = self.frames.last_mut() + else { + panic!(); + }; + for (i, cmd) in finalize.commands().iter().enumerate() { + if let Command::Position(position) = cmd { + if position.name() == label { + *instruction_index = i; + return; + } + } + } + panic!("branch to nonexistent label {}", label); + } + + pub fn step_aleo(&mut self) -> Result<()> { + if let Some(command) = self.next_command().cloned() { + self.step_aleo_command(command)?; + } else if let Some(instruction) = self.next_instruction().cloned() { + self.step_aleo_instruction(instruction)?; + } + + if self.execution_complete() { + let mut outputs = self.outputs(); + self.frames.pop(); + self.contexts.pop(); + if outputs.len() > 1 { + self.values.push(Value::Tuple(outputs)); + } else { + self.values.push(mem::take(&mut outputs[0])); + } + } + + Ok(()) + } +} + +fn snarkvm_literal_type_to_type(snarkvm_type: LiteralType) -> Type { + use Type::*; + match snarkvm_type { + LiteralType::Address => Address, + LiteralType::Boolean => Boolean, + LiteralType::Field => Field, + LiteralType::Group => Group, + LiteralType::I8 => Integer(IntegerType::I8), + LiteralType::I16 => Integer(IntegerType::I16), + LiteralType::I32 => Integer(IntegerType::I32), + LiteralType::I64 => Integer(IntegerType::I64), + LiteralType::I128 => Integer(IntegerType::I128), + LiteralType::U8 => Integer(IntegerType::U8), + LiteralType::U16 => Integer(IntegerType::U16), + LiteralType::U32 => Integer(IntegerType::U32), + LiteralType::U64 => Integer(IntegerType::U64), + LiteralType::U128 => Integer(IntegerType::U128), + LiteralType::Scalar => Scalar, + LiteralType::Signature => todo!(), + LiteralType::String => todo!(), + } +} + +fn snarkvm_literal_to_value(literal: Literal) -> Value { + match literal { + Literal::Address(x) => Value::Address(x), + Literal::Boolean(x) => Value::Bool(*x), + Literal::Field(x) => Value::Field(x), + Literal::Group(x) => Value::Group(x), + Literal::I8(x) => Value::I8(*x), + Literal::I16(x) => Value::I16(*x), + Literal::I32(x) => Value::I32(*x), + Literal::I64(x) => Value::I64(*x), + Literal::I128(x) => Value::I128(*x), + Literal::U8(x) => Value::U8(*x), + Literal::U16(x) => Value::U16(*x), + Literal::U32(x) => Value::U32(*x), + Literal::U64(x) => Value::U64(*x), + Literal::U128(x) => Value::U128(*x), + Literal::Scalar(x) => Value::Scalar(x), + Literal::Signature(_) | Literal::String(_) => tc_fail!(), + } +} + +fn value_to_snarkvm_literal(value: Value) -> Literal { + match value { + Value::Bool(x) => Literal::Boolean(Boolean::new(x)), + Value::U8(x) => Literal::U8(Integer::new(x)), + Value::U16(x) => Literal::U16(Integer::new(x)), + Value::U32(x) => Literal::U32(Integer::new(x)), + Value::U64(x) => Literal::U64(Integer::new(x)), + Value::U128(x) => Literal::U128(Integer::new(x)), + Value::I8(x) => Literal::I8(Integer::new(x)), + Value::I16(x) => Literal::I16(Integer::new(x)), + Value::I32(x) => Literal::I32(Integer::new(x)), + Value::I64(x) => Literal::I64(Integer::new(x)), + Value::I128(x) => Literal::I128(Integer::new(x)), + Value::Group(x) => Literal::Group(x), + Value::Field(x) => Literal::Field(x), + Value::Scalar(x) => Literal::Scalar(x), + Value::Address(x) => Literal::Address(x), + Value::Array(_) | Value::Tuple(_) | Value::Unit | Value::Future(_) | Value::Struct(_) => tc_fail!(), + } +} diff --git a/interpreter/src/dialoguer_input.rs b/interpreter/src/dialoguer_input.rs new file mode 100644 index 0000000000..67926ddfb1 --- /dev/null +++ b/interpreter/src/dialoguer_input.rs @@ -0,0 +1,62 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::ui::{Ui, UserData}; + +use colored::*; + +pub struct DialoguerUi { + history: dialoguer::BasicHistory, +} + +impl DialoguerUi { + pub fn new() -> Self { + DialoguerUi { history: dialoguer::BasicHistory::new() } + } +} + +impl Ui for DialoguerUi { + fn display_user_data(&mut self, data: &UserData<'_>) { + if let Some(result) = data.result { + println!("{}: {}", "Result".bold(), result.bright_cyan()); + } + println!("{}", data.message); + if let Some(highlight_span) = data.highlight { + let first = data.code.get(0..highlight_span.0).expect("spans should be valid"); + let second = data.code.get(highlight_span.0..highlight_span.1).expect("spans should be valid"); + let third = data.code.get(highlight_span.1..).expect("spans should be valid"); + println!("{first}{}{third}", second.red()); + } else { + println!("{}", data.code); + } + + for (i, future) in data.futures.iter().enumerate() { + println!("{i:>4}: {future}"); + } + + for (i, watchpoint) in data.watchpoints.iter().enumerate() { + println!("{i:>4}: {watchpoint}"); + } + } + + fn receive_user_input(&mut self) -> String { + dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) + .with_prompt("Command?") + .history_with(&mut self.history) + .interact_text() + .unwrap() + } +} diff --git a/interpreter/src/interpreter.rs b/interpreter/src/interpreter.rs new file mode 100644 index 0000000000..efc7f51178 --- /dev/null +++ b/interpreter/src/interpreter.rs @@ -0,0 +1,453 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; + +use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler}; + +/// Contains the state of interpretation, in the form of the `Cursor`, +/// as well as information needed to interact with the user, like +/// the breakpoints. +pub struct Interpreter { + pub cursor: Cursor<'static>, + actions: Vec, + handler: Handler, + node_builder: NodeBuilder, + breakpoints: Vec, + pub watchpoints: Vec, + saved_cursors: Vec>, + filename_to_program: HashMap, + parsed_inputs: u32, +} + +#[derive(Clone, Debug)] +pub struct Breakpoint { + pub program: String, + pub line: usize, +} + +#[derive(Clone, Debug)] +pub struct Watchpoint { + pub code: String, + pub last_result: Option, +} + +#[derive(Clone, Debug)] +pub enum InterpreterAction { + LeoInterpretInto(String), + LeoInterpretOver(String), + Watch(String), + RunFuture(usize), + Breakpoint(Breakpoint), + PrintRegister(u64), + Into, + Over, + Step, + Run, +} + +impl Interpreter { + pub fn new<'a, P: 'a + AsRef, Q: 'a + AsRef>( + leo_source_files: impl IntoIterator, + aleo_source_files: impl IntoIterator, + signer: SvmAddress, + block_height: u32, + ) -> Result { + Self::new_impl( + &mut leo_source_files.into_iter().map(|p| p.as_ref()), + &mut aleo_source_files.into_iter().map(|p| p.as_ref()), + signer, + block_height, + ) + } + + fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result { + let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; + let filename = FileName::Real(path.to_path_buf()); + let source_file = with_session_globals(|s| s.source_map.new_source(&text, filename)); + leo_parser::parse_ast::(handler, node_builder, &text, source_file.start_pos) + } + + fn new_impl( + leo_source_files: &mut dyn Iterator, + aleo_source_files: &mut dyn Iterator, + signer: SvmAddress, + block_height: u32, + ) -> Result { + let handler = Handler::default(); + let node_builder = Default::default(); + let mut cursor: Cursor<'_> = Cursor::new( + true, // really_async + signer, + block_height, + ); + let mut filename_to_program = HashMap::new(); + for path in leo_source_files { + let ast = Self::get_ast(path, &handler, &node_builder)?; + // TODO: This leak is silly. + let ast = Box::leak(Box::new(ast)); + for (&program, scope) in ast.ast.program_scopes.iter() { + filename_to_program.insert(path.to_path_buf(), program.to_string()); + for (name, function) in scope.functions.iter() { + cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function)); + } + + for (name, composite) in scope.structs.iter() { + cursor.structs.insert( + GlobalId { program, name: *name }, + composite.members.iter().map(|member| member.identifier.name).collect(), + ); + } + + for (name, _mapping) in scope.mappings.iter() { + cursor.mappings.insert(GlobalId { program, name: *name }, HashMap::new()); + } + + for (name, const_declaration) in scope.consts.iter() { + cursor.frames.push(Frame { + step: 0, + element: Element::Expression(&const_declaration.value), + user_initiated: false, + }); + cursor.over()?; + let value = cursor.values.pop().unwrap(); + cursor.globals.insert(GlobalId { program, name: *name }, value); + } + } + } + + for path in aleo_source_files { + let aleo_program = Self::get_aleo_program(path)?; + // TODO: Another goofy leak. + let aleo_program = Box::leak(Box::new(aleo_program)); + let program = snarkvm_identifier_to_symbol(aleo_program.id().name()); + filename_to_program.insert(path.to_path_buf(), program.to_string()); + + for (name, struct_type) in aleo_program.structs().iter() { + cursor.structs.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(), + ); + } + + for (name, record_type) in aleo_program.records().iter() { + cursor.structs.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(), + ); + } + + for (name, _mapping) in aleo_program.mappings().iter() { + cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new()); + } + + for (name, function) in aleo_program.functions().iter() { + cursor.functions.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + FunctionVariant::AleoFunction(function), + ); + } + + for (name, closure) in aleo_program.closures().iter() { + cursor.functions.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + FunctionVariant::AleoClosure(closure), + ); + } + } + + Ok(Interpreter { + cursor, + handler, + node_builder, + actions: Vec::new(), + breakpoints: Vec::new(), + watchpoints: Vec::new(), + saved_cursors: Vec::new(), + filename_to_program, + parsed_inputs: 0, + }) + } + + pub fn save_cursor(&mut self) { + self.saved_cursors.push(self.cursor.clone()); + } + + /// Returns false if there was no saved cursor to restore. + pub fn restore_cursor(&mut self) -> bool { + if let Some(old_cursor) = self.saved_cursors.pop() { + self.cursor = old_cursor; + true + } else { + false + } + } + + fn get_aleo_program(path: &Path) -> Result> { + let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; + let program = text.parse()?; + Ok(program) + } + + /// Returns true if any watchpoints changed. + pub fn update_watchpoints(&mut self) -> Result { + let mut changed = false; + let safe_cursor = self.cursor.clone(); + + for i in 0..self.watchpoints.len() { + let code = self.watchpoints[i].code.clone(); + let new_value = match self.action(InterpreterAction::LeoInterpretOver(code)) { + Ok(None) => None, + Ok(Some(ret)) => Some(ret.to_string()), + Err(LeoError::InterpreterHalt(halt)) => { + self.cursor = safe_cursor.clone(); + Some(halt.to_string()) + } + Err(e) => return Err(e), + }; + if self.watchpoints[i].last_result != new_value { + changed = true; + self.watchpoints[i].last_result = new_value; + } + } + Ok(changed) + } + + pub fn action(&mut self, act: InterpreterAction) -> Result> { + use InterpreterAction::*; + + let ret = match &act { + RunFuture(n) => { + let future = self.cursor.futures.remove(*n); + for async_exec in future.0.into_iter().rev() { + self.cursor.values.extend(async_exec.arguments); + self.cursor.frames.push(Frame { + step: 0, + element: Element::DelayedCall(async_exec.function), + user_initiated: true, + }); + } + self.cursor.step()? + } + LeoInterpretInto(s) | LeoInterpretOver(s) => { + let filename = FileName::Custom(format!("user_input{:04}", self.parsed_inputs)); + self.parsed_inputs += 1; + let source_file = with_session_globals(|globals| globals.source_map.new_source(s, filename)); + let s = s.trim(); + if s.ends_with(';') { + let statement = leo_parser::parse_statement::( + &self.handler, + &self.node_builder, + s, + source_file.start_pos, + ) + .map_err(|_e| { + LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into())) + })?; + // TODO: This leak is silly. + let stmt = Box::leak(Box::new(statement)); + + // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. + self.cursor.frames.push(Frame { step: 0, element: Element::Statement(stmt), user_initiated: true }); + } else { + let expression = leo_parser::parse_expression::( + &self.handler, + &self.node_builder, + s, + source_file.start_pos, + ) + .map_err(|e| { + LeoError::InterpreterHalt(InterpreterHalt::new(format!("Failed to parse expression: {e}"))) + })?; + // TODO: This leak is silly. + let expr = Box::leak(Box::new(expression)); + + // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. + self.cursor.frames.push(Frame { + step: 0, + element: Element::Expression(expr), + user_initiated: true, + }); + }; + + if matches!(act, LeoInterpretOver(..)) { + self.cursor.over()? + } else { + StepResult { finished: false, value: None } + } + } + + Step => self.cursor.whole_step()?, + + Into => self.cursor.step()?, + + Over => self.cursor.over()?, + + Breakpoint(breakpoint) => { + self.breakpoints.push(breakpoint.clone()); + StepResult { finished: false, value: None } + } + + Watch(code) => { + self.watchpoints.push(Watchpoint { code: code.clone(), last_result: None }); + StepResult { finished: false, value: None } + } + + PrintRegister(register_index) => { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last() + else { + halt_no_span!("cannot print register - not currently interpreting Aleo VM code"); + }; + + if let Some(value) = registers.get(register_index) { + StepResult { finished: false, value: Some(value.clone()) } + } else { + halt_no_span!("no such register {register_index}"); + } + } + + Run => { + while !self.cursor.frames.is_empty() { + if let Some((program, line)) = self.current_program_and_line() { + if self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line) { + return Ok(None); + } + } + self.cursor.step()?; + if self.update_watchpoints()? { + return Ok(None); + } + } + StepResult { finished: false, value: None } + } + }; + + self.actions.push(act); + + Ok(ret.value) + } + + pub fn view_current(&self) -> Option { + if let Some(span) = self.current_span() { + if span != Default::default() { + return with_session_globals(|s| s.source_map.contents_of_span(span)); + } + } + + Some(match self.cursor.frames.last()?.element { + Element::Statement(statement) => format!("{statement}"), + Element::Expression(expression) => format!("{expression}"), + Element::Block { block, .. } => format!("{block}"), + Element::DelayedCall(gid) => format!("Delayed call to {gid}"), + Element::AleoExecution { context, instruction_index, .. } => match context { + AleoContext::Closure(closure) => closure.instructions().get(instruction_index).map(|i| format!("{i}")), + AleoContext::Function(function) => { + function.instructions().get(instruction_index).map(|i| format!("{i}")) + } + AleoContext::Finalize(finalize) => finalize.commands().get(instruction_index).map(|i| format!("{i}")), + } + .unwrap_or_else(|| "...".to_string()), + }) + } + + pub fn view_current_in_context(&self) -> Option<(impl Display, usize, usize)> { + if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) = + self.cursor.frames.last() + { + // For Aleo VM code, there are no spans; just print out the code without referring to the source code. + + fn write_all( + items: impl Iterator, + instruction_index: usize, + result: &mut String, + start: &mut usize, + stop: &mut usize, + ) { + for (i, item) in items.enumerate() { + if i == instruction_index { + *start = result.len(); + } + writeln!(result, " {item}").expect("write shouldn't fail"); + if i == instruction_index { + *stop = result.len(); + } + } + } + + let mut result = String::new(); + let mut start: usize = 0usize; + let mut stop: usize = 0usize; + + match context { + AleoContext::Closure(closure) => { + writeln!(&mut result, "closure {}", closure.name()).expect("write shouldn't fail"); + write_all(closure.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + write_all(closure.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop); + write_all(closure.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + } + AleoContext::Function(function) => { + writeln!(&mut result, "function {}", function.name()).expect("write shouldn't fail"); + write_all(function.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + write_all(function.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop); + write_all(function.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + } + AleoContext::Finalize(finalize) => { + writeln!(&mut result, "finalize {}", finalize.name()).expect("write shouldn't fail"); + write_all(finalize.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + write_all(finalize.commands().iter(), *instruction_index, &mut result, &mut start, &mut stop); + } + } + + Some((result, start, stop)) + } else { + // For Leo code, we use spans to print the original source code. + let span = self.current_span()?; + if span == Default::default() { + return None; + } + with_session_globals(|s| { + let source_file = s.source_map.find_source_file(span.lo)?; + let first_span = Span::new(source_file.start_pos, span.lo); + let last_span = Span::new(span.hi, source_file.end_pos); + let mut result = String::new(); + result.push_str(&s.source_map.contents_of_span(first_span)?); + let start = result.len(); + result.push_str(&s.source_map.contents_of_span(span)?); + let stop = result.len(); + result.push_str(&s.source_map.contents_of_span(last_span)?); + Some((result, start, stop)) + }) + } + } + + fn current_program_and_line(&self) -> Option<(String, usize)> { + if let Some(span) = self.current_span() { + if let Some(location) = with_session_globals(|s| s.source_map.span_to_location(span)) { + let line = location.line_start; + if let FileName::Real(name) = &location.source_file.name { + if let Some(program) = self.filename_to_program.get(name) { + return Some((program.clone(), line)); + } + } + } + } + None + } + + fn current_span(&self) -> Option { + self.cursor.frames.last().map(|f| f.element.span()) + } +} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs new file mode 100644 index 0000000000..cdbd9d6ac6 --- /dev/null +++ b/interpreter/src/lib.rs @@ -0,0 +1,267 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use leo_ast::{Ast, Node as _, NodeBuilder}; +use leo_errors::{InterpreterHalt, LeoError, Result}; +use leo_span::{Span, source_map::FileName, symbol::with_session_globals}; + +use snarkvm::prelude::{Program, TestnetV0}; + +use std::{ + collections::HashMap, + fmt::{Display, Write as _}, + fs, + path::{Path, PathBuf}, +}; + +#[cfg(test)] +mod test; + +mod util; +use util::*; + +mod cursor; +use cursor::*; + +mod interpreter; +use interpreter::*; + +mod cursor_aleo; + +mod value; +use value::*; + +mod ui; +use ui::Ui; + +mod dialoguer_input; + +mod ratatui_ui; + +const INTRO: &str = "This is the Leo Interpreter. Try the command `#help`."; + +const HELP: &str = " +You probably want to start by running a function or transition. +For instance +#into program.aleo/main() +Once a function is running, commands include +#into to evaluate into the next expression or statement; +#step to take one step towards evaluating the current expression or statement; +#over to complete evaluating the current expression or statement; +#run to finish evaluating +#quit to quit the interpreter. + +You can set a breakpoint with +#break program_name line_number + +When executing Aleo VM code, you can print the value of a register like this: +#print 2 + +Some of the commands may be run with one letter abbreviations, such as #i. + +Note that this interpreter is not line oriented as in many common debuggers; +rather it is oriented around expressions and statements. +As you step into code, individual expressions or statements will +be evaluated one by one, including arguments of function calls. + +You may simply enter Leo expressions or statements on the command line +to evaluate. For instance, if you want to see the value of a variable w: +w +If you want to set w to a new value: +w = z + 2u8; + +Note that statements (like the assignment above) must end with a semicolon. + +If there are futures available to be executed, they will be listed by +numerical index, and you may run them using `#future` (or `#f`); for instance +#future 0 + +The interpreter begins in a global context, not in any Leo program. You can set +the current program with + +#set_program program_name + +This allows you to refer to structs and other items in the indicated program. + +The interpreter may enter an invalid state, often due to Leo code entered at the +REPL. In this case, you may use the command + +#restore + +Which will restore to the last saved state of the interpreter. Any time you +enter Leo code at the prompt, interpreter state is saved. + +Input history is available - use the up and down arrow keys. +"; + +fn parse_breakpoint(s: &str) -> Option { + let strings: Vec<&str> = s.split_whitespace().collect(); + if strings.len() == 2 { + if let Ok(line) = strings[1].parse::() { + let program = strings[0].strip_suffix(".aleo").unwrap_or(strings[0]).to_string(); + return Some(Breakpoint { program, line }); + } + } + None +} + +/// Load all the Leo source files indicated and open the interpreter +/// to commands from the user. +pub fn interpret( + leo_filenames: &[PathBuf], + aleo_filenames: &[PathBuf], + signer: SvmAddress, + block_height: u32, + tui: bool, +) -> Result<()> { + let mut interpreter = Interpreter::new(leo_filenames.iter(), aleo_filenames.iter(), signer, block_height)?; + + let mut user_interface: Box = + if tui { Box::new(ratatui_ui::RatatuiUi::new()) } else { Box::new(dialoguer_input::DialoguerUi::new()) }; + + let mut code = String::new(); + let mut futures = Vec::new(); + let mut watchpoints = Vec::new(); + let mut message = INTRO.to_string(); + let mut result = String::new(); + + loop { + code.clear(); + futures.clear(); + watchpoints.clear(); + + let (code, highlight) = if let Some((code, lo, hi)) = interpreter.view_current_in_context() { + (code.to_string(), Some((lo, hi))) + } else if let Some(v) = interpreter.view_current() { + (v.to_string(), None) + } else { + ("".to_string(), None) + }; + + futures.extend(interpreter.cursor.futures.iter().map(|f| f.to_string())); + + interpreter.update_watchpoints()?; + + watchpoints.extend(interpreter.watchpoints.iter().map(|watchpoint| { + format!("{:>15} = {}", watchpoint.code, if let Some(s) = &watchpoint.last_result { &**s } else { "?" }) + })); + + let user_data = ui::UserData { + code: &code, + highlight, + message: &message, + futures: &futures, + watchpoints: &watchpoints, + result: if result.is_empty() { None } else { Some(&result) }, + }; + + user_interface.display_user_data(&user_data); + + message.clear(); + result.clear(); + + let user_input = user_interface.receive_user_input(); + + let (command, rest) = tokenize_user_input(&user_input); + + let action = match (command, rest) { + ("", "") => continue, + ("#h" | "#help", "") => { + message = HELP.to_string(); + continue; + } + ("#i" | "#into", "") => InterpreterAction::Into, + ("#i" | "#into", rest) => InterpreterAction::LeoInterpretInto(rest.into()), + ("#s" | "#step", "") => InterpreterAction::Step, + ("#o" | "#over", "") => InterpreterAction::Over, + ("#r" | "#run", "") => InterpreterAction::Run, + ("#q" | "#quit", "") => return Ok(()), + ("#f" | "#future", rest) => { + if let Ok(num) = rest.trim().parse::() { + if num >= interpreter.cursor.futures.len() { + message = "No such future.".to_string(); + continue; + } + InterpreterAction::RunFuture(num) + } else { + message = "Failed to parse future.".to_string(); + continue; + } + } + ("#restore", "") => { + if !interpreter.restore_cursor() { + message = "No saved state to restore".to_string(); + } + continue; + } + ("#b" | "#break", rest) => { + let Some(breakpoint) = parse_breakpoint(rest) else { + message = "Failed to parse breakpoint".to_string(); + continue; + }; + InterpreterAction::Breakpoint(breakpoint) + } + ("#p" | "#print", rest) => { + let without_r = rest.strip_prefix("r").unwrap_or(rest); + if let Ok(num) = without_r.parse::() { + InterpreterAction::PrintRegister(num) + } else { + message = "Failed to parse register number".to_string(); + continue; + } + } + ("#w" | "#watch", rest) => InterpreterAction::Watch(rest.to_string()), + ("#set_program", rest) => { + interpreter.cursor.set_program(rest); + continue; + } + ("", rest) => InterpreterAction::LeoInterpretOver(rest.to_string()), + _ => { + message = "Failed to parse command".to_string(); + continue; + } + }; + + if matches!(action, InterpreterAction::LeoInterpretInto(..) | InterpreterAction::LeoInterpretOver(..)) { + interpreter.save_cursor(); + } + + match interpreter.action(action) { + Ok(Some(value)) => { + result = value.to_string(); + } + Ok(None) => {} + Err(LeoError::InterpreterHalt(interpreter_halt)) => { + message = format!("Halted: {interpreter_halt}"); + } + Err(e) => return Err(e), + } + } +} + +fn tokenize_user_input(input: &str) -> (&str, &str) { + let input = input.trim(); + + if !input.starts_with("#") { + return ("", input); + } + + let Some((first, rest)) = input.split_once(' ') else { + return (input, ""); + }; + + (first.trim(), rest.trim()) +} diff --git a/interpreter/src/ratatui_ui.rs b/interpreter/src/ratatui_ui.rs new file mode 100644 index 0000000000..06a02e1c29 --- /dev/null +++ b/interpreter/src/ratatui_ui.rs @@ -0,0 +1,370 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::ui::{Ui, UserData}; + +use std::{cmp, collections::VecDeque, io::Stdout, mem}; + +use crossterm::event::{self, Event, KeyCode, KeyModifiers}; +use ratatui::{ + Frame, + Terminal, + prelude::{ + Buffer, + Constraint, + CrosstermBackend, + Direction, + Layout, + Line, + Modifier, + Rect, + Span, + Style, + Stylize as _, + }, + text::Text, + widgets::{Block, Paragraph, Widget}, +}; + +#[derive(Default)] +struct DrawData { + code: String, + highlight: Option<(usize, usize)>, + result: String, + watchpoints: Vec, + message: String, + prompt: Prompt, +} + +pub struct RatatuiUi { + terminal: Terminal>, + data: DrawData, +} + +impl Drop for RatatuiUi { + fn drop(&mut self) { + ratatui::restore(); + } +} + +impl RatatuiUi { + pub fn new() -> Self { + RatatuiUi { terminal: ratatui::init(), data: Default::default() } + } +} + +fn append_lines<'a>( + lines: &mut Vec>, + mut last_chunk: Option>, + string: &'a str, + style: Style, +) -> Option> { + let mut line_iter = string.lines().peekable(); + while let Some(line) = line_iter.next() { + let this_span = Span::styled(line, style); + let mut real_last_chunk = mem::take(&mut last_chunk).unwrap_or_else(|| Line::raw("")); + real_last_chunk.push_span(this_span); + if line_iter.peek().is_some() { + lines.push(real_last_chunk); + } else if string.ends_with('\n') { + lines.push(real_last_chunk); + return None; + } else { + return Some(real_last_chunk); + } + } + + last_chunk +} + +fn code_text(s: &str, highlight: Option<(usize, usize)>) -> (Text, usize) { + let Some((lo, hi)) = highlight else { + return (Text::from(s), 0); + }; + + let s1 = s.get(..lo).expect("should be able to split text"); + let s2 = s.get(lo..hi).expect("should be able to split text"); + let s3 = s.get(hi..).expect("should be able to split text"); + + let mut lines = Vec::new(); + + let s1_chunk = append_lines(&mut lines, None, s1, Style::default()); + let line = lines.len(); + let s2_chunk = append_lines(&mut lines, s1_chunk, s2, Style::new().red()); + let s3_chunk = append_lines(&mut lines, s2_chunk, s3, Style::default()); + + if let Some(chunk) = s3_chunk { + lines.push(chunk); + } + + (Text::from(lines), line) +} + +struct DebuggerLayout { + code: Rect, + result: Rect, + watchpoints: Rect, + user_input: Rect, + message: Rect, +} + +impl DebuggerLayout { + fn new(total: Rect) -> Self { + let overall_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(1), // Code + Constraint::Length(6), // Result and watchpoints + Constraint::Length(3), // Message + Constraint::Length(3), // User input + ]) + .split(total); + let code = overall_layout[0]; + let middle = overall_layout[1]; + let message = overall_layout[2]; + let user_input = overall_layout[3]; + + let middle = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(middle); + + DebuggerLayout { code, result: middle[0], watchpoints: middle[1], user_input, message } + } +} + +#[derive(Debug, Default)] +struct Prompt { + history: VecDeque, + history_index: usize, + current: String, + cursor: usize, +} + +impl<'a> Widget for &'a Prompt { + fn render(self, area: Rect, buf: &mut Buffer) { + let mut plain = || { + Text::raw(&self.current).render(area, buf); + }; + + if self.cursor >= self.current.len() { + let span1 = Span::raw(&self.current); + let span2 = Span::styled(" ", Style::new().add_modifier(Modifier::REVERSED)); + Text::from(Line::from_iter([span1, span2])).render(area, buf); + return; + } + + let Some(pre) = self.current.get(..self.cursor) else { + plain(); + return; + }; + + let Some(c) = self.current.get(self.cursor..self.cursor + 1) else { + plain(); + return; + }; + + let Some(post) = self.current.get(self.cursor + 1..) else { + plain(); + return; + }; + + Text::from(Line::from_iter([ + Span::raw(pre), + Span::styled(c, Style::new().add_modifier(Modifier::REVERSED)), + Span::raw(post), + ])) + .render(area, buf); + } +} + +impl Prompt { + fn handle_key(&mut self, key: KeyCode, control: bool) -> Option { + match (key, control) { + (KeyCode::Enter, _) => { + self.history.push_back(mem::take(&mut self.current)); + self.history_index = self.history.len(); + return self.history.back().cloned(); + } + (KeyCode::Backspace, _) => self.backspace(), + (KeyCode::Left, _) => self.left(), + (KeyCode::Right, _) => self.right(), + (KeyCode::Up, _) => self.history_prev(), + (KeyCode::Down, _) => self.history_next(), + (KeyCode::Delete, _) => self.delete(), + (KeyCode::Char(c), false) => self.new_character(c), + (KeyCode::Char('a'), true) => self.beginning_of_line(), + (KeyCode::Char('e'), true) => self.end_of_line(), + _ => {} + } + + None + } + + fn new_character(&mut self, c: char) { + if self.cursor >= self.current.len() { + self.current.push(c); + self.cursor = self.current.len(); + } else { + let Some(pre) = self.current.get(..self.cursor) else { + return; + }; + let Some(post) = self.current.get(self.cursor..) else { + return; + }; + let mut with_char = format!("{pre}{c}"); + self.cursor = with_char.len(); + with_char.push_str(post); + self.current = with_char; + } + self.check_history(); + } + + fn right(&mut self) { + self.cursor = cmp::min(self.cursor + 1, self.current.len()); + } + + fn left(&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + + fn backspace(&mut self) { + if self.cursor == 0 { + return; + } + + if self.cursor >= self.current.len() { + self.current.pop(); + self.cursor = self.current.len(); + return; + } + + let Some(pre) = self.current.get(..self.cursor - 1) else { + return; + }; + let Some(post) = self.current.get(self.cursor..) else { + return; + }; + self.cursor -= 1; + + let s = format!("{pre}{post}"); + + self.current = s; + + self.check_history(); + } + + fn delete(&mut self) { + if self.cursor + 1 >= self.current.len() { + return; + } + + let Some(pre) = self.current.get(..self.cursor) else { + return; + }; + let Some(post) = self.current.get(self.cursor + 1..) else { + return; + }; + + let s = format!("{pre}{post}"); + + self.current = s; + + self.check_history(); + } + + fn beginning_of_line(&mut self) { + self.cursor = 0; + } + + fn end_of_line(&mut self) { + self.cursor = self.current.len(); + } + + fn history_next(&mut self) { + self.history_index += 1; + if self.history_index > self.history.len() { + self.history_index = 0; + } + self.current = self.history.get(self.history_index).cloned().unwrap_or(String::new()); + } + + fn history_prev(&mut self) { + if self.history_index == 0 { + self.history_index = self.history.len(); + } else { + self.history_index -= 1; + } + self.current = self.history.get(self.history_index).cloned().unwrap_or(String::new()); + } + + fn check_history(&mut self) { + const MAX_HISTORY: usize = 50; + + while self.history.len() > MAX_HISTORY { + self.history.pop_front(); + } + + self.history_index = self.history.len(); + } +} + +fn render_titled(frame: &mut Frame, widget: W, title: &str, area: Rect) { + let block = Block::bordered().title(title); + frame.render_widget(widget, block.inner(area)); + frame.render_widget(block, area); +} + +impl DrawData { + fn draw(&mut self, frame: &mut Frame) { + let layout = DebuggerLayout::new(frame.area()); + + let (code, line) = code_text(&self.code, self.highlight); + let p = Paragraph::new(code).scroll((line.saturating_sub(4) as u16, 0)); + render_titled(frame, p, "code", layout.code); + + render_titled(frame, Text::raw(&self.result), "Result", layout.result); + + render_titled(frame, Text::from_iter(self.watchpoints.iter().map(|s| &**s)), "Watchpoints", layout.watchpoints); + + render_titled(frame, Text::raw(&self.message), "Message", layout.message); + + render_titled(frame, &self.prompt, "Command:", layout.user_input); + } +} + +impl Ui for RatatuiUi { + fn display_user_data(&mut self, data: &UserData<'_>) { + self.data.code = data.code.to_string(); + self.data.highlight = data.highlight; + self.data.result = data.result.map(|s| s.to_string()).unwrap_or_default(); + self.data.watchpoints.clear(); + self.data.watchpoints.extend(data.watchpoints.iter().enumerate().map(|(i, s)| format!("{i:>2} {s}"))); + self.data.message = data.message.to_string(); + } + + fn receive_user_input(&mut self) -> String { + loop { + self.terminal.draw(|frame| self.data.draw(frame)).expect("failed to draw frame"); + if let Event::Key(key_event) = event::read().expect("event") { + let control = key_event.modifiers.contains(KeyModifiers::CONTROL); + if let Some(string) = self.data.prompt.handle_key(key_event.code, control) { + return string; + } + } + } + } +} diff --git a/errors/src/errors/import/import_errors.rs b/interpreter/src/test/mod.rs similarity index 72% rename from errors/src/errors/import/import_errors.rs rename to interpreter/src/test/mod.rs index e53fa6dcdd..2e828f5ae6 100644 --- a/errors/src/errors/import/import_errors.rs +++ b/interpreter/src/test/mod.rs @@ -14,16 +14,12 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::create_errors; +use serial_test::serial; -use std::{ - error::Error as ErrorArg, - fmt::{Debug, Display}, -}; +mod runner; -create_errors!( - /// ImportError enum that represents all the errors for the `leo-import` crate. - ImportError, - exit_code_mask: 4000i32, - error_code_prefix: "IMP", -); +#[test] +#[serial] +pub fn tests() { + leo_test_framework::run_tests(&runner::InterpreterRunner, "interpreter"); +} diff --git a/interpreter/src/test/runner.rs b/interpreter/src/test/runner.rs new file mode 100644 index 0000000000..98fde3ebfb --- /dev/null +++ b/interpreter/src/test/runner.rs @@ -0,0 +1,98 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::*; + +use snarkvm::prelude::{Address, PrivateKey}; + +use leo_span::symbol::create_session_if_not_set_then; +use leo_test_framework::runner::{Namespace, ParseType, Runner, Test}; + +use std::{fs, path::PathBuf, str::FromStr as _}; + +pub struct LeoNamespace; + +impl Namespace for LeoNamespace { + fn parse_type(&self) -> ParseType { + ParseType::Whole + } + + fn run_test(&self, test: Test) -> Result { + create_session_if_not_set_then(|_| run_leo_test(test).map(|v| toml::Value::String(format!("{v}")))) + } +} + +pub struct AleoNamespace; + +impl Namespace for AleoNamespace { + fn parse_type(&self) -> ParseType { + ParseType::Whole + } + + fn run_test(&self, test: Test) -> Result { + create_session_if_not_set_then(|_| run_aleo_test(test).map(|v| toml::Value::String(format!("{v}")))) + } +} + +pub struct InterpreterRunner; + +impl Runner for InterpreterRunner { + fn resolve_namespace(&self, name: &str) -> Option> { + match name { + "Leo" => Some(Box::new(LeoNamespace)), + "Aleo" => Some(Box::new(AleoNamespace)), + _ => None, + } + } +} + +fn run_leo_test(test: Test) -> Result { + let tempdir = tempfile::tempdir().map_err(|e| format!("{e}"))?; + let mut filename = PathBuf::from(tempdir.path()); + filename.push("main.leo"); + fs::write(&filename, &test.content).map_err(|e| format!("{e}"))?; + + let private_key: PrivateKey = + PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY).expect("should be able to parse private key"); + let address = Address::try_from(&private_key).expect("should be able to create address"); + let empty: [&PathBuf; 0] = []; + let mut interpreter = Interpreter::new([filename].iter(), empty, address, 0).map_err(|e| format!("{e}"))?; + let v = interpreter.action(InterpreterAction::LeoInterpretOver("test.aleo/main()".into())); + println!("got {v:?}"); + match v { + Err(e) => Err(format!("{e}")), + Ok(None) => Err("no value received".to_string()), + Ok(Some(v)) => Ok(v), + } +} + +fn run_aleo_test(test: Test) -> Result { + let tempdir = tempfile::tempdir().map_err(|e| format!("{e}"))?; + let mut filename = PathBuf::from(tempdir.path()); + filename.push("main.aleo"); + fs::write(&filename, &test.content).map_err(|e| format!("{e}"))?; + + let private_key: PrivateKey = + PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY).expect("should be able to parse private key"); + let address = Address::try_from(&private_key).expect("should be able to create address"); + let empty: [&PathBuf; 0] = []; + let mut interpreter = Interpreter::new(empty, [filename].iter(), address, 0).map_err(|e| format!("{e}"))?; + match interpreter.action(InterpreterAction::LeoInterpretOver("test.aleo/main()".into())) { + Err(e) => Err(format!("{e}")), + Ok(None) => Err("no value received".to_string()), + Ok(Some(v)) => Ok(v), + } +} diff --git a/interpreter/src/ui.rs b/interpreter/src/ui.rs new file mode 100644 index 0000000000..abbc31e58b --- /dev/null +++ b/interpreter/src/ui.rs @@ -0,0 +1,30 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +pub struct UserData<'a> { + pub code: &'a str, + pub highlight: Option<(usize, usize)>, + pub message: &'a str, + pub futures: &'a [String], + pub watchpoints: &'a [String], + pub result: Option<&'a str>, +} + +pub trait Ui { + fn display_user_data(&mut self, data: &UserData<'_>); + + fn receive_user_input(&mut self) -> String; +} diff --git a/interpreter/src/util.rs b/interpreter/src/util.rs new file mode 100644 index 0000000000..e68bc5cdaa --- /dev/null +++ b/interpreter/src/util.rs @@ -0,0 +1,75 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use leo_errors::{InterpreterHalt, Result}; +use leo_span::{Span, Symbol}; + +use snarkvm::prelude::{Identifier, TestnetV0}; + +#[macro_export] +macro_rules! tc_fail { + () => { + panic!("type checker failure") + }; +} + +#[macro_export] +macro_rules! halt_no_span { + ($($x:tt)*) => { + return Err(InterpreterHalt::new(format!($($x)*)).into()) + } +} + +#[macro_export] +macro_rules! halt { + ($span: expr) => { + return Err(InterpreterHalt::new_spanned(String::new(), $span).into()) + + }; + + ($span: expr, $($x:tt)*) => { + return Err(InterpreterHalt::new_spanned(format!($($x)*), $span).into()) + }; +} + +pub trait ExpectTc { + type T; + fn expect_tc(self, span: Span) -> Result; +} + +impl ExpectTc for Option { + type T = T; + + fn expect_tc(self, span: Span) -> Result { + match self { + Some(t) => Ok(t), + None => Err(InterpreterHalt::new_spanned("type failure".into(), span).into()), + } + } +} + +impl ExpectTc for Result { + type T = T; + + fn expect_tc(self, span: Span) -> Result { + self.map_err(|_e| InterpreterHalt::new_spanned("type failure".into(), span).into()) + } +} + +pub fn snarkvm_identifier_to_symbol(id: &Identifier) -> Symbol { + let s = id.to_string(); + Symbol::intern(&s) +} diff --git a/interpreter/src/value.rs b/interpreter/src/value.rs new file mode 100644 index 0000000000..954de329e7 --- /dev/null +++ b/interpreter/src/value.rs @@ -0,0 +1,488 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; + +use leo_ast::{IntegerType, Type}; +use leo_span::Symbol; + +use snarkvm::prelude::{ + Address as SvmAddressParam, + Boolean as SvmBooleanParam, + Cast, + Field as SvmFieldParam, + FromBits as _, + Group as SvmGroupParam, + Identifier as SvmIdentifierParam, + Literal, + Plaintext, + Scalar as SvmScalarParam, + // Signature as SvmSignatureParam, + TestnetV0, + ToBits, + integers::Integer as SvmIntegerParam, +}; + +use indexmap::IndexMap; +use std::{ + fmt, + hash::{Hash, Hasher}, + str::FromStr as _, +}; + +pub type SvmAddress = SvmAddressParam; +type SvmBoolean = SvmBooleanParam; +type SvmField = SvmFieldParam; +type SvmGroup = SvmGroupParam; +type SvmIdentifier = SvmIdentifierParam; +type SvmInteger = SvmIntegerParam; +type SvmScalar = SvmScalarParam; +// type SvmSignature = SvmSignatureParam; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StructContents { + pub name: Symbol, + pub contents: IndexMap, +} + +impl Hash for StructContents { + fn hash(&self, state: &mut H) { + self.name.hash(state); + for (_symbol, value) in self.contents.iter() { + value.hash(state); + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AsyncExecution { + pub function: GlobalId, + pub arguments: Vec, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct Future(pub Vec); + +impl fmt::Display for Future { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Future")?; + if !self.0.is_empty() { + write!(f, " with calls to ")?; + let mut names = self.0.iter().map(|async_ex| async_ex.function).peekable(); + while let Some(name) = names.next() { + write!(f, "{name}")?; + if names.peek().is_some() { + write!(f, ", ")?; + } + } + } + Ok(()) + } +} + +/// A Leo value of any type. +/// +/// Mappings and functions aren't considered values. +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub enum Value { + #[default] + Unit, + Bool(bool), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), + I8(i8), + I16(i16), + I32(i32), + I64(i64), + I128(i128), + Group(SvmGroup), + Field(SvmField), + Scalar(SvmScalar), + Array(Vec), + // Signature(Box), + Tuple(Vec), + Address(SvmAddress), + Future(Future), + Struct(StructContents), + // String(()), +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Value::*; + match self { + Unit => write!(f, "()"), + + Bool(x) => write!(f, "{x}"), + U8(x) => write!(f, "{x}u8"), + U16(x) => write!(f, "{x}u16"), + U32(x) => write!(f, "{x}u32"), + U64(x) => write!(f, "{x}u64"), + U128(x) => write!(f, "{x}u128"), + I8(x) => write!(f, "{x}i8"), + I16(x) => write!(f, "{x}i16"), + I32(x) => write!(f, "{x}i32"), + I64(x) => write!(f, "{x}i64"), + I128(x) => write!(f, "{x}i128"), + Group(x) => write!(f, "{x}"), + Field(x) => write!(f, "{x}"), + Scalar(x) => write!(f, "{x}"), + Array(x) => { + write!(f, "[")?; + let mut iter = x.iter().peekable(); + while let Some(value) = iter.next() { + write!(f, "{value}")?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, "]") + } + Struct(StructContents { name, contents }) => { + write!(f, "{name} {{")?; + let mut iter = contents.iter().peekable(); + while let Some((member_name, value)) = iter.next() { + write!(f, "{member_name}: {value}")?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, "}}") + } + Tuple(x) => { + write!(f, "(")?; + let mut iter = x.iter().peekable(); + while let Some(value) = iter.next() { + write!(f, "{value}")?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, ")") + } + Address(x) => write!(f, "{x}"), + Future(future) => write!(f, "{future}"), + // Signature(x) => write!(f, "{x}"), + // String(_) => todo!(), + } + } +} + +impl ToBits for Value { + fn write_bits_le(&self, vec: &mut Vec) { + use Value::*; + + let plaintext: Plaintext = match self { + Bool(x) => Literal::Boolean(SvmBoolean::new(*x)).into(), + U8(x) => Literal::U8(SvmInteger::new(*x)).into(), + U16(x) => Literal::U16(SvmInteger::new(*x)).into(), + U32(x) => Literal::U32(SvmInteger::new(*x)).into(), + U64(x) => Literal::U64(SvmInteger::new(*x)).into(), + U128(x) => Literal::U128(SvmInteger::new(*x)).into(), + I8(x) => Literal::I8(SvmInteger::new(*x)).into(), + I16(x) => Literal::I16(SvmInteger::new(*x)).into(), + I32(x) => Literal::I32(SvmInteger::new(*x)).into(), + I64(x) => Literal::I64(SvmInteger::new(*x)).into(), + I128(x) => Literal::I128(SvmInteger::new(*x)).into(), + Group(x) => Literal::Group(*x).into(), + Field(x) => Literal::Field(*x).into(), + Scalar(x) => Literal::Scalar(*x).into(), + Address(x) => Literal::Address(*x).into(), + Struct(StructContents { name: _, contents }) => { + (contents.len() as u8).write_bits_le(vec); + for (name, value) in contents.iter() { + let name_s = name.to_string(); + let identifier = SvmIdentifier::from_str(&name_s).expect("identifier should parse"); + identifier.size_in_bits().write_bits_le(vec); + identifier.write_bits_le(vec); + let value_bits = value.to_bits_le(); + (value_bits.len() as u16).write_bits_le(vec); + vec.extend_from_slice(&value_bits); + } + return; + } + + Array(array) => { + for element in array.iter() { + let bits = element.to_bits_le(); + (bits.len() as u16).write_bits_le(vec); + vec.extend_from_slice(&bits); + } + return; + } + _ => tc_fail!(), + }; + + plaintext.write_bits_le(vec); + } + + fn write_bits_be(&self, _vec: &mut Vec) { + todo!() + } +} + +impl Value { + pub fn to_fields(&self) -> Vec { + let mut bits = self.to_bits_le(); + bits.push(true); + bits.chunks(SvmField::SIZE_IN_DATA_BITS) + .map(|bits| SvmField::from_bits_le(bits).expect("conversion should work")) + .collect() + } + + pub fn gte(&self, rhs: &Self) -> Result { + rhs.gt(self).map(|v| !v) + } + + pub fn lte(&self, rhs: &Self) -> Result { + rhs.lt(self).map(|v| !v) + } + + pub fn lt(&self, rhs: &Self) -> Result { + use Value::*; + Ok(match (self, rhs) { + (U8(x), U8(y)) => x < y, + (U16(x), U16(y)) => x < y, + (U32(x), U32(y)) => x < y, + (U64(x), U64(y)) => x < y, + (U128(x), U128(y)) => x < y, + (I8(x), I8(y)) => x < y, + (I16(x), I16(y)) => x < y, + (I32(x), I32(y)) => x < y, + (I64(x), I64(y)) => x < y, + (I128(x), I128(y)) => x < y, + (Field(x), Field(y)) => x < y, + (a, b) => halt_no_span!("Type failure: {a} < {b}"), + }) + } + + pub fn gt(&self, rhs: &Self) -> Result { + use Value::*; + Ok(match (self, rhs) { + (U8(x), U8(y)) => x > y, + (U16(x), U16(y)) => x > y, + (U32(x), U32(y)) => x > y, + (U64(x), U64(y)) => x > y, + (U128(x), U128(y)) => x > y, + (I8(x), I8(y)) => x > y, + (I16(x), I16(y)) => x > y, + (I32(x), I32(y)) => x > y, + (I64(x), I64(y)) => x > y, + (I128(x), I128(y)) => x > y, + (Field(x), Field(y)) => x > y, + (a, b) => halt_no_span!("Type failure: {a} > {b}"), + }) + } + + pub fn neq(&self, rhs: &Self) -> Result { + self.eq(rhs).map(|v| !v) + } + + /// Are the values equal, according to SnarkVM? + /// + /// We use this rather than the Eq trait so we can + /// fail when comparing values of different types, + /// rather than just returning false. + pub fn eq(&self, rhs: &Self) -> Result { + use Value::*; + Ok(match (self, rhs) { + (Unit, Unit) => true, + (Bool(x), Bool(y)) => x == y, + (U8(x), U8(y)) => x == y, + (U16(x), U16(y)) => x == y, + (U32(x), U32(y)) => x == y, + (U64(x), U64(y)) => x == y, + (U128(x), U128(y)) => x == y, + (I8(x), I8(y)) => x == y, + (I16(x), I16(y)) => x == y, + (I32(x), I32(y)) => x == y, + (I64(x), I64(y)) => x == y, + (I128(x), I128(y)) => x == y, + (Field(x), Field(y)) => x == y, + (Group(x), Group(y)) => x == y, + (Array(x), Array(y)) => { + if x.len() != y.len() { + return Ok(false); + } + for (lhs, rhs) in x.iter().zip(y.iter()) { + match lhs.eq(rhs) { + Ok(true) => {} + Ok(false) => return Ok(false), + Err(e) => return Err(e), + } + } + true + } + (a, b) => halt_no_span!("Type failure: {a} == {b}"), + }) + } + + pub fn inc_wrapping(&self) -> Self { + match self { + Value::U8(x) => Value::U8(x.wrapping_add(1)), + Value::U16(x) => Value::U16(x.wrapping_add(1)), + Value::U32(x) => Value::U32(x.wrapping_add(1)), + Value::U64(x) => Value::U64(x.wrapping_add(1)), + Value::U128(x) => Value::U128(x.wrapping_add(1)), + Value::I8(x) => Value::I8(x.wrapping_add(1)), + Value::I16(x) => Value::I16(x.wrapping_add(1)), + Value::I32(x) => Value::I32(x.wrapping_add(1)), + Value::I64(x) => Value::I64(x.wrapping_add(1)), + Value::I128(x) => Value::I128(x.wrapping_add(1)), + _ => tc_fail!(), + } + } + + /// Return the group generator. + pub fn generator() -> Self { + Value::Group(SvmGroup::generator()) + } + + /// Doesn't correspond to Aleo's shl, because it + /// does not fail when set bits are shifted out. + pub fn simple_shl(&self, shift: u32) -> Self { + match self { + Value::U8(x) => Value::U8(x << shift), + Value::U16(x) => Value::U16(x << shift), + Value::U32(x) => Value::U32(x << shift), + Value::U64(x) => Value::U64(x << shift), + Value::U128(x) => Value::U128(x << shift), + Value::I8(x) => Value::I8(x << shift), + Value::I16(x) => Value::I16(x << shift), + Value::I32(x) => Value::I32(x << shift), + Value::I64(x) => Value::I64(x << shift), + Value::I128(x) => Value::I128(x << shift), + _ => tc_fail!(), + } + } + + pub fn simple_shr(&self, shift: u32) -> Self { + match self { + Value::U8(x) => Value::U8(x >> shift), + Value::U16(x) => Value::U16(x >> shift), + Value::U32(x) => Value::U32(x >> shift), + Value::U64(x) => Value::U64(x >> shift), + Value::U128(x) => Value::U128(x >> shift), + Value::I8(x) => Value::I8(x >> shift), + Value::I16(x) => Value::I16(x >> shift), + Value::I32(x) => Value::I32(x >> shift), + Value::I64(x) => Value::I64(x >> shift), + Value::I128(x) => Value::I128(x >> shift), + _ => tc_fail!(), + } + } + + /// Convert to the given type if possible under Aleo casting rules. + pub fn cast(self, cast_type: &Type) -> Option { + match self { + Value::Bool(b) => really_cast(SvmBoolean::new(b), cast_type), + Value::U8(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U16(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U32(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U64(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U128(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I8(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I16(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I32(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I64(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I128(x) => really_cast(SvmInteger::new(x), cast_type), + Value::Group(g) => really_cast(g.to_x_coordinate(), cast_type), + Value::Field(f) => really_cast(f, cast_type), + Value::Scalar(s) => really_cast(s, cast_type), + Value::Address(a) => really_cast(a.to_group().to_x_coordinate(), cast_type), + _ => None, + } + } +} + +fn really_cast(c: C, cast_type: &Type) -> Option +where + C: Cast + + Cast + + Cast + + Cast + + Cast + + Cast + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast>, +{ + use Type::*; + + let value = match cast_type { + Address => Value::Address(c.cast().ok()?), + Boolean => Value::Bool({ + let b: SvmBoolean = c.cast().ok()?; + *b + }), + Field => Value::Field(c.cast().ok()?), + Group => Value::Group(c.cast().ok()?), + Integer(IntegerType::U8) => Value::U8({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U16) => Value::U16({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U32) => Value::U32({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U64) => Value::U64({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U128) => Value::U128({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I8) => Value::I8({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I16) => Value::I16({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I32) => Value::I32({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I64) => Value::I64({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I128) => Value::I128({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Scalar => Value::Scalar(c.cast().ok()?), + + _ => tc_fail!(), + }; + Some(value) +} diff --git a/leo/cli/cli.rs b/leo/cli/cli.rs index 3cf4fff3f6..6f6b10596b 100644 --- a/leo/cli/cli.rs +++ b/leo/cli/cli.rs @@ -82,6 +82,11 @@ enum Commands { #[clap(flatten)] command: LeoBuild, }, + #[clap(about = "Debug the current package via the interpreter")] + Debug { + #[clap(flatten)] + command: LeoDebug, + }, #[clap(about = "Add a new on-chain or local dependency to the current package.")] Add { #[clap(flatten)] @@ -97,6 +102,11 @@ enum Commands { #[clap(flatten)] command: LeoClean, }, + #[clap(about = "Execute native, interpreted, and end-to-end tests.")] + Test { + #[clap(flatten)] + command: LeoTest, + }, #[clap(about = "Update the Leo CLI")] Update { #[clap(flatten)] @@ -124,6 +134,11 @@ pub fn run_with_args(cli: CLI) -> Result<()> { })?; } + // Check for updates. If not forced, it checks once per day. + if let Ok(true) = updater::Updater::check_for_updates(false) { + let _ = updater::Updater::print_cli(); + } + // Get custom root folder and create context for it. // If not specified, default context will be created in cwd. let context = handle_error(Context::new(cli.path, cli.home, false)); @@ -133,6 +148,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> { Commands::Account { command } => command.try_execute(context), Commands::New { command } => command.try_execute(context), Commands::Build { command } => command.try_execute(context), + Commands::Debug { command } => command.try_execute(context), Commands::Query { command } => command.try_execute(context), Commands::Clean { command } => command.try_execute(context), Commands::Deploy { command } => command.try_execute(context), @@ -140,9 +156,11 @@ pub fn run_with_args(cli: CLI) -> Result<()> { Commands::Run { command } => command.try_execute(context), Commands::Execute { command } => command.try_execute(context), Commands::Remove { command } => command.try_execute(context), + Commands::Test { command } => command.try_execute(context), Commands::Update { command } => command.try_execute(context), } } + #[cfg(test)] mod tests { use crate::cli::{ @@ -404,8 +422,8 @@ function external_nested_function: command: LeoAdd { name: "nested_example_layer_0".to_string(), local: None, + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(project_directory.clone()), @@ -516,8 +534,8 @@ program child.aleo { command: LeoAdd { name: "parent".to_string(), local: Some(parent_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(grandparent_directory.clone()), @@ -531,8 +549,8 @@ program child.aleo { command: LeoAdd { name: "child".to_string(), local: Some(child_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(grandparent_directory.clone()), @@ -546,8 +564,8 @@ program child.aleo { command: LeoAdd { name: "child".to_string(), local: Some(child_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(parent_directory.clone()), @@ -688,8 +706,8 @@ program outer.aleo { command: LeoAdd { name: "inner_1".to_string(), local: Some(inner_1_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(outer_directory.clone()), @@ -703,8 +721,8 @@ program outer.aleo { command: LeoAdd { name: "inner_2".to_string(), local: Some(inner_2_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(outer_directory.clone()), @@ -875,8 +893,8 @@ program outer_2.aleo { command: LeoAdd { name: "inner_1".to_string(), local: Some(inner_1_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(outer_directory.clone()), @@ -890,8 +908,8 @@ program outer_2.aleo { command: LeoAdd { name: "inner_2".to_string(), local: Some(inner_2_directory.clone()), + dev: false, network: NETWORK.to_string(), - clear: false, }, }, path: Some(outer_directory.clone()), diff --git a/leo/cli/commands/add.rs b/leo/cli/commands/add.rs index f37604fee0..4c95e66637 100755 --- a/leo/cli/commands/add.rs +++ b/leo/cli/commands/add.rs @@ -28,11 +28,11 @@ pub struct LeoAdd { #[clap(short = 'l', long, help = "Path to local dependency")] pub(crate) local: Option, + #[clap(short = 'd', long, help = "Whether the dependency is a dev dependency", default_value = "false")] + pub(crate) dev: bool, + #[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet")] pub(crate) network: String, - - #[clap(short = 'c', long, help = "Clear all previous dependencies.", default_value = "false")] - pub(crate) clear: bool, } impl Command for LeoAdd { @@ -66,65 +66,64 @@ impl Command for LeoAdd { name => return Err(PackageError::invalid_file_name_dependency(name).into()), }; - // Add dependency section to manifest if it doesn't exist. - let mut dependencies = match (self.clear, manifest.dependencies()) { - (false, Some(ref dependencies)) => dependencies - .iter() - .filter_map(|dependency| { - // Overwrite old dependencies of the same name. - if dependency.name() == &name { - let msg = match (dependency.path(), dependency.network()) { - (Some(local_path), _) => { - format!("local dependency at path `{}`", local_path.to_str().unwrap().replace('\"', "")) - } - (_, Some(network)) => { - format!("network dependency from `{}`", network) - } - _ => "git dependency".to_string(), - }; - tracing::warn!("⚠️ Program `{name}` already exists as a {msg}. Overwriting."); - None - } else if self.local.is_some() && &self.local == dependency.path() { - // Overwrite old dependencies at the same local path. - tracing::warn!( - "⚠️ Path `{}` already exists as the location for local dependency `{}`. Overwriting.", - self.local.clone().unwrap().to_str().unwrap().replace('\"', ""), - dependency.name() - ); - None - } else { - Some(dependency.clone()) - } - }) - .collect(), - _ => Vec::new(), - }; + // Destructure the manifest. + let Manifest { program, version, description, license, dependencies, dev_dependencies } = manifest; - // Add new dependency to the manifest. - dependencies.push(match self.local { - Some(local_path) => { - tracing::info!( - "✅ Added local dependency to program `{name}` at path `{}`.", - local_path.to_str().unwrap().replace('\"', "") - ); - Dependency::new(name, Location::Local, None, Some(local_path)) - } - None => { - tracing::info!("✅ Added network dependency to program `{name}` from network `{}`.", self.network); - Dependency::new(name, Location::Network, Some(NetworkName::try_from(self.network.as_str())?), None) - } - }); + // Add the dependency to the appropriate section. + let (dependencies, dev_dependencies) = if self.dev { + (dependencies, add_dependency(dev_dependencies, name, self.local, self.network)?) + } else { + (add_dependency(dependencies, name, self.local, self.network)?, dev_dependencies) + }; // Update the manifest file. let new_manifest = Manifest::new( - manifest.program(), - manifest.version(), - manifest.description(), - manifest.license(), - Some(dependencies), + program.as_str(), + version.as_str(), + description.as_str(), + license.as_str(), + dependencies, + dev_dependencies, ); new_manifest.write_to_dir(&path)?; Ok(()) } } + +// A helper function to add a dependency to either the `dependencies` or `dev_dependencies` section of the manifest. +fn add_dependency( + dependencies: Option>, + name: String, + location: Option, + network: String, +) -> Result>> { + // Check if the dependency already exists, returning the original list if it does. + let mut dependencies = if let Some(dependencies) = dependencies { + if dependencies.iter().any(|dependency| dependency.name() == &name) { + tracing::warn!( + "⚠️ Program `{name}` already exists as a dependency. If you wish to update it, explicitly remove it using `leo remove` and add it again." + ); + return Ok(Some(dependencies)); + } + dependencies + } else { + Vec::new() + }; + // Add the new dependency to the list. + dependencies.push(match location { + Some(local_path) => { + tracing::info!( + "✅ Added local dependency to program `{name}` at path `{}`.", + local_path.to_str().unwrap().replace('\"', "") + ); + Dependency::new(name, Location::Local, None, Some(local_path)) + } + None => { + tracing::info!("✅ Added network dependency to program `{name}` from network `{network}`."); + Dependency::new(name, Location::Network, Some(NetworkName::try_from(network.as_str())?), None) + } + }); + // Return the updated list of dependencies. + Ok(Some(dependencies)) +} diff --git a/leo/cli/commands/build.rs b/leo/cli/commands/build.rs index 588ca33a63..cb856feb61 100644 --- a/leo/cli/commands/build.rs +++ b/leo/cli/commands/build.rs @@ -29,6 +29,8 @@ use snarkvm::{ }; use indexmap::IndexMap; +use leo_package::tst::TestDirectory; +use leo_span::source_map::FileName; use snarkvm::prelude::CanaryV0; use std::{ io::Write, @@ -43,6 +45,7 @@ impl From for CompilerOptions { dce_enabled: options.enable_dce, conditional_block_max_depth: options.conditional_block_max_depth, disable_conditional_branch_type_checking: options.disable_conditional_branch_type_checking, + build_tests: options.build_tests, }, output: OutputOptions { symbol_table_spans_enabled: options.enable_symbol_table_spans, @@ -141,11 +144,18 @@ fn handle_build(command: &LeoBuild, context: Context) -> Result<(command: &LeoBuild, context: Context) -> Result<::try_from(format!("{}.aleo", dependency)) - .map_err(|_| UtilError::snarkvm_error_building_program_id(Default::default()))?, - &local_outputs_directory, - &local_build_directory, - &handler, - command.options.clone(), - stubs.clone(), - )?; - } + // Compile the sources. + compile_leo_files::( + dependency.to_string(), + local_source_files, + &local_outputs_directory, + &local_build_directory, + &handler, + command.options.clone(), + stubs.clone(), + )?; } // Writes `leo.lock` as well as caches objects (when target is an intermediate dependency) @@ -179,39 +186,46 @@ fn handle_build(command: &LeoBuild, context: Context) -> Result<::open(&build_directory).map_err(CliError::failed_to_execute_build)?; + let package = Package::::open(&build_directory).map_err(CliError::failed_to_execute_build)?; + // Add the main program as a stub. + main_stubs.insert(main_sym, leo_disassembler::disassemble(package.program())); + + // If the `build_tests` flag is set, compile the tests. + if command.options.build_tests { + println!("main_stubs: {:?}", main_stubs); + // Compile the tests. + compile_tests::(main_sym.to_string(), &package_path, &handler, command.options.clone(), main_stubs.clone())?; + } Ok(()) } -/// Compiles a Leo file in the `src/` directory. +/// Compiles Leo files in the `src/` directory. #[allow(clippy::too_many_arguments)] -fn compile_leo_file( - file_path: PathBuf, - program_id: &ProgramID, +fn compile_leo_files( + name: String, + local_source_files: Vec, outputs: &Path, build: &Path, handler: &Handler, options: BuildOptions, stubs: IndexMap, ) -> Result<()> { - // Construct program name from the program_id found in `package.json`. - let program_name = program_id.name().to_string(); + // Read the files and collect it into sources. + let mut sources = Vec::with_capacity(local_source_files.len()); + for file_path in local_source_files.iter() { + let file_content = std::fs::read_to_string(file_path.clone()).map_err(|e| { + CliError::general_cli_error(format!("Failed to read source file '{:?}': {e}", file_path.as_path())) + })?; // Read the file content. + sources.push((FileName::Real(file_path.clone()), file_content)); + } // Create the path to the Aleo file. let mut aleo_file_path = build.to_path_buf(); - aleo_file_path.push(format!("main.{}", program_id.network())); + aleo_file_path.push("main.aleo"); // Create a new instance of the Leo compiler. - let mut compiler = Compiler::::new( - program_name.clone(), - program_id.network().to_string(), - handler, - file_path.clone(), - outputs.to_path_buf(), - Some(options.into()), - stubs, - ); + let mut compiler = Compiler::::new(name.clone(), handler, sources, outputs.to_path_buf(), options.into(), stubs); // Compile the Leo program into Aleo instructions. let instructions = compiler.compile()?; @@ -222,6 +236,61 @@ fn compile_leo_file( .write_all(instructions.as_bytes()) .map_err(CliError::failed_to_load_instructions)?; - tracing::info!("✅ Compiled '{program_name}.aleo' into Aleo instructions"); + tracing::info!("✅ Compiled sources for '{name}'"); + Ok(()) +} + +/// Compiles test files in the `tests/` directory. +#[allow(clippy::too_many_arguments)] +fn compile_tests( + name: String, + package_path: &Path, + handler: &Handler, + options: BuildOptions, + stubs: IndexMap, +) -> Result<()> { + // Get the files in `/tests` directory. + let test_files = TestDirectory::files(package_path)?; + + // Construct the compiler. + let mut compiler = Compiler::::new( + "tests".to_string(), + handler, + vec![], + PathBuf::from("build/tests"), + options.into(), + stubs.clone(), + ); + + // Read and compile the test files individually. + for file_path in test_files { + // Read the test file. + let file_content = std::fs::read_to_string(&file_path).map_err(|e| { + CliError::general_cli_error(format!( + "Failed to read test file '{:?}': {e}", + file_path.clone().into_os_string() + )) + })?; + + // Reset the compiler with the test file content. + compiler.reset(vec![(FileName::Real(file_path.clone()), file_content)]); + + // Compile the test file. + let output = compiler.compile_tests()?; + + // Create a subdirectory for the test. + let build_dir = BuildDirectory::open(package_path)?; + let test_dir = build_dir.join("tests"); + std::fs::create_dir_all(&test_dir) + .map_err(|e| CliError::general_cli_error(format!("Failed to create `build/tests` directory: {e}")))?; + + // Write the outputs. + let test_file_name = file_path.file_name().unwrap().to_str().unwrap(); + let test_file_path = test_dir.join(test_file_name); + std::fs::write(&test_file_path, output).map_err(|e| { + CliError::general_cli_error(format!("Failed to write test file '{:?}': {e}", test_file_path)) + })?; + } + tracing::info!("✅ Compiled tests for '{name}'"); Ok(()) } diff --git a/leo/cli/commands/debug.rs b/leo/cli/commands/debug.rs new file mode 100644 index 0000000000..ed904d21e2 --- /dev/null +++ b/leo/cli/commands/debug.rs @@ -0,0 +1,154 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use std::{fs, path::PathBuf}; + +use snarkvm::prelude::{Network, ProgramID, TestnetV0}; + +#[cfg(not(feature = "only_testnet"))] +use snarkvm::prelude::{CanaryV0, MainnetV0}; + +use leo_errors::UtilError; +use leo_retriever::{Manifest, NetworkName, Retriever}; +use leo_span::Symbol; + +use super::*; + +/// Debugs an Aleo program through the interpreter. +#[derive(Parser, Debug)] +pub struct LeoDebug { + #[arg(long, help = "Use these source files instead of finding source files through the project structure.", num_args = 1..)] + pub(crate) paths: Vec, + + #[arg(long, help = "The block height, accessible via block.height.", default_value = "0")] + pub(crate) block_height: u32, + + #[arg(long, action, help = "Use the text user interface.")] + pub(crate) tui: bool, + + #[clap(flatten)] + pub(crate) compiler_options: BuildOptions, +} + +impl Command for LeoDebug { + type Input = ::Output; + type Output = (); + + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Leo") + } + + fn prelude(&self, context: Context) -> Result { + if self.paths.is_empty() { + (LeoBuild { options: self.compiler_options.clone() }).execute(context) + } else { + Ok(()) + } + } + + fn apply(self, context: Context, _: Self::Input) -> Result { + // Parse the network. + let network = NetworkName::try_from(context.get_network(&self.compiler_options.network)?)?; + match network { + NetworkName::TestnetV0 => handle_debug::(&self, context), + NetworkName::MainnetV0 => { + #[cfg(feature = "only_testnet")] + panic!("Mainnet chosen with only_testnet feature"); + #[cfg(not(feature = "only_testnet"))] + return handle_debug::(&self, context); + } + NetworkName::CanaryV0 => { + #[cfg(feature = "only_testnet")] + panic!("Canary chosen with only_testnet feature"); + #[cfg(not(feature = "only_testnet"))] + return handle_debug::(&self, context); + } + } + } +} + +fn handle_debug(command: &LeoDebug, context: Context) -> Result<()> { + if command.paths.is_empty() { + // Get the package path. + let package_path = context.dir()?; + let home_path = context.home()?; + + // Get the program id. + let manifest = Manifest::read_from_dir(&package_path)?; + let program_id = ProgramID::::from_str(manifest.program())?; + + // Get the private key. + let private_key = context.get_private_key(&None)?; + let address = Address::try_from(&private_key)?; + + // Retrieve all local dependencies in post order + let main_sym = Symbol::intern(&program_id.name().to_string()); + let mut retriever = Retriever::::new( + main_sym, + &package_path, + &home_path, + context.get_endpoint(&command.compiler_options.endpoint)?.to_string(), + ) + .map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?; + let mut local_dependencies = + retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?; + + // Push the main program at the end of the list. + local_dependencies.push(main_sym); + + let paths: Vec = local_dependencies + .into_iter() + .map(|dependency| { + let base_path = retriever.get_context(&dependency).full_path(); + base_path.join("src/main.leo") + }) + .collect(); + + let imports_directory = package_path.join("build/imports"); + + let aleo_paths: Vec = if let Ok(dir) = fs::read_dir(imports_directory) { + dir.flat_map(|maybe_filename| maybe_filename.ok()) + .filter(|entry| entry.file_type().ok().map(|filetype| filetype.is_file()).unwrap_or(false)) + .flat_map(|entry| { + let path = entry.path(); + if path.extension().map(|e| e == "aleo").unwrap_or(false) { Some(path) } else { None } + }) + .collect() + } else { + Vec::new() + }; + + leo_interpreter::interpret(&paths, &aleo_paths, address, command.block_height, command.tui) + } else { + let private_key: PrivateKey = PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY)?; + let address = Address::try_from(&private_key)?; + + let leo_paths: Vec = command + .paths + .iter() + .filter(|path_str| path_str.ends_with(".leo")) + .map(|path_str| path_str.into()) + .collect(); + let aleo_paths: Vec = command + .paths + .iter() + .filter(|path_str| !path_str.ends_with(".leo")) + .map(|path_str| path_str.into()) + .collect(); + + leo_interpreter::interpret(&leo_paths, &aleo_paths, address, command.block_height, command.tui) + } +} diff --git a/leo/cli/commands/deploy.rs b/leo/cli/commands/deploy.rs index 860a092e22..be7c3a442a 100644 --- a/leo/cli/commands/deploy.rs +++ b/leo/cli/commands/deploy.rs @@ -160,19 +160,24 @@ fn handle_deploy, N: Network>( // Initialize the VM. let vm = VM::from(store)?; - // Compute the minimum deployment cost. - let (mut total_cost, (storage_cost, synthesis_cost, namespace_cost)) = deployment_cost(&deployment)?; - - // Display the deployment cost breakdown using `credit` denomination. - total_cost += command.fee_options.priority_fee; - deploy_cost_breakdown( - name, - total_cost as f64 / 1_000_000.0, - storage_cost as f64 / 1_000_000.0, - synthesis_cost as f64 / 1_000_000.0, - namespace_cost as f64 / 1_000_000.0, - command.fee_options.priority_fee as f64 / 1_000_000.0, - )?; + let base_fee = match command.fee_options.base_fee { + Some(base_fee) => base_fee, + None => { + // Compute the minimum deployment cost. + let (base_fee, (storage_cost, synthesis_cost, namespace_cost)) = deployment_cost(&deployment)?; + + // Display the deployment cost breakdown using `credit` denomination. + deploy_cost_breakdown( + name, + base_fee as f64 / 1_000_000.0, + storage_cost as f64 / 1_000_000.0, + synthesis_cost as f64 / 1_000_000.0, + namespace_cost as f64 / 1_000_000.0, + command.fee_options.priority_fee as f64 / 1_000_000.0, + )?; + base_fee + } + }; // Initialize an RNG. let rng = &mut rand::thread_rng(); @@ -184,7 +189,7 @@ fn handle_deploy, N: Network>( let fee_authorization = vm.authorize_fee_private( &private_key, fee_record, - total_cost, + base_fee, command.fee_options.priority_fee, deployment_id, rng, @@ -193,10 +198,16 @@ fn handle_deploy, N: Network>( } None => { // Make sure the user has enough public balance to pay for the deployment. - check_balance(&private_key, endpoint, &network.to_string(), context.clone(), total_cost)?; + check_balance( + &private_key, + endpoint, + &network.to_string(), + &context, + base_fee + command.fee_options.priority_fee, + )?; let fee_authorization = vm.authorize_fee_public( &private_key, - total_cost, + base_fee, command.fee_options.priority_fee, deployment_id, rng, @@ -247,13 +258,13 @@ fn handle_deploy, N: Network>( // A helper function to display a cost breakdown of the deployment. fn deploy_cost_breakdown( name: &String, - total_cost: f64, + base_fee: f64, storage_cost: f64, synthesis_cost: f64, namespace_cost: f64, priority_fee: f64, ) -> Result<()> { - println!("\nBase deployment cost for '{}' is {} credits.\n", name.bold(), total_cost); + println!("\nBase deployment cost for '{}' is {} credits.\n", name.bold(), base_fee); // Display the cost breakdown in a table. let data = [ [name, "Cost (credits)"], @@ -261,7 +272,7 @@ fn deploy_cost_breakdown( ["Program Synthesis", &format!("{:.6}", synthesis_cost)], ["Namespace", &format!("{:.6}", namespace_cost)], ["Priority Fee", &format!("{:.6}", priority_fee)], - ["Total", &format!("{:.6}", total_cost)], + ["Total", &format!("{:.6}", base_fee + priority_fee)], ]; let mut out = Vec::new(); text_tables::render(&mut out, data).map_err(CliError::table_render_failed)?; diff --git a/leo/cli/commands/execute.rs b/leo/cli/commands/execute.rs index e6b1356549..bc9636e867 100644 --- a/leo/cli/commands/execute.rs +++ b/leo/cli/commands/execute.rs @@ -31,7 +31,6 @@ use snarkvm::circuit::{AleoCanaryV0, AleoV0}; use snarkvm::{ circuit::{Aleo, AleoTestnetV0}, cli::LOCALE, - ledger::Transaction::Execute as ExecuteTransaction, package::Package as SnarkVMPackage, prelude::{ Identifier, @@ -41,7 +40,8 @@ use snarkvm::{ ProgramID, VM, Value, - execution_cost, + execution_cost_v1, + execution_cost_v2, query::Query as SnarkVMQuery, store::{ ConsensusStore, @@ -193,46 +193,111 @@ fn handle_execute( let program_id = &ProgramID::::from_str(&format!("{program_name}.aleo"))?; load_program_from_network(context.clone(), &mut vm.process().write(), program_id, network, endpoint)?; + // Compute the authorization. + let authorization = vm.authorize(&private_key, program_id, &command.name, inputs, rng)?; + // Determine if a fee is required. + let is_fee_required = !authorization.is_split(); + // Determine if a priority fee is declared. + let is_priority_fee_declared = command.fee_options.priority_fee > 0; + // Compute the execution. + let execution = match vm.execute_authorization(authorization, None, Some(query.clone()), rng)? { + Transaction::Execute(_, execution, _) => execution, + _ => unreachable!("VM::execute_authorization should return a Transaction::Execute"), + }; + let fee_record = if let Some(record) = command.fee_options.record { Some(parse_record(&private_key, &record)?) } else { None }; - // Create a new transaction. - let transaction = vm.execute( - &private_key, - (program_id, command.name.clone()), - inputs.iter(), - fee_record.clone(), - command.fee_options.priority_fee, - Some(query), - rng, - )?; - // Check the transaction cost. - let (mut total_cost, (storage_cost, finalize_cost)) = if let ExecuteTransaction(_, execution, _) = &transaction - { - execution_cost(&vm.process().read(), execution)? - } else { - panic!("All transactions should be of type Execute.") + let base_fee = match command.fee_options.base_fee { + Some(base_fee) => base_fee, + None => { + let (base_fee, (storage_cost, finalize_cost)) = + // Attempt to get the height of the latest block to determine which version of the execution cost to use. + if let Ok(height) = get_latest_block_height(endpoint, &network.to_string(), &context) { + if height < A::Network::CONSENSUS_V2_HEIGHT { + execution_cost_v1(&vm.process().read(), &execution)? + } else { + execution_cost_v2(&vm.process().read(), &execution)? + } + } + // Otherwise, default to the one provided in `fee_options`. + else { + // Get the consensus version from the command. + let version = match command.fee_options.consensus_version { + Some(1) => 1, + None | Some(2) => 2, + Some(version) => { + panic!("Invalid consensus version: {version}. Please specify a valid version.") + } + }; + // Print a warning message. + println!("Failed to get the latest block height. Defaulting to V{version}.",); + // Use the provided version. + match version { + 1 => execution_cost_v1(&vm.process().read(), &execution)?, + 2 => execution_cost_v2(&vm.process().read(), &execution)?, + _ => unreachable!(), + } + }; + + // Print the cost breakdown. + execution_cost_breakdown( + &program_name, + base_fee as f64 / 1_000_000.0, + storage_cost as f64 / 1_000_000.0, + finalize_cost as f64 / 1_000_000.0, + command.fee_options.priority_fee as f64 / 1_000_000.0, + )?; + base_fee + } }; - // Print the cost breakdown. - total_cost += command.fee_options.priority_fee; - execution_cost_breakdown( - &program_name, - total_cost as f64 / 1_000_000.0, - storage_cost as f64 / 1_000_000.0, - finalize_cost as f64 / 1_000_000.0, - command.fee_options.priority_fee as f64 / 1_000_000.0, - )?; - // Check if the public balance is sufficient. if fee_record.is_none() { - check_balance::(&private_key, endpoint, &network.to_string(), context, total_cost)?; + check_balance::( + &private_key, + endpoint, + &network.to_string(), + &context, + base_fee + command.fee_options.priority_fee, + )?; } + // Compute the fee. + let fee = match is_fee_required || is_priority_fee_declared { + true => { + // Compute the execution ID. + let execution_id = execution.to_execution_id()?; + // Authorize the fee. + let authorization = match fee_record { + Some(record) => vm.authorize_fee_private( + &private_key, + record, + base_fee, + command.fee_options.priority_fee, + execution_id, + rng, + )?, + None => vm.authorize_fee_public( + &private_key, + base_fee, + command.fee_options.priority_fee, + execution_id, + rng, + )?, + }; + // Execute the fee. + Some(vm.execute_fee_authorization(authorization, Some(query), rng)?) + } + false => None, + }; + // Return the execute transaction. + let transaction = Transaction::from_execution(execution, fee)?; + // Broadcast the execution transaction. if !command.fee_options.dry_run { if !command.fee_options.yes { @@ -396,19 +461,19 @@ fn load_program_from_network( // A helper function to display a cost breakdown of the execution. fn execution_cost_breakdown( name: &String, - total_cost: f64, + base_fee: f64, storage_cost: f64, finalize_cost: f64, priority_fee: f64, ) -> Result<()> { - println!("\nBase execution cost for '{}' is {} credits.\n", name.bold(), total_cost); + println!("\nBase execution cost for '{}' is {} credits.\n", name.bold(), base_fee); // Display the cost breakdown in a table. let data = [ [name, "Cost (credits)"], ["Transaction Storage", &format!("{:.6}", storage_cost)], ["On-chain Execution", &format!("{:.6}", finalize_cost)], ["Priority Fee", &format!("{:.6}", priority_fee)], - ["Total", &format!("{:.6}", total_cost)], + ["Total", &format!("{:.6}", base_fee + priority_fee)], ]; let mut out = Vec::new(); text_tables::render(&mut out, data).map_err(CliError::table_render_failed)?; diff --git a/leo/cli/commands/mod.rs b/leo/cli/commands/mod.rs index d22cd8683c..2d563bf73f 100644 --- a/leo/cli/commands/mod.rs +++ b/leo/cli/commands/mod.rs @@ -26,6 +26,9 @@ pub use build::LeoBuild; pub mod clean; pub use clean::LeoClean; +pub mod debug; +pub use debug::LeoDebug; + pub mod deploy; pub use deploy::Deploy; @@ -50,23 +53,30 @@ pub use remove::LeoRemove; pub mod run; pub use run::LeoRun; +pub mod test; +pub use test::LeoTest; + pub mod update; pub use update::LeoUpdate; use super::*; -use crate::cli::helpers::context::*; +use crate::cli::{helpers::context::*, query::QueryCommands}; + use leo_errors::{CliError, PackageError, Result, emitter::Handler}; use leo_package::{build::*, outputs::OutputsDirectory, package::*}; -use snarkvm::prelude::{Address, Ciphertext, Plaintext, PrivateKey, Record, ViewKey, block::Transaction}; +use leo_retriever::NetworkName; + +use snarkvm::{ + circuit::{Aleo, AleoCanaryV0, AleoTestnetV0, AleoV0}, + console::network::Network, + prelude::{Address, Ciphertext, Plaintext, PrivateKey, Record, ViewKey, block::Transaction}, +}; use clap::Parser; use colored::Colorize; use std::str::FromStr; use tracing::span::Span; -use crate::cli::query::QueryCommands; -use snarkvm::console::network::Network; - /// Base trait for the Leo CLI, see methods and their documentation for details. pub trait Command { /// If the current command requires running another command beforehand @@ -173,6 +183,8 @@ pub struct BuildOptions { pub conditional_block_max_depth: usize, #[clap(long, help = "Disable type checking of nested conditional branches in finalize scope.")] pub disable_conditional_branch_type_checking: bool, + #[clap(long, help = "Build the test programs as well.", default_value = "false")] + pub build_tests: bool, } impl Default for BuildOptions { @@ -198,6 +210,7 @@ impl Default for BuildOptions { enable_dce_ast_snapshot: false, conditional_block_max_depth: 10, disable_conditional_branch_type_checking: false, + build_tests: true, } } } @@ -210,6 +223,8 @@ pub struct FeeOptions { pub(crate) yes: bool, #[clap(long, help = "Performs a dry-run of transaction generation")] pub(crate) dry_run: bool, + #[clap(long, help = "Base fee in microcredits. Automatically calculated if not provided.")] + pub(crate) base_fee: Option, #[clap(long, help = "Priority fee in microcredits. Defaults to 0.", default_value = "0")] pub(crate) priority_fee: u64, #[clap(long, help = "Private key to authorize fee expenditure.")] @@ -220,6 +235,8 @@ pub struct FeeOptions { long )] record: Option, + #[clap(long, help = "Consensus version to use for the transaction.")] + pub(crate) consensus_version: Option, } /// Parses the record string. If the string is a ciphertext, then attempt to decrypt it. Lifted from snarkOS. @@ -241,7 +258,7 @@ fn check_balance( private_key: &PrivateKey, endpoint: &str, network: &str, - context: Context, + context: &Context, total_cost: u64, ) -> Result<()> { // Derive the account address. @@ -251,7 +268,7 @@ fn check_balance( endpoint: Some(endpoint.to_string()), network: Some(network.to_string()), command: QueryCommands::Program { - command: crate::cli::commands::query::LeoProgram { + command: query::LeoProgram { name: "credits".to_string(), mappings: false, mapping_value: Some(vec!["account".to_string(), address.to_string()]), @@ -276,6 +293,30 @@ fn check_balance( } } +// A helper function to query for the latest block height. +fn get_latest_block_height(endpoint: &str, network: &str, context: &Context) -> Result { + // Query the latest block height. + let height = LeoQuery { + endpoint: Some(endpoint.to_string()), + network: Some(network.to_string()), + command: QueryCommands::Block { + command: query::LeoBlock { + id: None, + latest: false, + latest_hash: false, + latest_height: true, + range: None, + transactions: false, + to_height: false, + }, + }, + } + .execute(Context::new(context.path.clone(), context.home.clone(), true)?)?; + // Parse the height. + let height = height.parse::().map_err(CliError::string_parse_error)?; + Ok(height) +} + /// Determine if the transaction should be broadcast or displayed to user. fn handle_broadcast(endpoint: &String, transaction: Transaction, operation: &String) -> Result<()> { println!("Broadcasting transaction to {}...\n", endpoint.clone()); diff --git a/leo/cli/commands/query/mod.rs b/leo/cli/commands/query/mod.rs index f09766a140..7b99ff81a4 100644 --- a/leo/cli/commands/query/mod.rs +++ b/leo/cli/commands/query/mod.rs @@ -17,26 +17,26 @@ use super::*; use snarkvm::prelude::{CanaryV0, MainnetV0, TestnetV0}; -mod block; -use block::LeoBlock; +pub mod block; +pub use block::LeoBlock; pub mod program; pub use program::LeoProgram; -mod state_root; -use state_root::StateRoot; +pub mod state_root; +pub use state_root::StateRoot; -mod committee; -use committee::LeoCommittee; +pub mod committee; +pub use committee::LeoCommittee; -mod mempool; -use mempool::LeoMempool; +pub mod mempool; +pub use mempool::LeoMempool; -mod peers; -use peers::LeoPeers; +pub mod peers; +pub use peers::LeoPeers; -mod transaction; -use transaction::LeoTransaction; +pub mod transaction; +pub use transaction::LeoTransaction; mod utils; use utils::*; diff --git a/leo/cli/commands/remove.rs b/leo/cli/commands/remove.rs index 4807991971..3b8e1501b0 100644 --- a/leo/cli/commands/remove.rs +++ b/leo/cli/commands/remove.rs @@ -28,6 +28,9 @@ pub struct LeoRemove { )] pub(crate) name: Option, + #[clap(short = 'd', long, help = "Whether the dependency is a dev dependency", default_value = "false")] + pub(crate) dev: bool, + #[clap(long, help = "Clear all previous dependencies.", default_value = "false")] pub(crate) all: bool, } @@ -54,58 +57,49 @@ impl Command for LeoRemove { let manifest: Manifest = serde_json::from_str(&program_data) .map_err(|err| PackageError::failed_to_deserialize_manifest_file(path.to_str().unwrap(), err))?; - let dependencies: Vec = if !self.all { - // Note that this unwrap is safe since `name` is required if `all` is `false`. - let name: String = self.name.unwrap().clone(); - - let mut found_match = false; - let dep = match manifest.dependencies() { - Some(ref dependencies) => dependencies - .iter() - .filter_map(|dependency| { - if dependency.name() == &name { - found_match = true; - let msg = match (dependency.path(), dependency.network()) { - (Some(local_path), _) => format!( - "local dependency to `{}` from path `{}`", - name, - local_path.to_str().unwrap().replace('\"', "") - ), - (_, Some(network)) => { - format!("network dependency to `{}` from network `{}`", name, network) - } - _ => format!("git dependency to `{name}`"), - }; - tracing::warn!("✅ Successfully removed the {msg}."); - None - } else { - Some(dependency.clone()) - } - }) - .collect(), - _ => Vec::new(), - }; - - // Throw error if no match is found. - if !found_match { - return Err(PackageError::dependency_not_found(name).into()); - } + // Destructure the manifest. + let Manifest { program, version, description, license, dependencies, dev_dependencies } = manifest; - dep + // Add the dependency to the appropriate section. + let (dependencies, dev_dependencies) = if self.all { + if self.dev { (Some(Vec::new()), dev_dependencies) } else { (dependencies, Some(Vec::new())) } } else { - Vec::new() + // Note that this unwrap is safe since `name` is required if `all` is `false`. + let name = self.name.unwrap(); + if self.dev { + (dependencies, remove_dependency(dev_dependencies, name)?) + } else { + (remove_dependency(dependencies, name)?, dev_dependencies) + } }; // Update the manifest file. let new_manifest = Manifest::new( - manifest.program(), - manifest.version(), - manifest.description(), - manifest.license(), - Some(dependencies), + program.as_str(), + version.as_str(), + description.as_str(), + license.as_str(), + dependencies, + dev_dependencies, ); new_manifest.write_to_dir(&path)?; Ok(()) } } + +// A helper function to remove a dependency from either the `dependencies` or `dev_dependencies` section of the manifest. +fn remove_dependency(dependencies: Option>, name: String) -> Result>> { + // Remove the dependency from the list, returning an error if it was not found. + match dependencies { + None => Err(PackageError::dependency_not_found(name).into()), + Some(mut dependencies) => { + if let Some(index) = dependencies.iter().position(|dep| dep.name() == &name) { + dependencies.remove(index); + Ok(Some(dependencies)) + } else { + Err(PackageError::dependency_not_found(name).into()) + } + } + } +} diff --git a/leo/cli/commands/test.rs b/leo/cli/commands/test.rs new file mode 100644 index 0000000000..5c78701c17 --- /dev/null +++ b/leo/cli/commands/test.rs @@ -0,0 +1,86 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use super::*; + +/// Build, Prove and Run Leo program with inputs +#[derive(Parser, Debug)] +pub struct LeoTest { + #[clap(name = "TESTNAME", help = "If specified, only run tests containing this string in their names.")] + name: Option, + #[clap(long, help = "Compile, but don't run the tests", default_value = "false")] + no_run: bool, + #[clap(long, help = "Run all tests regardless of failure.", default_value = "false")] + no_fail_fast: bool, + #[clap(short, long, help = "Number of parallel jobs, the maximum is the number of CPUs.")] + jobs: Option, + #[clap(long, help = "Skip running the native tests.", default_value = "false")] + skip_native: bool, + // TODO: The default should eventually be `false`. + #[clap(long, help = "Skip running the interpreted tests.", default_value = "true")] + skip_interpreted: bool, + // TODO: The default should eventually be `false`. + #[clap(long, help = "Skip running the end-to-end tests.", default_value = "true")] + skip_end_to_end: bool, + #[clap(flatten)] + compiler_options: BuildOptions, +} + +impl Command for LeoTest { + type Input = ::Output; + type Output = (); + + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Leo") + } + + fn prelude(&self, context: Context) -> Result { + // Set `build_tests` to `true` to ensure that the tests are built. + let mut options = self.compiler_options.clone(); + options.build_tests = true; + (LeoBuild { options }).execute(context) + } + + fn apply(self, context: Context, _input: Self::Input) -> Result { + // Parse the network. + let network = NetworkName::try_from(context.get_network(&self.compiler_options.network)?)?; + match network { + NetworkName::TestnetV0 => handle_test::(self, context), + NetworkName::MainnetV0 => { + #[cfg(feature = "only_testnet")] + panic!("Mainnet chosen with only_testnet feature"); + #[cfg(not(feature = "only_testnet"))] + return handle_test::(self, context); + } + NetworkName::CanaryV0 => { + #[cfg(feature = "only_testnet")] + panic!("Canary chosen with only_testnet feature"); + #[cfg(not(feature = "only_testnet"))] + return handle_test::(self, context); + } + } + } +} + +// A helper function to handle the `test` command. +fn handle_test(command: LeoTest, context: Context) -> Result<::Output> { + // Select the number of jobs, defaulting to the number of CPUs. + // If the number exceeds the number of CPUs, it is clamped to the number of CPUs. + let num_cpus = num_cpus::get(); + let jobs = command.jobs.unwrap_or(num_cpus).min(num_cpus); + + Ok(()) +} diff --git a/leo/cli/helpers/updater.rs b/leo/cli/helpers/updater.rs index a63f605a0a..97bf104404 100644 --- a/leo/cli/helpers/updater.rs +++ b/leo/cli/helpers/updater.rs @@ -16,18 +16,28 @@ use leo_errors::{CliError, Result}; -use std::fmt::Write as _; +use aleo_std; use colored::Colorize; use self_update::{Status, backends::github, version::bump_is_greater}; +use std::{ + fmt::Write as _, + fs, + path::{Path, PathBuf}, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; pub struct Updater; // TODO Add logic for users to easily select release versions. impl Updater { const LEO_BIN_NAME: &'static str = "leo"; + const LEO_CACHE_LAST_CHECK_FILE: &'static str = "leo_cache_last_update_check"; + const LEO_CACHE_VERSION_FILE: &'static str = "leo_cache_latest_version"; const LEO_REPO_NAME: &'static str = "leo"; const LEO_REPO_OWNER: &'static str = "AleoHQ"; + // 24 hours + const LEO_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); /// Show all available releases for `leo`. pub fn show_available_releases() -> Result { @@ -85,15 +95,139 @@ impl Updater { } } - /// Display the CLI message, if the Leo configuration allows. - pub fn print_cli() { - // If the auto update configuration is off, notify the user to update leo. - if let Ok(latest_version) = Self::update_available() { - let mut message = "🟢 A new version is available! Run".bold().green().to_string(); - message += &" `leo update` ".bold().white(); - message += &format!("to update to v{latest_version}.").bold().green(); + /// Read the latest version from the version file. + pub fn read_latest_version() -> Result, CliError> { + let version_file_path = Self::get_version_file_path(); + match fs::read_to_string(version_file_path) { + Ok(version) => Ok(Some(version.trim().to_string())), + Err(_) => Ok(None), + } + } + + /// Generate the CLI message if a new version is available. + pub fn get_cli_string() -> Result, CliError> { + if let Some(latest_version) = Self::read_latest_version()? { + let colorized_message = format!( + "\n🟢 {} {} {}", + "A new version is available! Run".bold().green(), + "`leo update`".bold().white(), + format!("to update to v{}.", latest_version).bold().green() + ); + Ok(Some(colorized_message)) + } else { + Ok(None) + } + } - tracing::info!("\n{}\n", message); + /// Display the CLI message if a new version is available. + pub fn print_cli() -> Result<(), CliError> { + if let Some(message) = Self::get_cli_string()? { + println!("{}", message); } + Ok(()) + } + + /// Check for updates, respecting the update interval. (Currently once per day.) + /// If a new version is found, write it to a cache file and alert in every call. + pub fn check_for_updates(force: bool) -> Result { + // Get the cache directory and relevant file paths. + let cache_dir = Self::get_cache_dir(); + let last_check_file = cache_dir.join(Self::LEO_CACHE_LAST_CHECK_FILE); + let version_file = Self::get_version_file_path(); + + // Determine if we should check for updates. + let should_check = force || Self::should_check_for_updates(&last_check_file)?; + + if should_check { + match Self::update_available() { + Ok(latest_version) => { + // A new version is available + Self::update_check_files(&cache_dir, &last_check_file, &version_file, &latest_version)?; + Ok(true) + } + Err(_) => { + // No new version available or error occurred + // We'll treat both cases as "no update" for simplicity + Self::update_check_files(&cache_dir, &last_check_file, &version_file, env!("CARGO_PKG_VERSION"))?; + Ok(false) + } + } + } else if version_file.exists() { + if let Ok(stored_version) = fs::read_to_string(&version_file) { + let current_version = env!("CARGO_PKG_VERSION"); + Ok(bump_is_greater(current_version, stored_version.trim()).map_err(CliError::self_update_error)?) + } else { + // If we can't read the file, assume no update is available + Ok(false) + } + } else { + Ok(false) + } + } + + /// Updates the check files with the latest version information and timestamp. + /// + /// This function creates the cache directory if it doesn't exist, writes the current time + /// to the last check file, and writes the latest version to the version file. + fn update_check_files( + cache_dir: &Path, + last_check_file: &Path, + version_file: &Path, + latest_version: &str, + ) -> Result<(), CliError> { + // Recursively create the cache directory and all of its parent components if they are missing. + fs::create_dir_all(cache_dir).map_err(CliError::cli_io_error)?; + + // Get the current time. + let current_time = Self::get_current_time()?; + + // Write the current time to the last check file. + fs::write(last_check_file, current_time.to_string()).map_err(CliError::cli_io_error)?; + + // Write the latest version to the version file. + fs::write(version_file, latest_version).map_err(CliError::cli_io_error)?; + + Ok(()) + } + + /// Determines if an update check should be performed based on the last check time. + /// + /// This function reads the last check timestamp from a file and compares it with + /// the current time to decide if enough time has passed for a new check. + fn should_check_for_updates(last_check_file: &Path) -> Result { + match fs::read_to_string(last_check_file) { + Ok(contents) => { + // Parse the last check timestamp from the file. + let last_check = contents + .parse::() + .map_err(|e| CliError::cli_runtime_error(format!("Failed to parse last check time: {}", e)))?; + + // Get the current time. + let current_time = Self::get_current_time()?; + + // Check if enough time has passed since the last check. + Ok(current_time.saturating_sub(last_check) > Self::LEO_UPDATE_CHECK_INTERVAL.as_secs()) + } + // If we can't read the file, assume we should check + Err(_) => Ok(true), + } + } + + /// Gets the current system time as seconds since the Unix epoch. + fn get_current_time() -> Result { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| CliError::cli_runtime_error(format!("System time error: {}", e))) + .map(|duration| duration.as_secs()) + } + + /// Get the path to the file storing the latest version information. + fn get_version_file_path() -> PathBuf { + Self::get_cache_dir().join(Self::LEO_CACHE_VERSION_FILE) + } + + /// Get the cache directory for Leo. + fn get_cache_dir() -> PathBuf { + aleo_std::aleo_dir().join("leo") } } diff --git a/leo/package/Cargo.toml b/leo/package/Cargo.toml index 5de5d116fe..782d4b18a1 100644 --- a/leo/package/Cargo.toml +++ b/leo/package/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-package" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "Package parser for the Leo programming language" homepage = "https://leo-lang.org" @@ -31,7 +31,7 @@ workspace = true workspace = true [dependencies.serde] -workspace= true +workspace = true [dependencies.tracing] workspace = true diff --git a/leo/package/src/lib.rs b/leo/package/src/lib.rs index 6d069fdfee..eeeffd0410 100644 --- a/leo/package/src/lib.rs +++ b/leo/package/src/lib.rs @@ -24,6 +24,7 @@ pub mod outputs; pub mod package; pub mod root; pub mod source; +pub mod tst; use leo_errors::{PackageError, Result}; @@ -31,6 +32,8 @@ use std::{fs, fs::ReadDir, path::PathBuf}; pub static LEO_FILE_EXTENSION: &str = ".leo"; +pub static TEST_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"; + pub(crate) fn parse_file_paths(directory: ReadDir, file_paths: &mut Vec) -> Result<()> { for file_entry in directory { let file_entry = file_entry.map_err(PackageError::failed_to_get_leo_file_entry)?; diff --git a/leo/package/src/package.rs b/leo/package/src/package.rs index 63bae5fa79..747bf2ba0b 100644 --- a/leo/package/src/package.rs +++ b/leo/package/src/package.rs @@ -15,11 +15,14 @@ // along with the Leo library. If not, see . use crate::{ + TEST_PRIVATE_KEY, root::{Env, Gitignore}, source::{MainFile, SourceDirectory}, + tst::TestDirectory, }; use leo_errors::{PackageError, Result}; +use crate::tst::DefaultTestFile; use leo_retriever::{Manifest, NetworkName}; use serde::Deserialize; use snarkvm::prelude::{Network, PrivateKey}; @@ -148,11 +151,7 @@ impl Package { // Create the .env file. // Include the private key of validator 0 for ease of use with local devnets, as it will automatically be seeded with funds. - Env::::new( - Some(PrivateKey::::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH")?), - endpoint, - )? - .write_to(&path)?; + Env::::new(Some(PrivateKey::::from_str(TEST_PRIVATE_KEY)?), endpoint)?.write_to(&path)?; // Create a manifest. let manifest = Manifest::default(package_name); @@ -174,6 +173,12 @@ impl Package { .into()); } + // Create the test directory. + TestDirectory::create(&path)?; + + // Create the default test file. + DefaultTestFile::write_to(&path)?; + Ok(()) } } diff --git a/leo/package/src/tst/default.rs b/leo/package/src/tst/default.rs new file mode 100644 index 0000000000..c228cc0c64 --- /dev/null +++ b/leo/package/src/tst/default.rs @@ -0,0 +1,54 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +//! The default file provided when invoking `leo new` to create a new package. + +use crate::tst::directory::TEST_DIRECTORY_NAME; +use leo_errors::{PackageError, Result}; + +use std::{borrow::Cow, fs::File, io::Write, path::Path}; + +pub static DEFAULT_TEST_FILENAME: &str = "test.leo"; + +pub struct DefaultTestFile; + +impl DefaultTestFile { + pub fn write_to(path: &Path) -> Result<()> { + let mut path = Cow::from(path); + if path.is_dir() { + if !path.ends_with(TEST_DIRECTORY_NAME) { + path.to_mut().push(TEST_DIRECTORY_NAME); + } + path.to_mut().push(DEFAULT_TEST_FILENAME); + } + + let mut file = File::create(&path).map_err(PackageError::io_error_main_file)?; + Ok(file.write_all(Self::template().as_bytes()).map_err(PackageError::io_error_main_file)?) + } + + fn template() -> String { + r#"// A Leo test file. +// To learn more about testing your program, see the documentation at https://docs.leo-lang.org +program test.aleo { + @test + transition test_helloworld() {{ + assert_eq(1u32 + 2u32, 3u32); + }} +} +"# + .to_string() + } +} diff --git a/leo/package/src/tst/directory.rs b/leo/package/src/tst/directory.rs new file mode 100644 index 0000000000..6780f64894 --- /dev/null +++ b/leo/package/src/tst/directory.rs @@ -0,0 +1,57 @@ +// Copyright (C) 2019-2024 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::parse_file_paths; + +use leo_errors::{PackageError, Result}; + +use std::{ + borrow::Cow, + fs, + path::{Path, PathBuf}, +}; + +pub static TEST_DIRECTORY_NAME: &str = "tests/"; + +pub struct TestDirectory; + +impl TestDirectory { + /// Creates a directory at the provided path with the default directory name. + pub fn create(path: &Path) -> Result<()> { + let mut path = Cow::from(path); + if path.is_dir() && !path.ends_with(TEST_DIRECTORY_NAME) { + path.to_mut().push(TEST_DIRECTORY_NAME); + } + + fs::create_dir_all(&path).map_err(PackageError::failed_to_create_test_directory)?; + Ok(()) + } + + /// Returns a list of files in the test directory. + pub fn files(path: &Path) -> Result> { + let mut path = Cow::from(path); + if path.is_dir() && !path.ends_with(TEST_DIRECTORY_NAME) { + path.to_mut().push(TEST_DIRECTORY_NAME); + } + + let directory = fs::read_dir(&path).map_err(|err| PackageError::failed_to_read_file(path.display(), err))?; + let mut file_paths = Vec::new(); + + parse_file_paths(directory, &mut file_paths)?; + + Ok(file_paths) + } +} diff --git a/errors/src/errors/import/mod.rs b/leo/package/src/tst/mod.rs similarity index 87% rename from errors/src/errors/import/mod.rs rename to leo/package/src/tst/mod.rs index 184465ca8c..cfd64f8a8d 100644 --- a/errors/src/errors/import/mod.rs +++ b/leo/package/src/tst/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -/// This module contains the Import error definitions. -pub mod import_errors; -pub use self::import_errors::*; +pub mod default; +pub use default::*; + +pub mod directory; +pub use directory::*; diff --git a/notes b/notes new file mode 100644 index 0000000000..d7b0935124 --- /dev/null +++ b/notes @@ -0,0 +1,8 @@ + +call deposit(100u64), type failure + +cannot print register credits.aleo/transfer_public + +after set_program, message or print out symbols + +set self.signer diff --git a/tests/expectations/compiler/array/array_of_records.out b/tests/expectations/compiler/array/array_of_records.out index abfecc9441..562e9ffa43 100644 --- a/tests/expectations/compiler/array/array_of_records.out +++ b/tests/expectations/compiler/array/array_of_records.out @@ -5,5 +5,5 @@ Error [ETYC0372079]: An array cannot have a record as an element type --> compiler-test:9:20 | 9 | transition foo(a: [bar; 8]) -> u8 { - | ^ + | ^^^^^^^^^^^ """] diff --git a/tests/expectations/compiler/array/array_too_large_fail.out b/tests/expectations/compiler/array/array_too_large_fail.out index c36adb1789..3972b4c52c 100644 --- a/tests/expectations/compiler/array/array_too_large_fail.out +++ b/tests/expectations/compiler/array/array_too_large_fail.out @@ -5,5 +5,5 @@ Error [ETYC0372077]: An array cannot have more than 32 elements, found one with --> compiler-test:4:20 | 4 | transition foo(a: [bool; 33]) -> bool { - | ^ + | ^^^^^^^^^^^^^ """] diff --git a/tests/expectations/compiler/array/array_too_small_fail.out b/tests/expectations/compiler/array/array_too_small_fail.out index 87b9ba7226..70f3d81ad7 100644 --- a/tests/expectations/compiler/array/array_too_small_fail.out +++ b/tests/expectations/compiler/array/array_too_small_fail.out @@ -5,5 +5,5 @@ Error [ETYC0372076]: An array cannot be empty --> compiler-test:4:20 | 4 | transition foo(a: [bool; 0]) -> bool { - | ^ + | ^^^^^^^^^^^^ """] diff --git a/tests/expectations/compiler/constants/block_height_type_fail.out b/tests/expectations/compiler/constants/block_height_type_fail.out new file mode 100644 index 0000000000..fcb4df4d49 --- /dev/null +++ b/tests/expectations/compiler/constants/block_height_type_fail.out @@ -0,0 +1,9 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ETYC0372003]: Expected type `u64` but type `u32` was found + --> compiler-test:5:22 + | + 5 | let x: u64 = block.height; + | ^^^^^^^^^^^^ +"""] diff --git a/tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out b/tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out new file mode 100644 index 0000000000..4205dced3d --- /dev/null +++ b/tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out @@ -0,0 +1,24 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ETYC0372005]: Unknown variable `Yo` + --> compiler-test:13:34 + | + 13 | CheatCode::print_mapping(Yo); + | ^^ +Error [ETYC0372005]: Unknown variable `account` + --> compiler-test:14:34 + | + 14 | CheatCode::print_mapping(test_dep.aleo/account); + | ^^^^^^^^^^^^^^^^^^^^^ +Error [ETYC0372003]: Expected type `u32` but type `u64` was found + --> compiler-test:15:37 + | + 15 | CheatCode::set_block_height(1u64); + | ^^^^ +Error [ETYC0372003]: Expected type `u32` but type `u64` was found + --> compiler-test:16:37 + | + 16 | CheatCode::set_block_height(a); + | ^ +"""] diff --git a/tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out b/tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out new file mode 100644 index 0000000000..ff52b82d9e --- /dev/null +++ b/tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out @@ -0,0 +1,44 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "0fbe7b86610386bfb1c7f0f211b2043baae706b9195008f8553511968f9297e7", type_checked_symbol_table = "efc3324af11b2f3645010266f1a871d799b81b07bec594fa88402b3f6fe1330b", unrolled_symbol_table = "efc3324af11b2f3645010266f1a871d799b81b07bec594fa88402b3f6fe1330b", initial_ast = "472f984ad224e345de6a6a8cb7c4986b0bf8fa288713c38a506b41bad280faa5", unrolled_ast = "472f984ad224e345de6a6a8cb7c4986b0bf8fa288713c38a506b41bad280faa5", ssa_ast = "ff6501ea72e6a46b15d71a89b71181851fba9aa2e6ee2a36d70218ad1a089a68", flattened_ast = "ba4154876562575fc3f8b6106a3ed4ab331382a4538ebc9630c82ed9be48176b", destructured_ast = "a995365c129f150bc361a571e5a0810f014a62c170d39e904b7de473bcdac50f", inlined_ast = "3a2f11285208b9bd75048be921a23504d9389ae81e2bdc96f631943cfa4349c6", dce_ast = "ed19a1a5455d89e6a59914923e69d600b0fde7fa91cae652d70756eb59365e03", bytecode = """ +program test_dep.aleo; + +record yeets: + owner as address.private; + val as u32.private; + +mapping Yo: + key as u32.public; + value as u32.public; + +function main_dep: + input r0 as u32.private; + async main_dep r0 1u32 into r1; + cast self.caller 1u32 into r2 as yeets.record; + output r2 as yeets.record; + output r1 as test_dep.aleo/main_dep.future; + +finalize main_dep: + input r0 as u32.public; + input r1 as u32.public; + set r1 into Yo[r0]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "1ff3afb19b60e93b29bcf302b325d787717fc9f72dc76ebf0e2f16a88c61f8e1", type_checked_symbol_table = "34fca725cd812896570be3b52571fda6af6ae081e686f1414d3c356e3a96f568", unrolled_symbol_table = "34fca725cd812896570be3b52571fda6af6ae081e686f1414d3c356e3a96f568", initial_ast = "308e8bf01c3d58f3833b54d7bd297cc34a47754b723fdb727b06aafd88c7322c", unrolled_ast = "cbebc3742af33ad850a585eb37f0e50dd4182317f89bf229083826d3a41a7719", ssa_ast = "c29e889749cbd936b56a07f85d7fa1cc932901ef0b89c5d9f81badf262122286", flattened_ast = "37eb161a0cfc90d08f16ea37e2a815476b11e7b03adf57361c97217807a49e58", destructured_ast = "36ad597d27bc588495a6168d7fabfd8750b8efab765f39992851530b48e04712", inlined_ast = "0152ae3ca21c99c5c59eb80d72832fcd8cb830f13ab4dfab8137af9dfaa5e43e", dce_ast = "0152ae3ca21c99c5c59eb80d72832fcd8cb830f13ab4dfab8137af9dfaa5e43e", bytecode = """ +import test_dep.aleo; +program test.aleo; + +mapping account: + key as address.public; + value as u64.public; + +function main: + input r0 as u32.public; + async main r0 into r1; + output r1 as test.aleo/main.future; + +finalize main: + input r0 as u32.public; + assert.eq true true; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/finalize/finalize_incorrect_modes_fail.out b/tests/expectations/compiler/finalize/finalize_incorrect_modes_fail.out index 6dfc880ef6..c312bac687 100644 --- a/tests/expectations/compiler/finalize/finalize_incorrect_modes_fail.out +++ b/tests/expectations/compiler/finalize/finalize_incorrect_modes_fail.out @@ -14,7 +14,7 @@ Error [ETYC0372032]: An input to an async function must be public. --> compiler-test:10:76 | 10 | async function finalize_mint_public(public receiver: address, constant amount: u64) -> constant u64 { - | ^^^^^^ + | ^^^^^^^^^^^ | = Use a `public` modifier to the input variable declaration or remove the visibility modifier entirely. Error [ETYC0372038]: A returned value cannot be a constant. diff --git a/tests/expectations/compiler/function/annotated_function.out b/tests/expectations/compiler/function/annotated_function.out new file mode 100644 index 0000000000..9a2505435f --- /dev/null +++ b/tests/expectations/compiler/function/annotated_function.out @@ -0,0 +1,13 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [{ initial_symbol_table = "690edb74eb02c5ac9e717bfb51933668a2b530f7e803ba5666880d23f28d5bec", type_checked_symbol_table = "e7e865d4fe1f786bea9fa28c5cff41a8c29ce00da69306b522dd79eac50d4c78", unrolled_symbol_table = "e7e865d4fe1f786bea9fa28c5cff41a8c29ce00da69306b522dd79eac50d4c78", initial_ast = "97b8563a49a209737aa82195d8fe84257cdd16ac2b32133de91b43de7bb557b1", unrolled_ast = "97b8563a49a209737aa82195d8fe84257cdd16ac2b32133de91b43de7bb557b1", ssa_ast = "4e6c70e32a0fd6f3fc362568ba2f365b198be164922c9276b48a54513a9a0501", flattened_ast = "5ecbeaa566e8bd200515944b8f73945bff67eace488df97e8d0f477f2255bcd7", destructured_ast = "f0ae5e19798aaa5777a17d477f6eb0b2d9ccd1067c807c4348e1ba0a0f147dd5", inlined_ast = "f0ae5e19798aaa5777a17d477f6eb0b2d9ccd1067c807c4348e1ba0a0f147dd5", dce_ast = "f0ae5e19798aaa5777a17d477f6eb0b2d9ccd1067c807c4348e1ba0a0f147dd5", bytecode = """ +program test.aleo; + +function test: + is.eq 1u32 1u32 into r0; + assert.eq r0 true; + +function test_other: + is.eq 1u32 1u32 into r0; + assert.eq r0 true; +""", errors = "", warnings = "" }] }]] diff --git a/tests/expectations/compiler/function/annotated_function_fail.out b/tests/expectations/compiler/function/annotated_function_fail.out index 7c9b020e13..967040f3ea 100644 --- a/tests/expectations/compiler/function/annotated_function_fail.out +++ b/tests/expectations/compiler/function/annotated_function_fail.out @@ -1,21 +1,36 @@ namespace = "Compile" -expectation = "Fail" -outputs = [""" -Error [ETYC0372027]: Unknown annotation: `@test`. - --> compiler-test:4:5 +expectation = "Pass" +outputs = [[{ compile = [{ initial_symbol_table = "4886e187c6b46dbeeb507a7f5188c89f415516c2ff7781ad5af48ea13fca665b", type_checked_symbol_table = "214bdcdf17793e3098343e1aa5993244ad520847278f8864ce7ce0bf4e3bf8ad", unrolled_symbol_table = "214bdcdf17793e3098343e1aa5993244ad520847278f8864ce7ce0bf4e3bf8ad", initial_ast = "05ac2357efbe8d0e00d30f8b7ee33cb625070471ab6f513d2613cb8baeaeff39", unrolled_ast = "05ac2357efbe8d0e00d30f8b7ee33cb625070471ab6f513d2613cb8baeaeff39", ssa_ast = "2f8548323d07c917964e711b10e5198796ac601eb3c75aae61009a600a437220", flattened_ast = "67707c544bfd8a551287d9bf6d4d869b4817d767003c44735ec27ff33d75cf8d", destructured_ast = "3c3c87cf1069c8691bfe57d696be55e2716021cb03e11f8345d3bafb5b23c99b", inlined_ast = "3c3c87cf1069c8691bfe57d696be55e2716021cb03e11f8345d3bafb5b23c99b", dce_ast = "3c3c87cf1069c8691bfe57d696be55e2716021cb03e11f8345d3bafb5b23c99b", bytecode = """ +program test.aleo; + +function test: + is.eq 1u32 1u32 into r0; + assert.eq r0 true; + +closure foo: + input r0 as u8; + input r1 as u8; + add r0 r1 into r2; + output r2 as u8; + +closure bar: + input r0 as u8; + input r1 as u8; + mul r0 r1 into r2; + output r2 as u8; +""", errors = "", warnings = """ +Warning [WTYC0372005]: Unknown key `foo` in annotation `test`. + --> compiler-test:16:11 | - 4 | @test - | ^^^^^ -Error [ETYC0372027]: Unknown annotation: `@program`. - --> compiler-test:9:5 + 16 | @test(foo) + | ^^^ +Warning [WTYC0372004]: Unknown annotation: `foo`. + --> compiler-test:6:5 | - 9 | @program - | ^^^^^^^^ -Error [ETYC0372083]: A program must have at least one transition function. - --> compiler-test:1:1 + 6 | @foo + | ^^^^ +Warning [WTYC0372004]: Unknown annotation: `program`. + --> compiler-test:11:5 | - 1 | - 2 | - 3 | program test.aleo { - | ^^^^^^^^^^^^ -"""] + 11 | @program + | ^^^^^^^^""" }] }]] diff --git a/tests/expectations/compiler/function/function_returns_record_fail.out b/tests/expectations/compiler/function/function_returns_record_fail.out index 0ac88be6ab..28ff80dd06 100644 --- a/tests/expectations/compiler/function/function_returns_record_fail.out +++ b/tests/expectations/compiler/function/function_returns_record_fail.out @@ -5,12 +5,12 @@ Error [ETYC0372057]: Only `transition` functions can have a record as input or o --> compiler-test:9:18 | 9 | function foo(board: Board, data: u8) -> Board { - | ^^^^^ + | ^^^^^^^^^^^^ Error [ETYC0372057]: Only `transition` functions can have a record as input or output. --> compiler-test:9:18 | 9 | function foo(board: Board, data: u8) -> Board { - | ^^^^^ + | ^^^^^^^^^^^^ Error [ETYC0372057]: Only `transition` functions can have a record as input or output. --> compiler-test:9:45 | @@ -25,10 +25,10 @@ Error [ETYC0372057]: Only `transition` functions can have a record as input or o --> compiler-test:16:18 | 16 | function bar(board: Board) { - | ^^^^^ + | ^^^^^^^^^^^^ Error [ETYC0372057]: Only `transition` functions can have a record as input or output. --> compiler-test:16:18 | 16 | function bar(board: Board) { - | ^^^^^ + | ^^^^^^^^^^^^ """] diff --git a/tests/expectations/compiler/function/non_transition_variant_input_record_fail.out b/tests/expectations/compiler/function/non_transition_variant_input_record_fail.out index 14a345ce74..5c52a9615e 100644 --- a/tests/expectations/compiler/function/non_transition_variant_input_record_fail.out +++ b/tests/expectations/compiler/function/non_transition_variant_input_record_fail.out @@ -5,12 +5,12 @@ Error [ETYC0372057]: Only `transition` functions can have a record as input or o --> compiler-test:9:18 | 9 | function foo(a: credits) -> u8 { - | ^ + | ^^^^^^^^^^ Error [ETYC0372057]: Only `transition` functions can have a record as input or output. --> compiler-test:9:18 | 9 | function foo(a: credits) -> u8 { - | ^ + | ^^^^^^^^^^ Error [ETYC0372057]: Only `transition` functions can have a record as input or output. --> compiler-test:13:41 | diff --git a/tests/expectations/compiler/function/undefined_data_type_fail.out b/tests/expectations/compiler/function/undefined_data_type_fail.out index 63f8440de1..432e2cd2f6 100644 --- a/tests/expectations/compiler/function/undefined_data_type_fail.out +++ b/tests/expectations/compiler/function/undefined_data_type_fail.out @@ -5,14 +5,14 @@ Error [ETYC0372017]: The type `Board` is not found in the current scope. --> compiler-test:4:35 | 4 | function aria192check_for_win(b: Board, p: u8) -> u128bool { - | ^ + | ^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` Error [ETYC0372017]: The type `Board` is not found in the current scope. --> compiler-test:4:35 | 4 | function aria192check_for_win(b: Board, p: u8) -> u128bool { - | ^ + | ^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` Error [ETYC0372017]: The type `u128bool` is not found in the current scope. diff --git a/tests/expectations/compiler/function/unknown_parameter_type_fail.out b/tests/expectations/compiler/function/unknown_parameter_type_fail.out index fb869d1b9d..e7e1376626 100644 --- a/tests/expectations/compiler/function/unknown_parameter_type_fail.out +++ b/tests/expectations/compiler/function/unknown_parameter_type_fail.out @@ -5,7 +5,7 @@ Error [ETYC0372017]: The type `Foo` is not found in the current scope. --> compiler-test:4:28 | 4 | transition main(a: u8, foo: Foo) -> u8 { - | ^^^ + | ^^^^^^^^ | = If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo/credits` instead of `credits` Error [ETYC0372017]: The type `Foo` is not found in the current scope. diff --git a/tests/expectations/compiler/futures/await_out_of_order.out b/tests/expectations/compiler/futures/await_out_of_order.out new file mode 100644 index 0000000000..a8f20ac29b --- /dev/null +++ b/tests/expectations/compiler/futures/await_out_of_order.out @@ -0,0 +1,47 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "c05e8ce7b527a0192d65e50963afd67a1fe92c837da97a5299c22af29f0275dd", type_checked_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", unrolled_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", initial_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", unrolled_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", ssa_ast = "5e2a213bd10e28dad299e0e48ac3336484fd2ffd894eeb63d15d67e6af65195e", flattened_ast = "11828763a38326b604155e1074699e6ca85205fbc83167d554624d0fe5d8bb2b", destructured_ast = "83c9eaa9aef53de6143980c9e001e59da64c291a7b2aa0693139b868339e589c", inlined_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", dce_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", bytecode = """ +program test.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function main_inner: + input r0 as u32.public; + input r1 as u32.public; + async main_inner r0 r1 into r2; + output r2 as test.aleo/main_inner.future; + +finalize main_inner: + input r0 as u32.public; + input r1 as u32.public; + set r1 into foo[r0]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "748f1d6d760d63c1eb80d518379d99323e13132bf77520f8442534c79c64895b", type_checked_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", unrolled_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", initial_ast = "c1dddbcc56c1c3e89c4633a45eac54ca710cbb206f002e3cc8a4312342d5e43e", unrolled_ast = "1717122f03eb439466cb78560d07bb79e508a7549ce60de07bb76b1c0ccefdad", ssa_ast = "c53df47cb4b84b067fea09e1b1dbadeb7751b57c6624961d5df4317f609eddd1", flattened_ast = "ba297f2543a11739a669f85af00f9bae6b9149c6860d895216f1e5bd96617642", destructured_ast = "8531973ac60ebf3c0d7884bd0aa36794242a6575c379113053c01ca8f11a805f", inlined_ast = "48b3da6f8d8a056bb2e18f2b1b3d83d8a00ed4d259d65ad86321b824390141e9", dce_ast = "48b3da6f8d8a056bb2e18f2b1b3d83d8a00ed4d259d65ad86321b824390141e9", bytecode = """ +import test.aleo; +program basic.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call test.aleo/main_inner 0u32 0u32 into r2; + call test.aleo/main_inner 1u32 1u32 into r3; + async main r2 r3 into r4; + output r4 as basic.aleo/main.future; + +finalize main: + input r0 as test.aleo/main_inner.future; + input r1 as test.aleo/main_inner.future; + await r1; + await r0; +""", errors = "", warnings = """ +Warning [WSAZ0374003]: The future `f2` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:13:9 + | + 13 | f2.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context.""" }, +] }]] diff --git a/tests/expectations/compiler/futures/explicit_type_simple.out b/tests/expectations/compiler/futures/explicit_type_simple.out index 89b6b4df68..909c621b46 100644 --- a/tests/expectations/compiler/futures/explicit_type_simple.out +++ b/tests/expectations/compiler/futures/explicit_type_simple.out @@ -20,7 +20,7 @@ finalize main_inner: input r0 as u32.public; set 1u32 into foo[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "ad49aec92f87f1e65648a7ae10d5bfb563f50bb397a933a9852c979b4ed5e3f3", type_checked_symbol_table = "7a05bbb86250bee3f69eee0c1f46f9d506dcb57c74358c26d158bde771b29bd7", unrolled_symbol_table = "7a05bbb86250bee3f69eee0c1f46f9d506dcb57c74358c26d158bde771b29bd7", initial_ast = "f4b27c45b21e659b2b730a167dbbf8a309b19e71beded7108cb7267b06177417", unrolled_ast = "bdd7c6800831eebcb6a09cb05acd5be0ad83730e1d210eb4d9b4d6b968d0b326", ssa_ast = "e4441d4a0d42e1061d4481bce0113ebd8a6f258dc9e877adc5e52029d3f04991", flattened_ast = "82cca8f1537803acde719f029a4ac265e0c1c53fa6e8cd4e4e2800a4d840c871", destructured_ast = "aee30ce903740d4f39c7f88aae66ed0bca4affce5b51988699cc9167ff946494", inlined_ast = "f4292c099047c4d8e3c0fbdaf7f32a1273a3eb68c4a11b0eccff59bd7c804247", dce_ast = "406a8d3de9427c696512e49e8f7ab27d48616754516e535152dc13c15a3e1ee0", bytecode = """ + { initial_symbol_table = "4dd8afce563dd05c782ba0046cf84da0e161b0db932b993995bc119a0828174c", type_checked_symbol_table = "01e4b42297bfc584587cb7a0ebab2790e0fd87d7c645cdc55a8d6dcc6f48360f", unrolled_symbol_table = "01e4b42297bfc584587cb7a0ebab2790e0fd87d7c645cdc55a8d6dcc6f48360f", initial_ast = "f4b27c45b21e659b2b730a167dbbf8a309b19e71beded7108cb7267b06177417", unrolled_ast = "bdd7c6800831eebcb6a09cb05acd5be0ad83730e1d210eb4d9b4d6b968d0b326", ssa_ast = "e4441d4a0d42e1061d4481bce0113ebd8a6f258dc9e877adc5e52029d3f04991", flattened_ast = "82cca8f1537803acde719f029a4ac265e0c1c53fa6e8cd4e4e2800a4d840c871", destructured_ast = "aee30ce903740d4f39c7f88aae66ed0bca4affce5b51988699cc9167ff946494", inlined_ast = "f4292c099047c4d8e3c0fbdaf7f32a1273a3eb68c4a11b0eccff59bd7c804247", dce_ast = "406a8d3de9427c696512e49e8f7ab27d48616754516e535152dc13c15a3e1ee0", bytecode = """ import test.aleo; program basic.aleo; diff --git a/tests/expectations/compiler/futures/future_access_tuple_fail.out b/tests/expectations/compiler/futures/future_access_tuple_fail.out new file mode 100644 index 0000000000..e2c2639cc3 --- /dev/null +++ b/tests/expectations/compiler/futures/future_access_tuple_fail.out @@ -0,0 +1,11 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ESAZ0374005]: A future may not be used in this way + --> compiler-test:9:37 + | + 9 | let start_2: Future = start.1; + | ^ + | + = Futures should be created, assigned to a variable, and consumed without being moved or reassigned. +"""] diff --git a/tests/expectations/compiler/futures/future_in_composite_fail.out b/tests/expectations/compiler/futures/future_in_composite_fail.out new file mode 100644 index 0000000000..9c531d1fd5 --- /dev/null +++ b/tests/expectations/compiler/futures/future_in_composite_fail.out @@ -0,0 +1,24 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ETYC0372114]: A struct cannot contain a future. + --> compiler-test:7:9 + | + 7 | member: Future, + | ^^^^^^ +Error [ETYC0372031]: A mapping's key cannot be a future + --> compiler-test:4:5 + | + 4 | mapping x: Future => Future; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Error [ETYC0372031]: A mapping's value cannot be a future + --> compiler-test:4:5 + | + 4 | mapping x: Future => Future; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Error [ETYC0372115]: An array cannot have a future as an element type. + --> compiler-test:12:9 + | + 12 | let an_array: [Future; 1] = [future]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +"""] diff --git a/tests/expectations/compiler/futures/future_in_tuple.out b/tests/expectations/compiler/futures/future_in_tuple.out index 09ddba831c..143c38c3c0 100644 --- a/tests/expectations/compiler/futures/future_in_tuple.out +++ b/tests/expectations/compiler/futures/future_in_tuple.out @@ -19,7 +19,7 @@ function transfer_private_to_public: finalize transfer_private_to_public: assert.eq 1u8 1u8; """, errors = "", warnings = "" }, - { initial_symbol_table = "fd67d75af194fb6d6fee5a2b15b4b51ae5511e5d0546c6c6f83063611a168123", type_checked_symbol_table = "031e9fc89b17624e259bb154ca42385665d2cf4349bf1579347a2d2487305a1b", unrolled_symbol_table = "031e9fc89b17624e259bb154ca42385665d2cf4349bf1579347a2d2487305a1b", initial_ast = "fc9f1985c1e0441e9423e67cfd4cb8252178ccc236dfabae17187c5a5cc98ebe", unrolled_ast = "c6fdd37447ee674a058e7fe314096c0df8cf0c02f307ff499e0f08b76cdc6709", ssa_ast = "d26ea69b3993a2a3c4b2660a27706c51383f9b01357d27adf6275a5dfffe6e9d", flattened_ast = "5741efe1907a4da96fbad021b725a22e8c3365fa61b2413b06743c3ed01cda35", destructured_ast = "496bea9fd498c2d4ac9d93dd143beb403e13fdf59fc2ff842d8ff932883feda1", inlined_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", dce_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", bytecode = """ + { initial_symbol_table = "baa9875274a09ad91eb08326f18797401a6e98c32388e75b3b406a539acab343", type_checked_symbol_table = "6cc6e544cd0fac9b595d1236775033d8f492c506570b174bd4958280c45238fb", unrolled_symbol_table = "6cc6e544cd0fac9b595d1236775033d8f492c506570b174bd4958280c45238fb", initial_ast = "fc9f1985c1e0441e9423e67cfd4cb8252178ccc236dfabae17187c5a5cc98ebe", unrolled_ast = "c6fdd37447ee674a058e7fe314096c0df8cf0c02f307ff499e0f08b76cdc6709", ssa_ast = "d26ea69b3993a2a3c4b2660a27706c51383f9b01357d27adf6275a5dfffe6e9d", flattened_ast = "5741efe1907a4da96fbad021b725a22e8c3365fa61b2413b06743c3ed01cda35", destructured_ast = "496bea9fd498c2d4ac9d93dd143beb403e13fdf59fc2ff842d8ff932883feda1", inlined_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", dce_ast = "7c87cc964f8225fd91c634c8683ee0b09aaa301cb29ab85cadc4e4aea65253ba", bytecode = """ import credits.aleo; program test_credits.aleo; diff --git a/tests/expectations/compiler/futures/future_in_tuple_check_fail.out b/tests/expectations/compiler/futures/future_in_tuple_check_fail.out new file mode 100644 index 0000000000..9496f0f61a --- /dev/null +++ b/tests/expectations/compiler/futures/future_in_tuple_check_fail.out @@ -0,0 +1,11 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ETYC0372104]: Not all futures were consumed: result2 + --> compiler-test:9:27 + | + 9 | return (result.0, finish(result.1)); + | ^^^^^^^^^^^^^^^^ + | + = Make sure all futures are consumed exactly once. Consume by passing to an async function call. +"""] diff --git a/tests/expectations/compiler/futures/future_not_all_awaited_fail.out b/tests/expectations/compiler/futures/future_not_all_awaited_fail.out index f7ef0cdbdb..f861b7771b 100644 --- a/tests/expectations/compiler/futures/future_not_all_awaited_fail.out +++ b/tests/expectations/compiler/futures/future_not_all_awaited_fail.out @@ -1,7 +1,7 @@ namespace = "Compile" expectation = "Fail" outputs = [""" -Error [ETYC0372093]: The following futures were never awaited: f4 +Error [ESAZ0374001]: The following futures were never awaited: f4 --> compiler-test:12:5 | 12 | async function finalize_foo(f0: Future, f1: Future, f2: Future, f3: Future, f4: Future, f5: Future) { @@ -14,4 +14,31 @@ Error [ETYC0372093]: The following futures were never awaited: f4 | ^ | = Ex: for `f: Future` call `f.await()` to await a future. -"""] +Warning [WSAZ0374003]: The future `f1` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:13:9 + | + 13 | f1.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Warning [WSAZ0374003]: The future `f2` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:14:9 + | + 14 | f2.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Warning [WSAZ0374003]: The future `f3` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:15:9 + | + 15 | f3.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Warning [WSAZ0374003]: The future `f5` is not awaited in the order in which they were passed in to the `async` function. + --> compiler-test:17:9 + | + 17 | f5.await(); + | ^^ + | + = While it is not required for futures to be awaited in order, there is some specific behavior that arises, which may affect the semantics of your program. See `https://github.com/AleoNet/snarkVM/issues/2570` for more context."""] diff --git a/tests/expectations/compiler/futures/future_parameter_fail.out b/tests/expectations/compiler/futures/future_parameter_fail.out new file mode 100644 index 0000000000..57ebf24fdb --- /dev/null +++ b/tests/expectations/compiler/futures/future_parameter_fail.out @@ -0,0 +1,19 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ETYC0372116]: Futures may only appear as parameters to async functions. + --> compiler-test:4:28 + | + 4 | async transition first(x: Future) -> Future { + | ^^^^^^^^^ +Error [ETYC0372116]: Futures may only appear as parameters to async functions. + --> compiler-test:8:23 + | + 8 | transition second(x: Future) -> u8 { + | ^^^^^^^^^ +Error [ETYC0372116]: Futures may only appear as parameters to async functions. + --> compiler-test:12:20 + | + 12 | function third(x: Future) -> u8 { + | ^^^^^^^^^ +"""] diff --git a/tests/expectations/compiler/futures/misplaced_future_fail.out b/tests/expectations/compiler/futures/misplaced_future_fail.out new file mode 100644 index 0000000000..925066dc8b --- /dev/null +++ b/tests/expectations/compiler/futures/misplaced_future_fail.out @@ -0,0 +1,18 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ESAZ0374005]: A future may not be used in this way + --> compiler-test:10:9 + | + 10 | child.aleo/foo(); + | ^^^^^^^^^^^^^^^^ + | + = Futures should be created, assigned to a variable, and consumed without being moved or reassigned. +Error [ESAZ0374005]: A future may not be used in this way + --> compiler-test:12:9 + | + 12 | child.aleo/boo(); + | ^^^^^^^^^^^^^^^^ + | + = Futures should be created, assigned to a variable, and consumed without being moved or reassigned. +"""] diff --git a/tests/expectations/compiler/futures/nested.out b/tests/expectations/compiler/futures/nested.out index b4d7cc2ee9..b08dd78da1 100644 --- a/tests/expectations/compiler/futures/nested.out +++ b/tests/expectations/compiler/futures/nested.out @@ -24,7 +24,7 @@ finalize main_dep: input r1 as u32.public; set r1 into Yo[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "837e6e9f7a93af9d92cb90208d54a4e55693939bccddf588c94102805a600ec2", type_checked_symbol_table = "c33e10eabb14d2d0dc8a7ffd7370dcda4d0467b46dc00d9a526c0cf7fc373906", unrolled_symbol_table = "c33e10eabb14d2d0dc8a7ffd7370dcda4d0467b46dc00d9a526c0cf7fc373906", initial_ast = "64089bd9ecc0ab9ce224328c7ba9b2ece577f585b2417b48eb0883ec8cec304c", unrolled_ast = "450bb73f7249477591a716a45cbd0fbb332d98a8765b2804ca919488cbc7e1bf", ssa_ast = "d445e67098ada41b7ada11f69a07acf107d1b8e6ab052e7bb3e8d1b6530c4371", flattened_ast = "b3e5d4d940f433b770b6acdd85c2a5f1de7327617f71783b75108c2a515c12a1", destructured_ast = "36361778b1d97dcde52548c1e082ad7382dbe6e6be4fd6be1fdc73bb213d0016", inlined_ast = "b358e9fa7f234ae1154b48cbd83c3e2029c1a83c5298470035729f78537e03a6", dce_ast = "4d6d5c792f8d7a9d83e0c1bee6efcf24470e92fd4746aa7a9d0afabc93ec8a19", bytecode = """ + { initial_symbol_table = "354ff959a0573f0946a92f8626af3fd47b9ee3165f9b3971d60040e00d1e078f", type_checked_symbol_table = "f532890dcb4613a7f23287cebd8594b9f053e1d80ceba9ca9566c40925df460b", unrolled_symbol_table = "f532890dcb4613a7f23287cebd8594b9f053e1d80ceba9ca9566c40925df460b", initial_ast = "64089bd9ecc0ab9ce224328c7ba9b2ece577f585b2417b48eb0883ec8cec304c", unrolled_ast = "450bb73f7249477591a716a45cbd0fbb332d98a8765b2804ca919488cbc7e1bf", ssa_ast = "d445e67098ada41b7ada11f69a07acf107d1b8e6ab052e7bb3e8d1b6530c4371", flattened_ast = "b3e5d4d940f433b770b6acdd85c2a5f1de7327617f71783b75108c2a515c12a1", destructured_ast = "36361778b1d97dcde52548c1e082ad7382dbe6e6be4fd6be1fdc73bb213d0016", inlined_ast = "b358e9fa7f234ae1154b48cbd83c3e2029c1a83c5298470035729f78537e03a6", dce_ast = "4d6d5c792f8d7a9d83e0c1bee6efcf24470e92fd4746aa7a9d0afabc93ec8a19", bytecode = """ import test_dep.aleo; program test.aleo; @@ -65,7 +65,7 @@ finalize main: add r0[0u32] r1[0u32] into r5; set r5 into ayo[1u32]; """, errors = "", warnings = """ -Warning [WTYC0372000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. +Warning [WSAZ0374000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. --> compiler-test:17:5 | 17 | async function finalize_main(f: Future, f2: Future, a: u32) { @@ -86,7 +86,7 @@ Warning [WTYC0372000]: Not all paths through the function await all futures. 2/4 | ^ | = Ex: `f.await()` to await a future. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.""" }, - { initial_symbol_table = "11d73259b527776fa2019508fa961ca24850cc2bd0fbbcebfb7310c565289560", type_checked_symbol_table = "fb91e05612819b16dc6a1fb37cd80f776918dc1f502feca4d9428f42dc21754d", unrolled_symbol_table = "fb91e05612819b16dc6a1fb37cd80f776918dc1f502feca4d9428f42dc21754d", initial_ast = "05de2b0dcfd85ec6446f4507492e26b2093e771f44c497f92a24d6fff5e8c864", unrolled_ast = "4f09dae0678393afc3cbc5592159df83ca22b947084d3c8e779281724d07a2ca", ssa_ast = "0cb5c531ad471909089716ef6c7382fb3fcbb82dafb6edef541e4f7cff4fb8ba", flattened_ast = "46d54d4d9fe36538d34ac306780262ee1f54a6141aa2281ef7ae74ffcf4dddcf", destructured_ast = "88653b95656b6f56872d7ea452491322e4c122909879b72856b891c474aa8342", inlined_ast = "0f81029815dec13a526530eeea0e92e6eb61313421ce5a7b46ed3739d62beaf6", dce_ast = "6b852bcf601b323678eea14e096f49c72f8800d18ec811b00c31817daf630d63", bytecode = """ + { initial_symbol_table = "539fd89c0dea3feb2b2b3844532aacc032658f2546cd011dd1aa257eb18dc0af", type_checked_symbol_table = "869d309eda62dec11d14555672b7946d52fa30a015f510aeb8dcf77224f4125d", unrolled_symbol_table = "869d309eda62dec11d14555672b7946d52fa30a015f510aeb8dcf77224f4125d", initial_ast = "05de2b0dcfd85ec6446f4507492e26b2093e771f44c497f92a24d6fff5e8c864", unrolled_ast = "4f09dae0678393afc3cbc5592159df83ca22b947084d3c8e779281724d07a2ca", ssa_ast = "0cb5c531ad471909089716ef6c7382fb3fcbb82dafb6edef541e4f7cff4fb8ba", flattened_ast = "46d54d4d9fe36538d34ac306780262ee1f54a6141aa2281ef7ae74ffcf4dddcf", destructured_ast = "88653b95656b6f56872d7ea452491322e4c122909879b72856b891c474aa8342", inlined_ast = "0f81029815dec13a526530eeea0e92e6eb61313421ce5a7b46ed3739d62beaf6", dce_ast = "6b852bcf601b323678eea14e096f49c72f8800d18ec811b00c31817daf630d63", bytecode = """ import test_dep.aleo; import test.aleo; program wrapper.aleo; @@ -109,7 +109,7 @@ finalize main: await r1; await r2; """, errors = "", warnings = "" }, - { initial_symbol_table = "04a3a0ccbf4ed061d19da4e624725caff0e64ac838498cbd09df865f4f9044f2", type_checked_symbol_table = "69550e476553614e01dd39df0b3a8f682556cdf76982503af0e6a77d4916e027", unrolled_symbol_table = "69550e476553614e01dd39df0b3a8f682556cdf76982503af0e6a77d4916e027", initial_ast = "bf4f5dac2e3cac6f6c8b117a93b7bc9a4b9d31f66b3b0d946866da23003e6a69", unrolled_ast = "a1786c230d46f3b207f118aaaaea373cd1d9935aa7e63b99e403a8faf36df2fe", ssa_ast = "82581ca24afcd79d3e3c1346009981d4a9d3d227afc0540707b6c315ecdce107", flattened_ast = "2ff2d69c6199a5c70a8ffb96d8dc0529f6f1fbf631a1f690169d2d9162e91689", destructured_ast = "8da4c7c91fabf5edb6768e616f223e574b3415c848321f66ad9e587b76259210", inlined_ast = "a740025e070d37bd22f264e37dfd6802eb9e1b10c12c928a08acd14fbe9043d6", dce_ast = "e127a5223a49f123398009b927e96ebb44f266df7271feb7b1ff5f7f748e6ff5", bytecode = """ + { initial_symbol_table = "07bdfd403caa73ec17903694bb68a93e108011dc9d77e555fd2815e4da90a1de", type_checked_symbol_table = "0deaa782322a693add2dd04fce9b1bfc4ea73654172e59ad9c2da0e04d995820", unrolled_symbol_table = "0deaa782322a693add2dd04fce9b1bfc4ea73654172e59ad9c2da0e04d995820", initial_ast = "bf4f5dac2e3cac6f6c8b117a93b7bc9a4b9d31f66b3b0d946866da23003e6a69", unrolled_ast = "a1786c230d46f3b207f118aaaaea373cd1d9935aa7e63b99e403a8faf36df2fe", ssa_ast = "82581ca24afcd79d3e3c1346009981d4a9d3d227afc0540707b6c315ecdce107", flattened_ast = "2ff2d69c6199a5c70a8ffb96d8dc0529f6f1fbf631a1f690169d2d9162e91689", destructured_ast = "8da4c7c91fabf5edb6768e616f223e574b3415c848321f66ad9e587b76259210", inlined_ast = "a740025e070d37bd22f264e37dfd6802eb9e1b10c12c928a08acd14fbe9043d6", dce_ast = "e127a5223a49f123398009b927e96ebb44f266df7271feb7b1ff5f7f748e6ff5", bytecode = """ import test_dep.aleo; import test.aleo; import wrapper.aleo; diff --git a/tests/expectations/compiler/futures/non_async_after_complex_async.out b/tests/expectations/compiler/futures/non_async_after_complex_async.out new file mode 100644 index 0000000000..15289b4f3f --- /dev/null +++ b/tests/expectations/compiler/futures/non_async_after_complex_async.out @@ -0,0 +1,58 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "6cd6500abf0e8651ba271e6aa7703e19da9c183c54cc5304ecbc59bf9336fce8", type_checked_symbol_table = "75075f8b61db1c78b075b1b7b042f351d4b406457277b05e2f4f531e19eed0d0", unrolled_symbol_table = "75075f8b61db1c78b075b1b7b042f351d4b406457277b05e2f4f531e19eed0d0", initial_ast = "242372b2feb8f6e98c77aa4bb4a891130a5ceb5c07ec724cce9c4dde4fd49208", unrolled_ast = "242372b2feb8f6e98c77aa4bb4a891130a5ceb5c07ec724cce9c4dde4fd49208", ssa_ast = "2787cc66971dcd0423755380ff9f4adc0a4ec03825407ff9fda2f2c731179c75", flattened_ast = "cf44ace46cbe7e60114c27ac8c6fbbdd2097f599134bdfe37822f3d4093b9f8a", destructured_ast = "72f55596cce2dab4e8fd8f2a8eff12931cd159f6eb5585dc13334ff20ad36f87", inlined_ast = "fc67b7df09f254e6c1fd19bc3005be38ce306eea369efa384ecba6b38639a638", dce_ast = "fc67b7df09f254e6c1fd19bc3005be38ce306eea369efa384ecba6b38639a638", bytecode = """ +program inner.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function inner: + input r0 as u32.private; + async inner r0 into r1; + output r1 as inner.aleo/inner.future; + +finalize inner: + input r0 as u32.public; + set r0 into foo[0u32]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "386cb81f5601365a2c421d1a78a1bcfaa78fd6c3172666c6a9d1c49b02233d6e", type_checked_symbol_table = "7ad49c58b4cdb6eadc16c5c55f66dbc6dacde2fb624e91b374e60ce5ad8b2425", unrolled_symbol_table = "7ad49c58b4cdb6eadc16c5c55f66dbc6dacde2fb624e91b374e60ce5ad8b2425", initial_ast = "f25d550819ceefd793a9711f96460c97c78ca1aeb7da21d3105dbcb06020ea8f", unrolled_ast = "b04609dab316adc708b481faf5241c5a67cb9fd3cbfa21531788b6bc01c9c8e6", ssa_ast = "e3f3822d24d05d2b393d2311ad1a115c69b816050fbd5c5b095f2713573d2030", flattened_ast = "ee6255715b49f761e5f12a23d7e25b968eb29821f9c2c6ee91a040ab7b7a0a0c", destructured_ast = "3d349126a6c352d726734cfcdd72affbe58584734f65c77e819ee1b171a135d3", inlined_ast = "cd9854c1633542668ffe6bf0e1f00de68f99ed21fddae01d161f2887e2e18180", dce_ast = "cd9854c1633542668ffe6bf0e1f00de68f99ed21fddae01d161f2887e2e18180", bytecode = """ +import inner.aleo; +program mid.aleo; + +function mid: + input r0 as u32.private; + call inner.aleo/inner 0u32 into r1; + call inner.aleo/inner 1u32 into r2; + async mid r2 r1 into r3; + output r3 as mid.aleo/mid.future; + +finalize mid: + input r0 as inner.aleo/inner.future; + input r1 as inner.aleo/inner.future; + await r0; + await r1; + +function dummy: +""", errors = "", warnings = "" }, + { initial_symbol_table = "f662e7a456e22a2d1b4b42fa3221de16e5671edc25c1d0b20610dfd0ab55c579", type_checked_symbol_table = "dc5d562dfebd056cf956fd6c299ad70e99500786ddbe67c0f858bd09259d5d5f", unrolled_symbol_table = "dc5d562dfebd056cf956fd6c299ad70e99500786ddbe67c0f858bd09259d5d5f", initial_ast = "5df535c99668c958f5649f0e5d24ae951023b165941ded5e9df3665a1c4bdd7d", unrolled_ast = "57200953ba2c83408d2dbc51c10e7c01143b6ed3f3dcf96616e7072ac99e2152", ssa_ast = "223b30eb9d800a33aab6105dea3b4dde8bc3435673b1be29ab4268e944406384", flattened_ast = "2ed49413ee703e36ee432a5f271fecb3327be45039477ee9bc2bc6ef77e25f41", destructured_ast = "26b81f6ad2dab39e3a9a5e1d73ebff1f5a165f794897fd770ed0f7927a34bf95", inlined_ast = "5dc7a113088ff1f7682c9b5618e381baa011609a7f2e1a876272c1fd79b6dfd3", dce_ast = "5dc7a113088ff1f7682c9b5618e381baa011609a7f2e1a876272c1fd79b6dfd3", bytecode = """ +import inner.aleo; +import mid.aleo; +program outer.aleo; + +function outer: + input r0 as u32.private; + call mid.aleo/mid 0u32 into r1; + call mid.aleo/mid 1u32 into r2; + call mid.aleo/dummy; + async outer r1 r2 into r3; + output r3 as outer.aleo/outer.future; + +finalize outer: + input r0 as mid.aleo/mid.future; + input r1 as mid.aleo/mid.future; + await r0; + await r1; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/futures/non_async_before_async.out b/tests/expectations/compiler/futures/non_async_before_async.out new file mode 100644 index 0000000000..45ddfae933 --- /dev/null +++ b/tests/expectations/compiler/futures/non_async_before_async.out @@ -0,0 +1,52 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "16e119df6ee0ccf0c2a2eaa55c81867664630b3468627f778098d1ff0c8cd5fa", type_checked_symbol_table = "f4fb482aeb7abb52f5ca3aae4f42a53b98f7403ec1d2c488996efb1ff11f213b", unrolled_symbol_table = "f4fb482aeb7abb52f5ca3aae4f42a53b98f7403ec1d2c488996efb1ff11f213b", initial_ast = "5a07ca97b09d15a8549692408a74444dc346fbd3c8d08b1e3fa4dc60d2a0a05c", unrolled_ast = "5a07ca97b09d15a8549692408a74444dc346fbd3c8d08b1e3fa4dc60d2a0a05c", ssa_ast = "50c140777b792e917824e9021e722774b3e037f2d97c9d0c59a14b2c5088c98b", flattened_ast = "f6f59f2f6e0f8b8c933ecdb0d2360cd9c53a2ba10486c2935f72140b48b68927", destructured_ast = "f5851f2b9ebf08030bf8a9778e0c52c85f61b0a32f3eed802897da99c48a29bd", inlined_ast = "8051736585fdd624f74052e44368eef86e1a7e9533152406503a5737939c4e1e", dce_ast = "8051736585fdd624f74052e44368eef86e1a7e9533152406503a5737939c4e1e", bytecode = """ +program test.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function main_inner: + input r0 as u32.public; + input r1 as u32.public; + async main_inner r0 r1 into r2; + output r2 as test.aleo/main_inner.future; + +finalize main_inner: + input r0 as u32.public; + input r1 as u32.public; + set r1 into foo[r0]; + +function baz: + input r0 as u32.private; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; +""", errors = "", warnings = "" }, + { initial_symbol_table = "42d242bf08b1ff58a2640d65713dcb4783a7b59c9fe35af1863b819c33e6b9f2", type_checked_symbol_table = "f28e9e26827f070928d22fcd6ab9e070dec65d97d4e6b3333959e75ca91a9953", unrolled_symbol_table = "f28e9e26827f070928d22fcd6ab9e070dec65d97d4e6b3333959e75ca91a9953", initial_ast = "959ef8008a2cb837e0f067c6954356034892bed1a8dcda72224f08e360a1c791", unrolled_ast = "f5a2e49f992ab80a104ceae85eb1c0a34c094ee9012a7ca9d5d6406f050515c4", ssa_ast = "a70cc7f42605c3f72904723c4c8237e3eada556f617588f9bde00e07d58c2cd2", flattened_ast = "02f4563ed934754a6db1939c5b3017356e63e4deaeafb66091c46500a534d8ab", destructured_ast = "56153136bc984183c654e6a4fcc8bd607c801b0a92a502d4c008360e6b816c54", inlined_ast = "573187f8b898181cc4f1c59ca1dc960b4767fa818591ccd3189eee82fc698155", dce_ast = "573187f8b898181cc4f1c59ca1dc960b4767fa818591ccd3189eee82fc698155", bytecode = """ +import test.aleo; +program basic.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call test.aleo/baz r0 r1 into r2; + assert.eq r2 1u32; + call test.aleo/main_inner 0u32 0u32 into r3; + call test.aleo/baz r0 r1 into r4; + assert.eq r4 1u32; + call test.aleo/main_inner 1u32 1u32 into r5; + call test.aleo/baz r0 r1 into r6; + assert.eq r6 1u32; + async main r3 r5 into r7; + output r7 as basic.aleo/main.future; + +finalize main: + input r0 as test.aleo/main_inner.future; + input r1 as test.aleo/main_inner.future; + await r0; + await r1; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/futures/non_async_before_complex_async.out b/tests/expectations/compiler/futures/non_async_before_complex_async.out new file mode 100644 index 0000000000..f61b1045e3 --- /dev/null +++ b/tests/expectations/compiler/futures/non_async_before_complex_async.out @@ -0,0 +1,25 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ESAZ0374004]: The call to mid will result in failed executions on-chain. + --> compiler-test:8:26 + | + 8 | let f1: Future = mid.aleo/mid(0u32); + | ^^^^^^^^^^^^^^^^^^ + | + = There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Error [ESAZ0374004]: The call to mid will result in failed executions on-chain. + --> compiler-test:9:26 + | + 9 | let f2: Future = mid.aleo/mid(1u32); + | ^^^^^^^^^^^^^^^^^^ + | + = There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +Error [ESAZ0374004]: The call to mid will result in failed executions on-chain. + --> compiler-test:17:26 + | + 17 | let f2: Future = mid.aleo/mid(1u32); + | ^^^^^^^^^^^^^^^^^^ + | + = There is a subtle error that occurs if an async transition call follows a non-async transition call, and the async call returns a `Future` that itself takes a `Future` as an input. See See `https://github.com/AleoNet/snarkVM/issues/2570` for more context. +"""] diff --git a/tests/expectations/compiler/futures/partial_type_specification.out b/tests/expectations/compiler/futures/partial_type_specification.out index 6c24e52e4d..d7df5ae590 100644 --- a/tests/expectations/compiler/futures/partial_type_specification.out +++ b/tests/expectations/compiler/futures/partial_type_specification.out @@ -34,7 +34,7 @@ function main_dep_2: finalize main_dep_2: set 1u32 into Yo[1u32]; """, errors = "", warnings = "" }, - { initial_symbol_table = "1a537ce4873945cd8969e08fd2440d3d9dbf4175306e7a60a18f59305958366e", type_checked_symbol_table = "3c670b67da9da6028e642d487a1382f3de1b554c8c0d51fc531b71e36b5cdef5", unrolled_symbol_table = "3c670b67da9da6028e642d487a1382f3de1b554c8c0d51fc531b71e36b5cdef5", initial_ast = "bcfa98eafaf355e7313773fa4340b88d2530e3d2b279252fc1117327de42d77a", unrolled_ast = "01a9f5e11f5749b408619a513bf7f9eececfd83f9f87c883fcd8db53440babab", ssa_ast = "b6da9c41019a2af6cd137e29fe7b5041cc13a45d574b920101a69f7093c58980", flattened_ast = "7bddc7f16b5ef5baef1fc50ac2f45767844d05fc0de797d267c77306bc586dc5", destructured_ast = "df2c950dd52d4094ef1f2d364aa6dd57020f7ca431ead915353c2c33482ee05d", inlined_ast = "7dd0bb6eee84d038c01e43a8c7fdfd38ec3cbb269bf4990078a49e5202fe177e", dce_ast = "4378a2b09abc850959d98704efb7ec28bd6ad7962cc4ec761e26e57400cec8a0", bytecode = """ + { initial_symbol_table = "1efe1fca5533564cb10347a951f503433d841d861e7eae035d3a86912ac5e475", type_checked_symbol_table = "5c2b623a5f1f09b4bd46ab77d25c40a1966d596f6cc41f7c4d93f883094ad185", unrolled_symbol_table = "5c2b623a5f1f09b4bd46ab77d25c40a1966d596f6cc41f7c4d93f883094ad185", initial_ast = "bcfa98eafaf355e7313773fa4340b88d2530e3d2b279252fc1117327de42d77a", unrolled_ast = "01a9f5e11f5749b408619a513bf7f9eececfd83f9f87c883fcd8db53440babab", ssa_ast = "b6da9c41019a2af6cd137e29fe7b5041cc13a45d574b920101a69f7093c58980", flattened_ast = "7bddc7f16b5ef5baef1fc50ac2f45767844d05fc0de797d267c77306bc586dc5", destructured_ast = "df2c950dd52d4094ef1f2d364aa6dd57020f7ca431ead915353c2c33482ee05d", inlined_ast = "7dd0bb6eee84d038c01e43a8c7fdfd38ec3cbb269bf4990078a49e5202fe177e", dce_ast = "4378a2b09abc850959d98704efb7ec28bd6ad7962cc4ec761e26e57400cec8a0", bytecode = """ import test_dep.aleo; program test.aleo; @@ -75,7 +75,7 @@ finalize main: add r0[0u32] r1[0u32] into r5; set r5 into ayo[1u32]; """, errors = "", warnings = """ -Warning [WTYC0372000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. +Warning [WSAZ0374000]: Not all paths through the function await all futures. 2/4 paths contain at least one future that is never awaited. --> compiler-test:17:5 | 17 | async function finalize_main(f: Future, f2: Future, a: u32) { @@ -96,7 +96,7 @@ Warning [WTYC0372000]: Not all paths through the function await all futures. 2/4 | ^ | = Ex: `f.await()` to await a future. Remove this warning by including the `--disable-conditional-branch-type-checking` flag.""" }, - { initial_symbol_table = "04f7d3a44d791763aec79b596224c653e682ab928bc0cba71a1cd6282198e885", type_checked_symbol_table = "d9d3363d1049a924bbae356d0f90ac3c9bfca7f6ae5ba51ad915d66e9d0b9a1e", unrolled_symbol_table = "d9d3363d1049a924bbae356d0f90ac3c9bfca7f6ae5ba51ad915d66e9d0b9a1e", initial_ast = "856e56d95eaf14f6e9241001763546b7d982402ac87521e2ec3b7ea476764692", unrolled_ast = "75b69748ca1e534c95cf084164773d471f51537b50b2d517dc4be26dddb06e1b", ssa_ast = "6d38bf225e9cf5af37b9d6c595c2973ec31a32d227ca65cb590d27400d442780", flattened_ast = "65fb4138701cad86a5fcd7e024645e833aeb6e88b3ea2a3a6b69269fd1d77620", destructured_ast = "85a81c23da7e97b057ddf4ef71f375781e1dfcb90d656d694a5aa0f0c176b497", inlined_ast = "a1b2367575e170a79ace2ac7ff071bc3c770476b37ee149310c3b2cfe67b1c7f", dce_ast = "f46fa7963b327b9c75c9f7a7569e350d7f62c21964cb5df140cd2186c2043697", bytecode = """ + { initial_symbol_table = "cd14d130e4f9b9b92f731cb8caee6237ae4477573ea636af8775e5e02966390e", type_checked_symbol_table = "865ddd67b06a908cdb65e83c1bca76bc5c0d860f89ef6b2f354fd2470c94b188", unrolled_symbol_table = "865ddd67b06a908cdb65e83c1bca76bc5c0d860f89ef6b2f354fd2470c94b188", initial_ast = "856e56d95eaf14f6e9241001763546b7d982402ac87521e2ec3b7ea476764692", unrolled_ast = "75b69748ca1e534c95cf084164773d471f51537b50b2d517dc4be26dddb06e1b", ssa_ast = "6d38bf225e9cf5af37b9d6c595c2973ec31a32d227ca65cb590d27400d442780", flattened_ast = "65fb4138701cad86a5fcd7e024645e833aeb6e88b3ea2a3a6b69269fd1d77620", destructured_ast = "85a81c23da7e97b057ddf4ef71f375781e1dfcb90d656d694a5aa0f0c176b497", inlined_ast = "a1b2367575e170a79ace2ac7ff071bc3c770476b37ee149310c3b2cfe67b1c7f", dce_ast = "f46fa7963b327b9c75c9f7a7569e350d7f62c21964cb5df140cd2186c2043697", bytecode = """ import test_dep.aleo; import test.aleo; program wrapper.aleo; @@ -119,7 +119,7 @@ finalize main: await r1; await r2; """, errors = "", warnings = "" }, - { initial_symbol_table = "11c1000ce2f1774ad382af12ba51e8b55d5a98ee0da67cb8620e686c1fcaebb1", type_checked_symbol_table = "9f27eb3f177ceb81d9b14cc85c07b7198eb67d0ee806c04cbbff1cfb18b997ab", unrolled_symbol_table = "9f27eb3f177ceb81d9b14cc85c07b7198eb67d0ee806c04cbbff1cfb18b997ab", initial_ast = "575e251f07e552c917ab36bc9877b13dd1638651c4023ade20701dd2a5fe27ff", unrolled_ast = "2a4969ad315e900b5a3f1eecd4e6508dc6946fb5f6c3861ee793961ce6bcc203", ssa_ast = "4a00e3d36cdd4ff4be1fc6a389aaf17cfb02b6c54fa84276fb5be66b8a78b124", flattened_ast = "885c5f8145aa1a82e5fe41abbabae12cbd15eb014b333b246c6c5401b5b6bfea", destructured_ast = "f3b5b961a498f9befec85b69b3012145a6e97774d37a8c8e354ec4e5eeb64f84", inlined_ast = "2bf37fc499b3eca18c8227e61f69f730d36e755d7879dde13bb9161936bafbfc", dce_ast = "390391c2098cf6a910eeec98fc92fdea31303a84a1d6fd6673c8dbd9d20180de", bytecode = """ + { initial_symbol_table = "29aefa9acac4488265303e8b3bd52a434ae4824211df7919f6b63cfe6e1ec51f", type_checked_symbol_table = "056ebc268a7f7adf3aa336e21ded11d39f2c1eca00a1b675fe1622c7b92704ad", unrolled_symbol_table = "056ebc268a7f7adf3aa336e21ded11d39f2c1eca00a1b675fe1622c7b92704ad", initial_ast = "575e251f07e552c917ab36bc9877b13dd1638651c4023ade20701dd2a5fe27ff", unrolled_ast = "2a4969ad315e900b5a3f1eecd4e6508dc6946fb5f6c3861ee793961ce6bcc203", ssa_ast = "4a00e3d36cdd4ff4be1fc6a389aaf17cfb02b6c54fa84276fb5be66b8a78b124", flattened_ast = "885c5f8145aa1a82e5fe41abbabae12cbd15eb014b333b246c6c5401b5b6bfea", destructured_ast = "f3b5b961a498f9befec85b69b3012145a6e97774d37a8c8e354ec4e5eeb64f84", inlined_ast = "2bf37fc499b3eca18c8227e61f69f730d36e755d7879dde13bb9161936bafbfc", dce_ast = "390391c2098cf6a910eeec98fc92fdea31303a84a1d6fd6673c8dbd9d20180de", bytecode = """ import test_dep.aleo; import test.aleo; import wrapper.aleo; diff --git a/tests/expectations/compiler/futures/pass_in_out_of_order.out b/tests/expectations/compiler/futures/pass_in_out_of_order.out new file mode 100644 index 0000000000..5a437678df --- /dev/null +++ b/tests/expectations/compiler/futures/pass_in_out_of_order.out @@ -0,0 +1,40 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "c05e8ce7b527a0192d65e50963afd67a1fe92c837da97a5299c22af29f0275dd", type_checked_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", unrolled_symbol_table = "8b2f1b5c22979924b6e08419e57fb515c05d3ce6932a9d1c1533ed4a2927aec2", initial_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", unrolled_ast = "713c85bcb7d5fef43e4626a09e4fe3858f76de6025fcae475cb0398c26b5d123", ssa_ast = "5e2a213bd10e28dad299e0e48ac3336484fd2ffd894eeb63d15d67e6af65195e", flattened_ast = "11828763a38326b604155e1074699e6ca85205fbc83167d554624d0fe5d8bb2b", destructured_ast = "83c9eaa9aef53de6143980c9e001e59da64c291a7b2aa0693139b868339e589c", inlined_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", dce_ast = "dda1aade2a50a9f25571004c8a68e3ff90efadd6a567ce25b1edbbd2e82b59d7", bytecode = """ +program test.aleo; + +mapping foo: + key as u32.public; + value as u32.public; + +function main_inner: + input r0 as u32.public; + input r1 as u32.public; + async main_inner r0 r1 into r2; + output r2 as test.aleo/main_inner.future; + +finalize main_inner: + input r0 as u32.public; + input r1 as u32.public; + set r1 into foo[r0]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "748f1d6d760d63c1eb80d518379d99323e13132bf77520f8442534c79c64895b", type_checked_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", unrolled_symbol_table = "3c5970cf8251ed27741614785eb8871b1df48e9437da1cc425c9c85448f63ed3", initial_ast = "0614c4d1a71ead028505adbca60be45e21f8dfff3cac5c2d5825fdccb742599a", unrolled_ast = "3937cfdaeaeb310aed147b646aa466e86c2be135f08c34caba4c442d9287cc00", ssa_ast = "085e51180733890a2fa69926ff9aff285cbe30fbfb212363b7cde717f8fdb726", flattened_ast = "f8c1203193c1ede409ac0f76bdd99dcc3030182b0e04fcde92b21588e2130806", destructured_ast = "c2f3abb79321f123b41bf581e21b8abe73f799f1e3d4431fa68849d7ee4f2053", inlined_ast = "501b7b97d2873c583d12763895fa3d01a7fac7a7554d91b71850d9f8060413c8", dce_ast = "501b7b97d2873c583d12763895fa3d01a7fac7a7554d91b71850d9f8060413c8", bytecode = """ +import test.aleo; +program basic.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call test.aleo/main_inner 0u32 0u32 into r2; + call test.aleo/main_inner 1u32 1u32 into r3; + async main r3 r2 into r4; + output r4 as basic.aleo/main.future; + +finalize main: + input r0 as test.aleo/main_inner.future; + input r1 as test.aleo/main_inner.future; + await r0; + await r1; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/compiler/futures/simple.out b/tests/expectations/compiler/futures/simple.out index d8dd82936a..83a01d9e62 100644 --- a/tests/expectations/compiler/futures/simple.out +++ b/tests/expectations/compiler/futures/simple.out @@ -19,7 +19,7 @@ function main_inner: finalize main_inner: set 1u32 into foo[1u32]; """, errors = "", warnings = "" }, - { initial_symbol_table = "e68fd2fbfc3ff3832375c1c2df1e6a67787480498938fc77e766ca07ae751992", type_checked_symbol_table = "a3dbe89fee3c01d1a1798775bd34ee5e9a160d9a31bc223cf8d949ad08310b43", unrolled_symbol_table = "a3dbe89fee3c01d1a1798775bd34ee5e9a160d9a31bc223cf8d949ad08310b43", initial_ast = "90315edede362afca47bb3f8c861ab8bbbdb049ea56db7ebbbf8f20ce60aeb4a", unrolled_ast = "6541d8c338b4eeb027aedd7c9151f3eac30d61ab2986d22a008ef5bd4a67ffc7", ssa_ast = "80086e21c3779f9da4b57c755eedf9132709a1edc63644ef4ec574ce047b076f", flattened_ast = "a9988b6cbd9cb03bc49e6850084531888e0cc04e456496fe7eff390812d39611", destructured_ast = "a94ba575cc25982052a729a8a1b8fa3560a0043b305cf4dede91d17a71202fcb", inlined_ast = "7a6d98c84ce9a50bd944f11bca3d98f8262ab57b55fcc7f15537650b3d4bc6ef", dce_ast = "ef3d06f7a3ed3bba09c3fda4378aaa2f700384fc28e5d8c3751633bbc03f9f4e", bytecode = """ + { initial_symbol_table = "6c555c940588a42fbc942c59adb0a7d43f9423b5e08eea2ab43581fb8d979a41", type_checked_symbol_table = "bda27ed9363abf086753d5bb203fa4c10c8fb4168e6759f58e1eef1c5ae48dbf", unrolled_symbol_table = "bda27ed9363abf086753d5bb203fa4c10c8fb4168e6759f58e1eef1c5ae48dbf", initial_ast = "90315edede362afca47bb3f8c861ab8bbbdb049ea56db7ebbbf8f20ce60aeb4a", unrolled_ast = "6541d8c338b4eeb027aedd7c9151f3eac30d61ab2986d22a008ef5bd4a67ffc7", ssa_ast = "80086e21c3779f9da4b57c755eedf9132709a1edc63644ef4ec574ce047b076f", flattened_ast = "a9988b6cbd9cb03bc49e6850084531888e0cc04e456496fe7eff390812d39611", destructured_ast = "a94ba575cc25982052a729a8a1b8fa3560a0043b305cf4dede91d17a71202fcb", inlined_ast = "7a6d98c84ce9a50bd944f11bca3d98f8262ab57b55fcc7f15537650b3d4bc6ef", dce_ast = "ef3d06f7a3ed3bba09c3fda4378aaa2f700384fc28e5d8c3751633bbc03f9f4e", bytecode = """ import test.aleo; program basic.aleo; diff --git a/tests/expectations/compiler/mappings/read_external_mapping.out b/tests/expectations/compiler/mappings/read_external_mapping.out index 1e39ac93a4..5d999a9f76 100644 --- a/tests/expectations/compiler/mappings/read_external_mapping.out +++ b/tests/expectations/compiler/mappings/read_external_mapping.out @@ -24,7 +24,7 @@ finalize unregister: input r0 as address.public; set false into users[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "23d4f67793776c110bfd4cc47d98dedde4495edb453c82f6b06718a8cdbc7f6d", type_checked_symbol_table = "f8c6d89c3ff7316d9a2e391c1a0d6c7f3f4ab2f45109b0dbd58b6ff424d854dd", unrolled_symbol_table = "f8c6d89c3ff7316d9a2e391c1a0d6c7f3f4ab2f45109b0dbd58b6ff424d854dd", initial_ast = "9cc519cc416b2f54ecf753c541196b337f359d42616e4f38b8d9a5a86746de41", unrolled_ast = "4f5beff4969ba9db8b429435d2a6a6133eed2e8718564073fefa76ed4db76381", ssa_ast = "012d0c07475a7e03d3898338aa2a91b56d77032978437b17c9337a5001ae5249", flattened_ast = "e391d1d2c6731ec8961afe91d8fa94fb9edb091b892ddecfa48ce3f5a6febe8e", destructured_ast = "26f202a3d6a24f0af49542d0f2c29c635314073b2d52ede163d3ab5e5bcc86fa", inlined_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", dce_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", bytecode = """ + { initial_symbol_table = "ad44035dc7b8fd88c75dae7732786222905037adcba313f1fa11964e3b6a3a18", type_checked_symbol_table = "2ae36cf87aea22ac6069d3d750ea3ed2b2ac03c0f9ee75f957edf106095aa036", unrolled_symbol_table = "2ae36cf87aea22ac6069d3d750ea3ed2b2ac03c0f9ee75f957edf106095aa036", initial_ast = "9cc519cc416b2f54ecf753c541196b337f359d42616e4f38b8d9a5a86746de41", unrolled_ast = "4f5beff4969ba9db8b429435d2a6a6133eed2e8718564073fefa76ed4db76381", ssa_ast = "012d0c07475a7e03d3898338aa2a91b56d77032978437b17c9337a5001ae5249", flattened_ast = "e391d1d2c6731ec8961afe91d8fa94fb9edb091b892ddecfa48ce3f5a6febe8e", destructured_ast = "26f202a3d6a24f0af49542d0f2c29c635314073b2d52ede163d3ab5e5bcc86fa", inlined_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", dce_ast = "72e4121a823f91aeeb5b8433f03f07943d174353d55f58a3aae111bc1bab0798", bytecode = """ import registry.aleo; program relay.aleo; diff --git a/tests/expectations/compiler/structs/external_struct_in_async_function.out b/tests/expectations/compiler/structs/external_struct_in_async_function.out index a384590a60..b225b5672a 100644 --- a/tests/expectations/compiler/structs/external_struct_in_async_function.out +++ b/tests/expectations/compiler/structs/external_struct_in_async_function.out @@ -17,7 +17,7 @@ finalize init: input r0 as TestStruct.public; assert.eq 0u32 0u32; """, errors = "", warnings = "" }, - { initial_symbol_table = "c9f26fb8c18222d0819c01087efc4aae88ea8944dec03710d94c38c24e0d077a", type_checked_symbol_table = "ed3db1e139955da3a7df17d8abdf36ddcabf05e2cb0cc6af012cce4a4fc67fae", unrolled_symbol_table = "ed3db1e139955da3a7df17d8abdf36ddcabf05e2cb0cc6af012cce4a4fc67fae", initial_ast = "b1348090a951e00cbf76c62d734fa808bfceea5b4169aa6da15a08ff185cbc50", unrolled_ast = "f1c461c8b0f677d0954ff6d29ab29abb648b57c7c141ddaf116a28d837e2b546", ssa_ast = "39e50a1b965cf6d4c19750d75edd4b1a8f8c02c04bbcb361f4fa70cebdc39574", flattened_ast = "a5a1c8def04670f3c5177946811bd27dcae5b045fce181e5e3307d9964686341", destructured_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", inlined_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", dce_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", bytecode = """ + { initial_symbol_table = "df5db326efa87ea83dbafd0e06782435113131775dc2aa80754176a6433e34c3", type_checked_symbol_table = "4c659204bbe76e0bc59df1829ce5ae48f3bc830ae96ce5cf1b29cb69dfe0943b", unrolled_symbol_table = "4c659204bbe76e0bc59df1829ce5ae48f3bc830ae96ce5cf1b29cb69dfe0943b", initial_ast = "b1348090a951e00cbf76c62d734fa808bfceea5b4169aa6da15a08ff185cbc50", unrolled_ast = "f1c461c8b0f677d0954ff6d29ab29abb648b57c7c141ddaf116a28d837e2b546", ssa_ast = "39e50a1b965cf6d4c19750d75edd4b1a8f8c02c04bbcb361f4fa70cebdc39574", flattened_ast = "a5a1c8def04670f3c5177946811bd27dcae5b045fce181e5e3307d9964686341", destructured_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", inlined_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", dce_ast = "97153aa38aad12f2695c1e457270bd678add4d96f01f78660a9be0ab8cd409bf", bytecode = """ import parent.aleo; program child.aleo; diff --git a/tests/expectations/compiler/tuple/tuple_in_function_param.out b/tests/expectations/compiler/tuple/tuple_in_function_param.out index b735f9876c..3cd3c2d082 100644 --- a/tests/expectations/compiler/tuple/tuple_in_function_param.out +++ b/tests/expectations/compiler/tuple/tuple_in_function_param.out @@ -5,5 +5,5 @@ Error [ETYC0372051]: A function cannot take in a tuple as input. --> compiler-test:4:20 | 4 | transition foo(a: (u8, u16)) -> (u8, u16) { - | ^ + | ^^^^^^^^^^^^ """] diff --git a/tests/expectations/compiler/tuple/tuple_not_allowed_fail.out b/tests/expectations/compiler/tuple/tuple_not_allowed_fail.out index 0711e695a0..b8bb0647fe 100644 --- a/tests/expectations/compiler/tuple/tuple_not_allowed_fail.out +++ b/tests/expectations/compiler/tuple/tuple_not_allowed_fail.out @@ -10,7 +10,7 @@ Error [ETYC0372051]: A function cannot take in a tuple as input. --> compiler-test:8:18 | 8 | function foo(a: (u8, u16)) -> (u8, u16) { - | ^ + | ^^^^^^^^^^^^ Error [ETYC0372049]: A tuple type cannot contain a tuple. --> compiler-test:12:36 | diff --git a/tests/expectations/execution/complex_finalization.out b/tests/expectations/execution/complex_finalization.out index 5f81a3d90c..45eeaadea9 100644 --- a/tests/expectations/execution/complex_finalization.out +++ b/tests/expectations/execution/complex_finalization.out @@ -35,7 +35,7 @@ finalize d: add r1 1u64 into r2; set r2 into counts[r0]; """, errors = "", warnings = "" }, - { initial_symbol_table = "6223f92c3bd5bbad5da2f567698b6e984ece97d3134095b26cc0b1d11079f60c", type_checked_symbol_table = "e22aa51f2a565205fe03a6f3b00552bd2e3442e100315dab5f2805a7f8b4cb69", unrolled_symbol_table = "e22aa51f2a565205fe03a6f3b00552bd2e3442e100315dab5f2805a7f8b4cb69", initial_ast = "2c14e776b891d7131858e07a8dba4dbf727b3d01dbf4d2e22415711d688dc7c3", unrolled_ast = "31db5dfbc43b124cb4780c1d629ee28de4a249a5aba21727a0dcb9726d4322f6", ssa_ast = "1f4225e1f83eb88bb3368544c3b2a077da163281476eaeb688334dac41bc0a9d", flattened_ast = "eba4b124fd3df6170a5cbfaad89f0e6d398cb2cba50d61b3c18f00381a6b3be9", destructured_ast = "c1e81066ab08a49915eaaed5b82b323ab1b7227157be6916832ff22eb658b15c", inlined_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", dce_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", bytecode = """ + { initial_symbol_table = "245c739fc70f8cdcd2b24f30d8aec16924fcab28c52d7c0043646622dbe75f57", type_checked_symbol_table = "8263d5899d205220dc81a56f9785ba66255b723f07d6203b6697708812831273", unrolled_symbol_table = "8263d5899d205220dc81a56f9785ba66255b723f07d6203b6697708812831273", initial_ast = "2c14e776b891d7131858e07a8dba4dbf727b3d01dbf4d2e22415711d688dc7c3", unrolled_ast = "31db5dfbc43b124cb4780c1d629ee28de4a249a5aba21727a0dcb9726d4322f6", ssa_ast = "1f4225e1f83eb88bb3368544c3b2a077da163281476eaeb688334dac41bc0a9d", flattened_ast = "eba4b124fd3df6170a5cbfaad89f0e6d398cb2cba50d61b3c18f00381a6b3be9", destructured_ast = "c1e81066ab08a49915eaaed5b82b323ab1b7227157be6916832ff22eb658b15c", inlined_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", dce_ast = "22f3f544c5331fee78a3b81381f6695bdaa06f437c4a56142b36da1e852d9840", bytecode = """ import zero_program.aleo; import one_program.aleo; program two_program.aleo; @@ -60,7 +60,7 @@ finalize b: add r3 1u64 into r4; set r4 into counts[r2]; """, errors = "", warnings = "" }, - { initial_symbol_table = "7d0a0d54b673b8428f972bec8346ca6830248f69cb3fba4b42c32e1a72cc1b0f", type_checked_symbol_table = "ea10fb298006b83389a483e12f9b97b7e1f691dc0a1aee602e74e10d915e8b0c", unrolled_symbol_table = "ea10fb298006b83389a483e12f9b97b7e1f691dc0a1aee602e74e10d915e8b0c", initial_ast = "387aba043fde6ead4d99bf4eb5c817051491a7d16aecd6383411e3cbc6aaefd5", unrolled_ast = "f93e4fd19542c5af01a5e0aec60e9f6265491a0952cafabfb7cdcfac00bd81b9", ssa_ast = "0ad477f1c1bc42ebcd4098caf856428e5be9a0845972cbd2908dcf53c6ce45a0", flattened_ast = "3fa8070cfe4be62533fb8b3d899c490f940686a97ae01ee0c8f6f7743527d726", destructured_ast = "5407ddb3a931cde7e50dc466557108fde8f6ebfd8d446cdb44855542208f4056", inlined_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", dce_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", bytecode = """ + { initial_symbol_table = "ee9c47b21aa811094b66a4cef0eef7af32effb924a2f514094b5280b4d611987", type_checked_symbol_table = "2d8e0167d05b2b689c8464c1eacb5947a7a30c36892eb5debe6692f948a18c88", unrolled_symbol_table = "2d8e0167d05b2b689c8464c1eacb5947a7a30c36892eb5debe6692f948a18c88", initial_ast = "387aba043fde6ead4d99bf4eb5c817051491a7d16aecd6383411e3cbc6aaefd5", unrolled_ast = "f93e4fd19542c5af01a5e0aec60e9f6265491a0952cafabfb7cdcfac00bd81b9", ssa_ast = "0ad477f1c1bc42ebcd4098caf856428e5be9a0845972cbd2908dcf53c6ce45a0", flattened_ast = "3fa8070cfe4be62533fb8b3d899c490f940686a97ae01ee0c8f6f7743527d726", destructured_ast = "5407ddb3a931cde7e50dc466557108fde8f6ebfd8d446cdb44855542208f4056", inlined_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", dce_ast = "8accc3977c89a2e948b39f6abc2c7f989e52313aac237bcb25469e4bc91fc4f1", bytecode = """ import zero_program.aleo; import one_program.aleo; import two_program.aleo; @@ -89,7 +89,7 @@ finalize e: add r4 1u64 into r5; set r5 into counts[r3]; """, errors = "", warnings = "" }, - { initial_symbol_table = "8272b3774900302d111cc659f82a49e7df702875ceb4e54787c068bcac901a85", type_checked_symbol_table = "3b9ce08a512a197af239b00944b50298885603f4f723debc4ee96b281d28bc4c", unrolled_symbol_table = "3b9ce08a512a197af239b00944b50298885603f4f723debc4ee96b281d28bc4c", initial_ast = "f731cdda879e0134eb5b1cf0d64d3cf5abbee2fd2ce758d3afac05ee07fb885f", unrolled_ast = "79017a53e402d0c7aad500a44936f4e06e418407b4a2b40f2bf69a185c4865c0", ssa_ast = "8a4f2ea8f8118515b8843aad5a201824dc2c6b06046f68698dde622f5ace3c4f", flattened_ast = "35f966d0d86e1e38c2c6650d83e62d701a9b9440766b78919ee0b509c3255cf7", destructured_ast = "5677314a7b55bf523441d3c40029daedf97666fb7821159b0c88654776ea2932", inlined_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", dce_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", bytecode = """ + { initial_symbol_table = "044526dfe3dfe09fde9004db01d837c5fe566d508d6a6045b8b64c6921179e15", type_checked_symbol_table = "7ccfd2a1cc75ab982ad84ed7401f7a744d99206145a4962130608634686f7cac", unrolled_symbol_table = "7ccfd2a1cc75ab982ad84ed7401f7a744d99206145a4962130608634686f7cac", initial_ast = "f731cdda879e0134eb5b1cf0d64d3cf5abbee2fd2ce758d3afac05ee07fb885f", unrolled_ast = "79017a53e402d0c7aad500a44936f4e06e418407b4a2b40f2bf69a185c4865c0", ssa_ast = "8a4f2ea8f8118515b8843aad5a201824dc2c6b06046f68698dde622f5ace3c4f", flattened_ast = "35f966d0d86e1e38c2c6650d83e62d701a9b9440766b78919ee0b509c3255cf7", destructured_ast = "5677314a7b55bf523441d3c40029daedf97666fb7821159b0c88654776ea2932", inlined_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", dce_ast = "9c779149583480acdca132daad34c2577ec0d09e28c36b11ecf91beb556cc7b5", bytecode = """ import zero_program.aleo; import one_program.aleo; import two_program.aleo; diff --git a/tests/expectations/interpreter/arithmetic.out b/tests/expectations/interpreter/arithmetic.out new file mode 100644 index 0000000000..6e13df9810 --- /dev/null +++ b/tests/expectations/interpreter/arithmetic.out @@ -0,0 +1,3 @@ +namespace = "Leo" +expectation = "Pass" +outputs = ["1712u32"] diff --git a/tests/expectations/interpreter/hash.out b/tests/expectations/interpreter/hash.out new file mode 100644 index 0000000000..6874c9b7bc --- /dev/null +++ b/tests/expectations/interpreter/hash.out @@ -0,0 +1,3 @@ +namespace = "Leo" +expectation = "Pass" +outputs = ["7649508962193807282860816486231709561414143880166292770947785297592281622433field"] diff --git a/tests/expectations/parser/finalize/finalize.out b/tests/expectations/parser/finalize/finalize.out index 2cd67f7cf2..7ab35c030c 100644 --- a/tests/expectations/parser/finalize/finalize.out +++ b/tests/expectations/parser/finalize/finalize.out @@ -28,8 +28,8 @@ functions = [ [ "main", { annotations = [], variant = "AsyncFunction", identifier = '{"id":"13","name":"main","span":"{\"lo\":193,\"hi\":197}"}', id = 23, input = [ - { identifier = '{"id":"14","name":"a","span":"{\"lo\":198,\"hi\":199}"}', mode = "None", id = 16, type_ = { Composite = { id = '{"id":"15","name":"foo","span":"{\"lo\":201,\"hi\":204}"}', program = "test" } }, span = { lo = 198, hi = 199 } }, - { identifier = '{"id":"17","name":"b","span":"{\"lo\":206,\"hi\":207}"}', mode = "None", id = 19, type_ = { Composite = { id = '{"id":"18","name":"bar","span":"{\"lo\":209,\"hi\":212}"}', program = "test" } }, span = { lo = 206, hi = 207 } }, + { identifier = '{"id":"14","name":"a","span":"{\"lo\":198,\"hi\":199}"}', mode = "None", id = 16, type_ = { Composite = { id = '{"id":"15","name":"foo","span":"{\"lo\":201,\"hi\":204}"}', program = "test" } }, span = { lo = 198, hi = 204 } }, + { identifier = '{"id":"17","name":"b","span":"{\"lo\":206,\"hi\":207}"}', mode = "None", id = 19, type_ = { Composite = { id = '{"id":"18","name":"bar","span":"{\"lo\":209,\"hi\":212}"}', program = "test" } }, span = { lo = 206, hi = 212 } }, ], output = [{ mode = "None", id = 21, type_ = { Composite = { id = '{"id":"20","name":"baz","span":"{\"lo\":217,\"hi\":220}"}', program = "test" } }, span = { lo = 217, hi = 220 } }], output_type = { Composite = { id = '{"id":"20","name":"baz","span":"{\"lo\":217,\"hi\":220}"}', program = "test" } }, block = { statements = [], id = 22, span = { lo = 221, hi = 233 } }, span = { lo = 178, hi = 233 } }, ], ] diff --git a/tests/expectations/parser/functions/bounded_recursion.out b/tests/expectations/parser/functions/bounded_recursion.out index 655d7e66d5..0ca5264ebb 100644 --- a/tests/expectations/parser/functions/bounded_recursion.out +++ b/tests/expectations/parser/functions/bounded_recursion.out @@ -15,7 +15,7 @@ mappings = [] functions = [ [ "x", - { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 18, input = [{ identifier = '{"id":"3","name":"y","span":"{\"lo\":50,\"hi\":51}"}', mode = "Constant", id = 4, type_ = { Integer = "U32" }, span = { lo = 50, hi = 51 } }], output = [{ mode = "None", id = 5, type_ = { Integer = "U8" }, span = { lo = 61, hi = 63 } }], output_type = { Integer = "U8" }, block = { id = 17, statements = [{ Conditional = { id = 16, condition = { Binary = { op = "Lt", id = 8, left = { Identifier = '{"id":"6","name":"y","span":"{\"lo\":77,\"hi\":78}"}' }, right = { Literal = { Integer = [ + { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 18, input = [{ identifier = '{"id":"3","name":"y","span":"{\"lo\":50,\"hi\":51}"}', mode = "Constant", id = 4, type_ = { Integer = "U32" }, span = { lo = 50, hi = 56 } }], output = [{ mode = "None", id = 5, type_ = { Integer = "U8" }, span = { lo = 61, hi = 63 } }], output_type = { Integer = "U8" }, block = { id = 17, statements = [{ Conditional = { id = 16, condition = { Binary = { op = "Lt", id = 8, left = { Identifier = '{"id":"6","name":"y","span":"{\"lo\":77,\"hi\":78}"}' }, right = { Literal = { Integer = [ "U32", "5", { span = { lo = 81, hi = 85 } }, @@ -29,7 +29,7 @@ functions = [ ], [ "main", - { annotations = [], variant = "Function", identifier = '{"id":"19","name":"main","span":"{\"lo\":145,\"hi\":149}"}', output_type = "Boolean", id = 30, input = [{ identifier = '{"id":"20","name":"y","span":"{\"lo\":150,\"hi\":151}"}', mode = "None", type_ = "Boolean", id = 21, span = { lo = 150, hi = 151 } }], output = [{ mode = "None", type_ = "Boolean", id = 22, span = { lo = 162, hi = 166 } }], block = { id = 29, statements = [ + { annotations = [], variant = "Function", identifier = '{"id":"19","name":"main","span":"{\"lo\":145,\"hi\":149}"}', output_type = "Boolean", id = 30, input = [{ identifier = '{"id":"20","name":"y","span":"{\"lo\":150,\"hi\":151}"}', mode = "None", type_ = "Boolean", id = 21, span = { lo = 150, hi = 157 } }], output = [{ mode = "None", type_ = "Boolean", id = 22, span = { lo = 162, hi = 166 } }], block = { id = 29, statements = [ { Expression = { id = 26, expression = { Call = { program = "test", id = 25, arguments = [{ Literal = { Integer = [ "U32", "1", diff --git a/tests/expectations/parser/functions/const_param.out b/tests/expectations/parser/functions/const_param.out index 0fec13c8a4..a48d6cba76 100644 --- a/tests/expectations/parser/functions/const_param.out +++ b/tests/expectations/parser/functions/const_param.out @@ -16,8 +16,8 @@ functions = [ [ "x", { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 11, input = [ - { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 42 } }, - { identifier = '{"id":"5","name":"y","span":"{\"lo\":58,\"hi\":59}"}', mode = "Constant", id = 6, type_ = { Integer = "I32" }, span = { lo = 58, hi = 59 } }, + { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 47 } }, + { identifier = '{"id":"5","name":"y","span":"{\"lo\":58,\"hi\":59}"}', mode = "Constant", id = 6, type_ = { Integer = "I32" }, span = { lo = 58, hi = 64 } }, ], output = [{ mode = "None", id = 7, type_ = { Integer = "U8" }, span = { lo = 69, hi = 71 } }], output_type = { Integer = "U8" }, block = { id = 10, statements = [{ Return = { id = 9, expression = { Literal = { Integer = [ "U8", "0", @@ -28,8 +28,8 @@ functions = [ [ "x", { annotations = [], variant = "Function", identifier = '{"id":"12","name":"x","span":"{\"lo\":118,\"hi\":119}"}', id = 21, input = [ - { identifier = '{"id":"13","name":"x","span":"{\"lo\":129,\"hi\":130}"}', mode = "Constant", id = 14, type_ = { Integer = "U32" }, span = { lo = 129, hi = 130 } }, - { identifier = '{"id":"15","name":"y","span":"{\"lo\":137,\"hi\":138}"}', mode = "None", id = 16, type_ = { Integer = "I32" }, span = { lo = 137, hi = 138 } }, + { identifier = '{"id":"13","name":"x","span":"{\"lo\":129,\"hi\":130}"}', mode = "Constant", id = 14, type_ = { Integer = "U32" }, span = { lo = 129, hi = 135 } }, + { identifier = '{"id":"15","name":"y","span":"{\"lo\":137,\"hi\":138}"}', mode = "None", id = 16, type_ = { Integer = "I32" }, span = { lo = 137, hi = 143 } }, ], output = [{ mode = "None", id = 17, type_ = { Integer = "U8" }, span = { lo = 148, hi = 150 } }], output_type = { Integer = "U8" }, block = { id = 20, statements = [{ Return = { id = 19, expression = { Literal = { Integer = [ "U8", "0", diff --git a/tests/expectations/parser/functions/constant_input.out b/tests/expectations/parser/functions/constant_input.out index 26778a1f31..adc4d740a1 100644 --- a/tests/expectations/parser/functions/constant_input.out +++ b/tests/expectations/parser/functions/constant_input.out @@ -14,7 +14,7 @@ structs = [] mappings = [] functions = [[ "x", - { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 7, input = [{ identifier = '{"id":"3","name":"x","span":"{\"lo\":50,\"hi\":51}"}', mode = "Constant", id = 4, type_ = { Integer = "U8" }, span = { lo = 50, hi = 51 } }], output = [{ mode = "None", id = 5, type_ = { Integer = "U8" }, span = { lo = 60, hi = 62 } }], output_type = { Integer = "U8" }, block = { statements = [], id = 6, span = { lo = 63, hi = 65 } }, span = { lo = 30, hi = 65 } }, + { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 7, input = [{ identifier = '{"id":"3","name":"x","span":"{\"lo\":50,\"hi\":51}"}', mode = "Constant", id = 4, type_ = { Integer = "U8" }, span = { lo = 50, hi = 55 } }], output = [{ mode = "None", id = 5, type_ = { Integer = "U8" }, span = { lo = 60, hi = 62 } }], output_type = { Integer = "U8" }, block = { statements = [], id = 6, span = { lo = 63, hi = 65 } }, span = { lo = 30, hi = 65 } }, ]] [outputs.program_scopes.test.span] diff --git a/tests/expectations/parser/functions/infinite_recursion.out b/tests/expectations/parser/functions/infinite_recursion.out index 7f3875898d..f59db073a4 100644 --- a/tests/expectations/parser/functions/infinite_recursion.out +++ b/tests/expectations/parser/functions/infinite_recursion.out @@ -19,7 +19,7 @@ functions = [ ], [ "main", - { annotations = [], variant = "Function", identifier = '{"id":"9","name":"main","span":"{\"lo\":92,\"hi\":96}"}', output_type = "Boolean", id = 19, input = [{ identifier = '{"id":"10","name":"y","span":"{\"lo\":97,\"hi\":98}"}', mode = "None", type_ = "Boolean", id = 11, span = { lo = 97, hi = 98 } }], output = [{ mode = "None", type_ = "Boolean", id = 12, span = { lo = 109, hi = 113 } }], block = { id = 18, statements = [ + { annotations = [], variant = "Function", identifier = '{"id":"9","name":"main","span":"{\"lo\":92,\"hi\":96}"}', output_type = "Boolean", id = 19, input = [{ identifier = '{"id":"10","name":"y","span":"{\"lo\":97,\"hi\":98}"}', mode = "None", type_ = "Boolean", id = 11, span = { lo = 97, hi = 104 } }], output = [{ mode = "None", type_ = "Boolean", id = 12, span = { lo = 109, hi = 113 } }], block = { id = 18, statements = [ { Expression = { id = 15, expression = { Call = { arguments = [], program = "test", id = 14, function = { Identifier = '{"id":"13","name":"inf","span":"{\"lo\":124,\"hi\":127}"}' }, span = { lo = 124, hi = 129 } } }, span = { lo = 124, hi = 130 } } }, { Return = { id = 17, expression = { Identifier = '{"id":"16","name":"y","span":"{\"lo\":146,\"hi\":147}"}' }, span = { lo = 139, hi = 148 } } }, ], span = { lo = 114, hi = 154 } }, span = { lo = 83, hi = 154 } }, diff --git a/tests/expectations/parser/functions/inline_function.out b/tests/expectations/parser/functions/inline_function.out index bfcf3be574..55eeb9ffe3 100644 --- a/tests/expectations/parser/functions/inline_function.out +++ b/tests/expectations/parser/functions/inline_function.out @@ -15,8 +15,8 @@ mappings = [] functions = [[ "foo", { annotations = [], variant = "Inline", identifier = '{"id":"2","name":"foo","span":"{\"lo\":33,\"hi\":36}"}', id = 11, input = [ - { identifier = '{"id":"3","name":"x","span":"{\"lo\":37,\"hi\":38}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 37, hi = 38 } }, - { identifier = '{"id":"5","name":"y","span":"{\"lo\":45,\"hi\":46}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 45, hi = 46 } }, + { identifier = '{"id":"3","name":"x","span":"{\"lo\":37,\"hi\":38}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 37, hi = 43 } }, + { identifier = '{"id":"5","name":"y","span":"{\"lo\":45,\"hi\":46}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 45, hi = 51 } }, ], output = [{ mode = "None", id = 7, type_ = { Integer = "U32" }, span = { lo = 56, hi = 59 } }], output_type = { Integer = "U32" }, block = { id = 10, statements = [{ Return = { id = 9, expression = { Literal = { Integer = [ "U32", "0", diff --git a/tests/expectations/parser/functions/params.out b/tests/expectations/parser/functions/params.out index caf146ef80..35a937bab7 100644 --- a/tests/expectations/parser/functions/params.out +++ b/tests/expectations/parser/functions/params.out @@ -15,8 +15,8 @@ mappings = [] functions = [[ "x", { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 11, input = [ - { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 42 } }, - { identifier = '{"id":"5","name":"y","span":"{\"lo\":49,\"hi\":50}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 49, hi = 50 } }, + { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 47 } }, + { identifier = '{"id":"5","name":"y","span":"{\"lo\":49,\"hi\":50}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 49, hi = 55 } }, ], output = [{ mode = "None", id = 7, type_ = { Integer = "U8" }, span = { lo = 60, hi = 62 } }], output_type = { Integer = "U8" }, block = { id = 10, statements = [{ Return = { id = 9, expression = { Literal = { Integer = [ "U8", "0", diff --git a/tests/expectations/parser/functions/params_return.out b/tests/expectations/parser/functions/params_return.out index f4363bc3ac..be8d76a552 100644 --- a/tests/expectations/parser/functions/params_return.out +++ b/tests/expectations/parser/functions/params_return.out @@ -15,8 +15,8 @@ mappings = [] functions = [[ "x", { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 11, input = [ - { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 42 } }, - { identifier = '{"id":"5","name":"y","span":"{\"lo\":49,\"hi\":50}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 49, hi = 50 } }, + { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 47 } }, + { identifier = '{"id":"5","name":"y","span":"{\"lo\":49,\"hi\":50}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 49, hi = 55 } }, ], output = [{ mode = "None", id = 7, type_ = { Integer = "U32" }, span = { lo = 60, hi = 63 } }], output_type = { Integer = "U32" }, block = { id = 10, statements = [{ Return = { id = 9, expression = { Literal = { Integer = [ "U8", "0", diff --git a/tests/expectations/parser/functions/public_param.out b/tests/expectations/parser/functions/public_param.out index 5c4413e6a2..417f1570a7 100644 --- a/tests/expectations/parser/functions/public_param.out +++ b/tests/expectations/parser/functions/public_param.out @@ -16,8 +16,8 @@ functions = [ [ "x", { annotations = [], variant = "Function", identifier = '{"id":"2","name":"x","span":"{\"lo\":39,\"hi\":40}"}', id = 11, input = [ - { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 42 } }, - { identifier = '{"id":"5","name":"y","span":"{\"lo\":56,\"hi\":57}"}', mode = "Public", id = 6, type_ = { Integer = "I32" }, span = { lo = 56, hi = 57 } }, + { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 47 } }, + { identifier = '{"id":"5","name":"y","span":"{\"lo\":56,\"hi\":57}"}', mode = "Public", id = 6, type_ = { Integer = "I32" }, span = { lo = 56, hi = 62 } }, ], output = [{ mode = "None", id = 7, type_ = { Integer = "U8" }, span = { lo = 67, hi = 69 } }], output_type = { Integer = "U8" }, block = { id = 10, statements = [{ Return = { id = 9, expression = { Literal = { Integer = [ "U8", "0", @@ -28,8 +28,8 @@ functions = [ [ "x", { annotations = [], variant = "Function", identifier = '{"id":"12","name":"x","span":"{\"lo\":116,\"hi\":117}"}', id = 21, input = [ - { identifier = '{"id":"13","name":"x","span":"{\"lo\":125,\"hi\":126}"}', mode = "Public", id = 14, type_ = { Integer = "U32" }, span = { lo = 125, hi = 126 } }, - { identifier = '{"id":"15","name":"y","span":"{\"lo\":133,\"hi\":134}"}', mode = "None", id = 16, type_ = { Integer = "I32" }, span = { lo = 133, hi = 134 } }, + { identifier = '{"id":"13","name":"x","span":"{\"lo\":125,\"hi\":126}"}', mode = "Public", id = 14, type_ = { Integer = "U32" }, span = { lo = 125, hi = 131 } }, + { identifier = '{"id":"15","name":"y","span":"{\"lo\":133,\"hi\":134}"}', mode = "None", id = 16, type_ = { Integer = "I32" }, span = { lo = 133, hi = 139 } }, ], output = [{ mode = "None", id = 17, type_ = { Integer = "U8" }, span = { lo = 144, hi = 146 } }], output_type = { Integer = "U8" }, block = { id = 20, statements = [{ Return = { id = 19, expression = { Literal = { Integer = [ "U8", "0", diff --git a/tests/expectations/parser/functions/transition_function.out b/tests/expectations/parser/functions/transition_function.out index 93cfc1c88e..f89e8a9003 100644 --- a/tests/expectations/parser/functions/transition_function.out +++ b/tests/expectations/parser/functions/transition_function.out @@ -15,8 +15,8 @@ mappings = [] functions = [[ "foo", { annotations = [], variant = "Transition", identifier = '{"id":"2","name":"foo","span":"{\"lo\":37,\"hi\":40}"}', id = 11, input = [ - { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 42 } }, - { identifier = '{"id":"5","name":"y","span":"{\"lo\":49,\"hi\":50}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 49, hi = 50 } }, + { identifier = '{"id":"3","name":"x","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", id = 4, type_ = { Integer = "U32" }, span = { lo = 41, hi = 47 } }, + { identifier = '{"id":"5","name":"y","span":"{\"lo\":49,\"hi\":50}"}', mode = "None", id = 6, type_ = { Integer = "I32" }, span = { lo = 49, hi = 55 } }, ], output = [{ mode = "None", id = 7, type_ = { Integer = "U32" }, span = { lo = 60, hi = 63 } }], output_type = { Integer = "U32" }, block = { id = 10, statements = [{ Return = { id = 9, expression = { Literal = { Integer = [ "U32", "0", diff --git a/tests/expectations/parser/future/explicit_future_typing.out b/tests/expectations/parser/future/explicit_future_typing.out index a93226c0fa..5899def04c 100644 --- a/tests/expectations/parser/future/explicit_future_typing.out +++ b/tests/expectations/parser/future/explicit_future_typing.out @@ -79,16 +79,16 @@ functions = [ [ "finalize_main", { annotations = [], variant = "AsyncFunction", identifier = '{"id":"26","name":"finalize_main","span":"{\"lo\":426,\"hi\":439}"}', output = [], output_type = "Unit", id = 49, input = [ - { identifier = '{"id":"27","name":"a","span":"{\"lo\":440,\"hi\":441}"}', mode = "None", id = 28, type_ = { Integer = "U32" }, span = { lo = 440, hi = 441 } }, - { identifier = '{"id":"29","name":"b","span":"{\"lo\":448,\"hi\":449}"}', mode = "None", id = 30, type_ = { Integer = "U32" }, span = { lo = 448, hi = 449 } }, - { identifier = '{"id":"31","name":"f0","span":"{\"lo\":456,\"hi\":458}"}', mode = "None", id = 32, type_ = { Future = { inputs = [], is_explicit = true } }, span = { lo = 456, hi = 458 } }, + { identifier = '{"id":"27","name":"a","span":"{\"lo\":440,\"hi\":441}"}', mode = "None", id = 28, type_ = { Integer = "U32" }, span = { lo = 440, hi = 446 } }, + { identifier = '{"id":"29","name":"b","span":"{\"lo\":448,\"hi\":449}"}', mode = "None", id = 30, type_ = { Integer = "U32" }, span = { lo = 448, hi = 454 } }, + { identifier = '{"id":"31","name":"f0","span":"{\"lo\":456,\"hi\":458}"}', mode = "None", id = 32, type_ = { Future = { inputs = [], is_explicit = true } }, span = { lo = 456, hi = 471 } }, { identifier = '{"id":"33","name":"f1","span":"{\"lo\":474,\"hi\":476}"}', mode = "None", id = 34, type_ = { Future = { is_explicit = true, inputs = [ { Integer = "U32" }, { Future = { is_explicit = true, inputs = [ { Integer = "U32" }, { Integer = "U32" }, ] } }, -] } }, span = { lo = 474, hi = 476 } }, +] } }, span = { lo = 474, hi = 514 } }, ], block = { id = 48, statements = [ { Expression = { id = 39, expression = { Access = { AssociatedFunction = { variant = '{"id":"37","name":"Future","span":"{\"lo\":0,\"hi\":0}"}', name = '{"id":"36","name":"await","span":"{\"lo\":530,\"hi\":535}"}', id = 38, arguments = [{ Identifier = '{"id":"35","name":"f0","span":"{\"lo\":527,\"hi\":529}"}' }], span = { lo = 527, hi = 537 } } } }, span = { lo = 527, hi = 538 } } }, { Expression = { id = 44, expression = { Access = { AssociatedFunction = { variant = '{"id":"42","name":"Future","span":"{\"lo\":0,\"hi\":0}"}', name = '{"id":"41","name":"await","span":"{\"lo\":550,\"hi\":555}"}', id = 43, arguments = [{ Identifier = '{"id":"40","name":"f1","span":"{\"lo\":547,\"hi\":549}"}' }], span = { lo = 547, hi = 557 } } } }, span = { lo = 547, hi = 558 } } }, diff --git a/tests/expectations/parser/program/async_basic.out b/tests/expectations/parser/program/async_basic.out index ff3effce84..5c8307160a 100644 --- a/tests/expectations/parser/program/async_basic.out +++ b/tests/expectations/parser/program/async_basic.out @@ -39,8 +39,8 @@ functions = [ [ "finalize_main", { annotations = [], variant = "AsyncFunction", identifier = '{"id":"16","name":"finalize_main","span":"{\"lo\":186,\"hi\":199}"}', id = 30, input = [ - { identifier = '{"id":"17","name":"a","span":"{\"lo\":200,\"hi\":201}"}', mode = "None", id = 18, type_ = { Integer = "U32" }, span = { lo = 200, hi = 201 } }, - { identifier = '{"id":"19","name":"b","span":"{\"lo\":207,\"hi\":208}"}', mode = "None", id = 20, type_ = { Integer = "U32" }, span = { lo = 207, hi = 208 } }, + { identifier = '{"id":"17","name":"a","span":"{\"lo\":200,\"hi\":201}"}', mode = "None", id = 18, type_ = { Integer = "U32" }, span = { lo = 200, hi = 205 } }, + { identifier = '{"id":"19","name":"b","span":"{\"lo\":207,\"hi\":208}"}', mode = "None", id = 20, type_ = { Integer = "U32" }, span = { lo = 207, hi = 212 } }, ], output = [{ mode = "None", id = 21, type_ = { Future = { inputs = [], is_explicit = false } }, span = { lo = 217, hi = 223 } }], output_type = { Future = { inputs = [], is_explicit = false } }, block = { id = 29, statements = [{ Expression = { id = 28, expression = { Access = { AssociatedFunction = { variant = '{"id":"22","name":"Mapping","span":"{\"lo\":234,\"hi\":241}"}', name = '{"id":"23","name":"set","span":"{\"lo\":243,\"hi\":246}"}', id = 27, arguments = [ { Identifier = '{"id":"24","name":"Yo","span":"{\"lo\":247,\"hi\":249}"}' }, { Identifier = '{"id":"25","name":"a","span":"{\"lo\":251,\"hi\":252}"}' }, diff --git a/tests/expectations/parser/program/network_id.out b/tests/expectations/parser/program/network_id.out index b9ccf598c3..2baad6cbbf 100644 --- a/tests/expectations/parser/program/network_id.out +++ b/tests/expectations/parser/program/network_id.out @@ -14,7 +14,7 @@ structs = [] mappings = [] functions = [[ "main", - { annotations = [], variant = "Function", identifier = '{"id":"2","name":"main","span":"{\"lo\":34,\"hi\":38}"}', output = [], output_type = "Unit", id = 13, input = [{ identifier = '{"id":"3","name":"a","span":"{\"lo\":39,\"hi\":40}"}', mode = "None", type_ = "Address", id = 4, span = { lo = 39, hi = 40 } }], block = { id = 12, statements = [ + { annotations = [], variant = "Function", identifier = '{"id":"2","name":"main","span":"{\"lo\":34,\"hi\":38}"}', output = [], output_type = "Unit", id = 13, input = [{ identifier = '{"id":"3","name":"a","span":"{\"lo\":39,\"hi\":40}"}', mode = "None", type_ = "Address", id = 4, span = { lo = 39, hi = 49 } }], block = { id = 12, statements = [ { Assert = { id = 9, variant = { AssertEq = [ { Access = { Member = { name = '{"id":"6","name":"id","span":"{\"lo\":79,\"hi\":81}"}', id = 7, inner = { Identifier = '{"id":"5","name":"network","span":"{\"lo\":71,\"hi\":78}"}' }, span = { lo = 71, hi = 81 } } } }, { Literal = { Integer = [ diff --git a/tests/expectations/parser/program/special_address.out b/tests/expectations/parser/program/special_address.out index 8924e6940a..36f4f220d8 100644 --- a/tests/expectations/parser/program/special_address.out +++ b/tests/expectations/parser/program/special_address.out @@ -17,7 +17,7 @@ mappings = [[ ]] functions = [[ "main", - { annotations = [], variant = "Transition", identifier = '{"id":"4","name":"main","span":"{\"lo\":70,\"hi\":74}"}', output = [], output_type = "Unit", id = 24, input = [{ identifier = '{"id":"5","name":"a","span":"{\"lo\":75,\"hi\":76}"}', mode = "None", type_ = "Address", id = 6, span = { lo = 75, hi = 76 } }], block = { id = 23, statements = [ + { annotations = [], variant = "Transition", identifier = '{"id":"4","name":"main","span":"{\"lo\":70,\"hi\":74}"}', output = [], output_type = "Unit", id = 24, input = [{ identifier = '{"id":"5","name":"a","span":"{\"lo\":75,\"hi\":76}"}', mode = "None", type_ = "Address", id = 6, span = { lo = 75, hi = 85 } }], block = { id = 23, statements = [ { Assert = { id = 11, variant = { AssertEq = [ { Identifier = '{"id":"7","name":"a","span":"{\"lo\":107,\"hi\":108}"}' }, { Access = { Member = { name = '{"id":"9","name":"caller","span":"{\"lo\":115,\"hi\":121}"}', id = 10, inner = { Identifier = '{"id":"8","name":"self","span":"{\"lo\":110,\"hi\":114}"}' }, span = { lo = 110, hi = 121 } } } }, diff --git a/tests/expectations/parser/type_/signature.out b/tests/expectations/parser/type_/signature.out index e36afb3cb1..bda7d3c80d 100644 --- a/tests/expectations/parser/type_/signature.out +++ b/tests/expectations/parser/type_/signature.out @@ -16,9 +16,9 @@ functions = [ [ "baz", { annotations = [], variant = "Transition", identifier = '{"id":"2","name":"baz","span":"{\"lo\":37,\"hi\":40}"}', output = [], output_type = "Unit", id = 30, input = [ - { identifier = '{"id":"3","name":"s","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", type_ = "Signature", id = 4, span = { lo = 41, hi = 42 } }, - { identifier = '{"id":"5","name":"a","span":"{\"lo\":55,\"hi\":56}"}', mode = "None", type_ = "Address", id = 6, span = { lo = 55, hi = 56 } }, - { identifier = '{"id":"7","name":"v","span":"{\"lo\":67,\"hi\":68}"}', mode = "None", id = 9, type_ = { Composite = { id = '{"id":"8","name":"value","span":"{\"lo\":70,\"hi\":75}"}', program = "test" } }, span = { lo = 67, hi = 68 } }, + { identifier = '{"id":"3","name":"s","span":"{\"lo\":41,\"hi\":42}"}', mode = "None", type_ = "Signature", id = 4, span = { lo = 41, hi = 53 } }, + { identifier = '{"id":"5","name":"a","span":"{\"lo\":55,\"hi\":56}"}', mode = "None", type_ = "Address", id = 6, span = { lo = 55, hi = 65 } }, + { identifier = '{"id":"7","name":"v","span":"{\"lo\":67,\"hi\":68}"}', mode = "None", id = 9, type_ = { Composite = { id = '{"id":"8","name":"value","span":"{\"lo\":70,\"hi\":75}"}', program = "test" } }, span = { lo = 67, hi = 75 } }, ], block = { id = 29, statements = [ { Definition = { declaration_type = "Let", type_ = "Boolean", id = 17, place = { Identifier = '{"id":"10","name":"a","span":"{\"lo\":91,\"hi\":92}"}' }, value = { Access = { AssociatedFunction = { variant = '{"id":"11","name":"signature","span":"{\"lo\":101,\"hi\":110}"}', name = '{"id":"12","name":"verify","span":"{\"lo\":112,\"hi\":118}"}', id = 16, arguments = [ { Identifier = '{"id":"13","name":"s","span":"{\"lo\":119,\"hi\":120}"}' }, @@ -38,7 +38,7 @@ functions = [ ], [ "bar", - { annotations = [], variant = "Transition", identifier = '{"id":"31","name":"bar","span":"{\"lo\":214,\"hi\":217}"}', id = 43, input = [{ identifier = '{"id":"32","name":"x","span":"{\"lo\":218,\"hi\":219}"}', mode = "None", id = 33, type_ = { Integer = "U8" }, span = { lo = 218, hi = 219 } }], output = [{ mode = "None", id = 34, type_ = { Integer = "U8" }, span = { lo = 228, hi = 230 } }], output_type = { Integer = "U8" }, block = { id = 42, statements = [ + { annotations = [], variant = "Transition", identifier = '{"id":"31","name":"bar","span":"{\"lo\":214,\"hi\":217}"}', id = 43, input = [{ identifier = '{"id":"32","name":"x","span":"{\"lo\":218,\"hi\":219}"}', mode = "None", id = 33, type_ = { Integer = "U8" }, span = { lo = 218, hi = 223 } }], output = [{ mode = "None", id = 34, type_ = { Integer = "U8" }, span = { lo = 228, hi = 230 } }], output_type = { Integer = "U8" }, block = { id = 42, statements = [ { Definition = { declaration_type = "Let", id = 39, place = { Identifier = '{"id":"35","name":"signature","span":"{\"lo\":245,\"hi\":254}"}' }, type_ = { Integer = "U8" }, value = { Binary = { op = "Add", id = 38, left = { Literal = { Integer = [ "U8", "1", @@ -51,9 +51,9 @@ functions = [ [ "bax", { annotations = [], variant = "Transition", identifier = '{"id":"44","name":"bax","span":"{\"lo\":317,\"hi\":320}"}', output = [], output_type = "Unit", id = 64, input = [ - { identifier = '{"id":"45","name":"s","span":"{\"lo\":321,\"hi\":322}"}', mode = "None", type_ = "Signature", id = 46, span = { lo = 321, hi = 322 } }, - { identifier = '{"id":"47","name":"a","span":"{\"lo\":335,\"hi\":336}"}', mode = "None", type_ = "Address", id = 48, span = { lo = 335, hi = 336 } }, - { identifier = '{"id":"49","name":"v","span":"{\"lo\":347,\"hi\":348}"}', mode = "None", id = 51, type_ = { Composite = { id = '{"id":"50","name":"value","span":"{\"lo\":350,\"hi\":355}"}', program = "test" } }, span = { lo = 347, hi = 348 } }, + { identifier = '{"id":"45","name":"s","span":"{\"lo\":321,\"hi\":322}"}', mode = "None", type_ = "Signature", id = 46, span = { lo = 321, hi = 333 } }, + { identifier = '{"id":"47","name":"a","span":"{\"lo\":335,\"hi\":336}"}', mode = "None", type_ = "Address", id = 48, span = { lo = 335, hi = 345 } }, + { identifier = '{"id":"49","name":"v","span":"{\"lo\":347,\"hi\":348}"}', mode = "None", id = 51, type_ = { Composite = { id = '{"id":"50","name":"value","span":"{\"lo\":350,\"hi\":355}"}', program = "test" } }, span = { lo = 347, hi = 355 } }, ], block = { id = 63, statements = [ { Definition = { declaration_type = "Let", type_ = "Boolean", id = 59, place = { Identifier = '{"id":"52","name":"a","span":"{\"lo\":370,\"hi\":371}"}' }, value = { Access = { AssociatedFunction = { variant = '{"id":"53","name":"signature","span":"{\"lo\":380,\"hi\":389}"}', name = '{"id":"54","name":"sign","span":"{\"lo\":391,\"hi\":395}"}', id = 58, arguments = [ { Identifier = '{"id":"55","name":"s","span":"{\"lo\":396,\"hi\":397}"}' }, diff --git a/tests/test-framework/Cargo.toml b/tests/test-framework/Cargo.toml index fcad45138e..0c4f90eac9 100644 --- a/tests/test-framework/Cargo.toml +++ b/tests/test-framework/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-test-framework" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "The testing framework for the Leo programming language" homepage = "https://leo-lang.org" @@ -24,7 +24,7 @@ harness = false [dependencies.leo-errors] path = "../../errors" -version = "2.3.0" +version = "2.4.1" [dependencies.backtrace] version = "0.3.74" diff --git a/tests/test-framework/benches/leo_compiler.rs b/tests/test-framework/benches/leo_compiler.rs index e18755c946..72d1dc6f26 100644 --- a/tests/test-framework/benches/leo_compiler.rs +++ b/tests/test-framework/benches/leo_compiler.rs @@ -86,15 +86,15 @@ struct Sample { fn new_compiler(handler: &Handler) -> Compiler<'_, CurrentNetwork> { Compiler::new( String::from("test"), - String::from("aleo"), handler, + vec![], PathBuf::from(String::new()), - PathBuf::from(String::new()), - Some(CompilerOptions { + CompilerOptions { build: BuildOptions { dce_enabled: true, conditional_block_max_depth: 10, disable_conditional_branch_type_checking: false, + build_tests: true, }, output: OutputOptions { symbol_table_spans_enabled: false, @@ -110,7 +110,7 @@ fn new_compiler(handler: &Handler) -> Compiler<'_, CurrentNetwork> { inlined_ast: false, dce_ast: false, }, - }), + }, IndexMap::new(), ) } @@ -166,7 +166,8 @@ impl Sample { ) { self.bencher(c, mode, |mut compiler| { let (input, name) = self.data(); - compiler.parse_program_from_string(input, name).expect("Failed to parse program"); + compiler.reset(vec![(name, input.to_string())]); + compiler.parse().expect("Failed to parse program"); logic(compiler) }); } @@ -174,8 +175,9 @@ impl Sample { fn bench_parse(&self, c: &mut Criterion) { self.bencher(c, "parse", |mut compiler| { let (input, name) = self.data(); + compiler.reset(vec![(name, input.to_string())]); let start = Instant::now(); - let out = compiler.parse_program_from_string(input, name); + let out = compiler.parse(); let time = start.elapsed(); out.expect("Failed to parse program"); time @@ -318,8 +320,9 @@ impl Sample { fn bench_full(&self, c: &mut Criterion) { self.bencher(c, "full", |mut compiler| { let (input, name) = self.data(); + compiler.reset(vec![(name, input.to_string())]); let start = Instant::now(); - compiler.parse_program_from_string(input, name).expect("Failed to parse program"); + compiler.parse().expect("Failed to parse program"); let symbol_table = compiler.symbol_table_pass().expect("failed to generate symbol table"); let (symbol_table, struct_graph, call_graph) = compiler.type_checker_pass(symbol_table).expect("failed to run type check pass"); diff --git a/tests/test-framework/src/error.rs b/tests/test-framework/src/error.rs index 1a0862dea6..81c44beb99 100644 --- a/tests/test-framework/src/error.rs +++ b/tests/test-framework/src/error.rs @@ -48,6 +48,8 @@ fn toml_to_string(x: &Value) -> String { s.push('\n'); } s + } else if let Some(s) = x.as_str() { + s.to_string() } else { toml::to_string(x).expect("serialization failed") } diff --git a/tests/tests/compiler/constants/block_height_type_fail.leo b/tests/tests/compiler/constants/block_height_type_fail.leo new file mode 100644 index 0000000000..a933a3b433 --- /dev/null +++ b/tests/tests/compiler/constants/block_height_type_fail.leo @@ -0,0 +1,14 @@ +/* + namespace = "Compile" + expectation = "Fail" + */ + +program test.aleo { + async function bar() { + let x: u64 = block.height; + } + + async transition foo() -> Future { + return bar(); + } +} diff --git a/tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo b/tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo new file mode 100644 index 0000000000..0c7bcc3e0e --- /dev/null +++ b/tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo @@ -0,0 +1,43 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ + +// The 'test_dep' program. +program test_dep.aleo { + mapping Yo: u32 => u32; + record yeets { + owner: address, + val: u32, + } + + async transition main_dep(a:u32) -> (yeets, Future) { + let f: Future = finalize_main_dep(a, 1u32); + let l: yeets = yeets {owner: self.caller, val: 1u32}; + return (l, f); + } + + async function finalize_main_dep(a:u32, b:u32) { + Mapping::set(Yo, a, b); + let c:u32 = a + b; + } +} + +// --- Next Program --- // + +import test_dep.aleo; + +program test.aleo { + mapping account: address => u64; + + async transition main(public a: u64) -> Future { + return finish(a); + } + + async function finish(public a: u64) { + CheatCode::print_mapping(Yo); + CheatCode::print_mapping(test_dep.aleo/account); + CheatCode::set_block_height(1u64); + CheatCode::set_block_height(a); + } +} diff --git a/tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo b/tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo new file mode 100644 index 0000000000..050d3ae4f7 --- /dev/null +++ b/tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo @@ -0,0 +1,43 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ + +// The 'test_dep' program. +program test_dep.aleo { + mapping Yo: u32 => u32; + record yeets { + owner: address, + val: u32, + } + + async transition main_dep(a:u32) -> (yeets, Future) { + let f: Future = finalize_main_dep(a, 1u32); + let l: yeets = yeets {owner: self.caller, val: 1u32}; + return (l, f); + } + + async function finalize_main_dep(a:u32, b:u32) { + Mapping::set(Yo, a, b); + let c:u32 = a + b; + } +} + +// --- Next Program --- // + +import test_dep.aleo; + +program test.aleo { + mapping account: address => u64; + + async transition main(public a: u32) -> Future { + return finish(a); + } + + async function finish(public a: u32) { + CheatCode::print_mapping(account); + CheatCode::print_mapping(test_dep.aleo/Yo); + CheatCode::set_block_height(1u32); + CheatCode::set_block_height(a); + } +} diff --git a/tests/tests/compiler/function/annotated_function.leo b/tests/tests/compiler/function/annotated_function.leo new file mode 100644 index 0000000000..6bddeb9ec4 --- /dev/null +++ b/tests/tests/compiler/function/annotated_function.leo @@ -0,0 +1,20 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ + +program test.aleo { + @test + transition test() { + assert(1u32 == 1u32); + } + + @test( + private_key = "APrivateKey1zkp9wLdmfcM57QFL3ZEzgfZwCWV52nM24ckmLSmTQcp64FL", + batch = "0", + seed = "1234" + ) + transition test_other() { + assert(1u32 == 1u32); + } +} diff --git a/tests/tests/compiler/function/annotated_function_fail.leo b/tests/tests/compiler/function/annotated_function_fail.leo index 2eb1425e09..5e09b4d8c5 100644 --- a/tests/tests/compiler/function/annotated_function_fail.leo +++ b/tests/tests/compiler/function/annotated_function_fail.leo @@ -1,10 +1,12 @@ /* namespace = "Compile" -expectation = "Fail" +expectation = "Pass" */ +// This test should pass, but produce warnings about unrecognized and malformed annotations. + program test.aleo { - @test + @foo function foo(a: u8, b: u8) -> u8 { return a + b; } @@ -12,4 +14,10 @@ program test.aleo { @program function bar(a: u8, b: u8) -> u8 { return a * b; - }} + } + + @test(foo) + transition test() { + assert(1u32 == 1u32); + } +} diff --git a/tests/tests/compiler/futures/await_out_of_order.leo b/tests/tests/compiler/futures/await_out_of_order.leo new file mode 100644 index 0000000000..d545c151fc --- /dev/null +++ b/tests/tests/compiler/futures/await_out_of_order.leo @@ -0,0 +1,31 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program test.aleo { + mapping foo: u32 => u32; + async transition main_inner(public a: u32, public b: u32) -> Future { + return finalize(a, b); + } + + async function finalize(a: u32, b: u32) { + Mapping::set(foo, a, b); + } +} + +// --- Next Program --- // + +import test.aleo; +program basic.aleo { + async transition main(public a: u32, b: u32) -> Future { + let f1: Future = test.aleo/main_inner(0u32, 0u32); + let f2: Future = test.aleo/main_inner(1u32, 1u32); + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f2.await(); + f1.await(); + } +} diff --git a/tests/tests/compiler/futures/future_access_tuple_fail.leo b/tests/tests/compiler/futures/future_access_tuple_fail.leo new file mode 100644 index 0000000000..5560859d7e --- /dev/null +++ b/tests/tests/compiler/futures/future_access_tuple_fail.leo @@ -0,0 +1,37 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ +program credits.aleo { + record credits { + owner: address, + amount: u64, + } + + async transition transfer_private_to_public(input: credits, addr: address, amount:u64) -> (credits, Future) { + let f: Future = finalize(); + return (input, f); + } + + async function finalize() { + assert_eq(1u8, 1u8); + } +} + +// --- Next Program --- // + +import credits.aleo; + +program test_credits.aleo { + async transition send_credits(input: credits.aleo/credits, amount: u64) -> (credits.aleo/credits, Future) { + let start: (credits.aleo/credits, Future) = credits.aleo/transfer_private_to_public(input, self.address, amount); + let after: (u8, credits.aleo/credits) = (1u8, start.0); + let start_2: Future = start.1; + return (after.1, finish(start.1, start_2)); + } + + async function finish(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/future_in_composite_fail.leo b/tests/tests/compiler/futures/future_in_composite_fail.leo new file mode 100644 index 0000000000..3c18efa56d --- /dev/null +++ b/tests/tests/compiler/futures/future_in_composite_fail.leo @@ -0,0 +1,22 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ + +program test.aleo { + mapping x: Future => Future; + + struct S { + member: Future, + } + + async transition main() -> Future { + let future: Future = finish(); + let an_array: [Future; 1] = [future]; + return an_array[0u32]; + } + + async function finish() { + assert_eq(1u8, 1u8); + } +} diff --git a/tests/tests/compiler/futures/future_in_tuple_check_fail.leo b/tests/tests/compiler/futures/future_in_tuple_check_fail.leo new file mode 100644 index 0000000000..21051be8c9 --- /dev/null +++ b/tests/tests/compiler/futures/future_in_tuple_check_fail.leo @@ -0,0 +1,35 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ +program credits.aleo { + record credits { + owner: address, + amount: u64, + } + + async transition transfer_private_to_public(input: credits, addr: address, amount:u64) -> (credits, Future) { + let f: Future = finalize(); + return (input, f); + } + + async function finalize() { + assert_eq(1u8, 1u8); + } +} + +// --- Next Program --- // + +import credits.aleo; + +program test_credits.aleo { + async transition send_credits(input: credits.aleo/credits, amount: u64) -> (credits.aleo/credits, Future) { + let result: (credits.aleo/credits, Future) = credits.aleo/transfer_private_to_public(input, self.address, amount); + let result2: (credits.aleo/credits, Future) = credits.aleo/transfer_private_to_public(input, self.address, amount); + return (result.0, finish(result.1)); + } + + async function finish(f: Future) { + f.await(); + } +} diff --git a/tests/tests/compiler/futures/future_parameter_fail.leo b/tests/tests/compiler/futures/future_parameter_fail.leo new file mode 100644 index 0000000000..7beb72fa31 --- /dev/null +++ b/tests/tests/compiler/futures/future_parameter_fail.leo @@ -0,0 +1,22 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ + +program test.aleo { + async transition first(x: Future) -> Future { + return finish(); + } + + transition second(x: Future) -> u8 { + return 1u8; + } + + function third(x: Future) -> u8 { + return 1u8; + } + + async function finish() { + assert_eq(1u8, 1u8); + } +} diff --git a/tests/tests/compiler/futures/misplaced_future_fail.leo b/tests/tests/compiler/futures/misplaced_future_fail.leo new file mode 100644 index 0000000000..c8e06ac59d --- /dev/null +++ b/tests/tests/compiler/futures/misplaced_future_fail.leo @@ -0,0 +1,43 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ + +program child.aleo { + async transition foo() -> Future { + return finalize_foo(0u32); + } + + async function finalize_foo(x: u32) { + assert_eq(1u32, 1u32); + } + + async transition boo() -> (u32, Future) { + return (1u32, finalize_boo(0u32)); + } + + async function finalize_boo(x: u32) { + assert_eq(1u32, 1u32); + } + } + +// --- Next Program --- // + +import child.aleo; + +program parent.aleo { + + async transition foo() -> Future { + let f0: Future = child.aleo/foo(); + + child.aleo/foo(); + + child.aleo/boo(); + + return finalize_foo(f0); + } + + async function finalize_foo(f0: Future) { + f0.await(); + } +} diff --git a/tests/tests/compiler/futures/non_async_after_complex_async.leo b/tests/tests/compiler/futures/non_async_after_complex_async.leo new file mode 100644 index 0000000000..6a205cd4e0 --- /dev/null +++ b/tests/tests/compiler/futures/non_async_after_complex_async.leo @@ -0,0 +1,53 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program inner.aleo { + mapping foo: u32 => u32; + + async transition inner(a: u32) -> Future { + return finalize(a); + } + + async function finalize(a: u32) { + Mapping::set(foo, 0u32, a); + } +} + +// --- Next Program --- // + +import inner.aleo; +program mid.aleo { + async transition mid(a: u32) -> Future { + let f1: Future = inner.aleo/inner(0u32); + let f2: Future = inner.aleo/inner(1u32); + let f:Future = finalize(f2, f1); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } + + transition dummy() {} +} + +// --- Next Program --- // + +import inner.aleo; +import mid.aleo; +program outer.aleo { + async transition outer(a: u32) -> Future { + let f1: Future = mid.aleo/mid(0u32); + let f2: Future = mid.aleo/mid(1u32); + mid.aleo/dummy(); + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/non_async_before_async.leo b/tests/tests/compiler/futures/non_async_before_async.leo new file mode 100644 index 0000000000..d53ffe6e94 --- /dev/null +++ b/tests/tests/compiler/futures/non_async_before_async.leo @@ -0,0 +1,47 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program test.aleo { + mapping foo: u32 => u32; + + async transition main_inner(public a: u32, public b: u32) -> Future { + return finalize(a, b); + } + + async function finalize(a: u32, b: u32) { + Mapping::set(foo, a, b); + } + + transition baz(a: u32, b: u32) -> u32 { + return a + b; + } +} + +// --- Next Program --- // + +import test.aleo; +program basic.aleo { + async transition main(public a: u32, b: u32) -> Future { + let sum1: u32 = test.aleo/baz(a, b); + assert_eq(sum1, 1u32); + + let f1: Future = test.aleo/main_inner(0u32, 0u32); + + let sum2: u32 = test.aleo/baz(a, b); + assert_eq(sum2, 1u32); + + let f2: Future = test.aleo/main_inner(1u32, 1u32); + + let sum3: u32 = test.aleo/baz(a, b); + assert_eq(sum3, 1u32); + + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/non_async_before_complex_async.leo b/tests/tests/compiler/futures/non_async_before_complex_async.leo new file mode 100644 index 0000000000..f31a36b20c --- /dev/null +++ b/tests/tests/compiler/futures/non_async_before_complex_async.leo @@ -0,0 +1,61 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ +program inner.aleo { + mapping foo: u32 => u32; + + async transition inner(a: u32) -> Future { + return finalize(a); + } + + async function finalize(a: u32) { + Mapping::set(foo, 0u32, a); + } +} + +// --- Next Program --- // + +import inner.aleo; +program mid.aleo { + async transition mid(a: u32) -> Future { + let f1: Future = inner.aleo/inner(0u32); + let f2: Future = inner.aleo/inner(1u32); + let f:Future = finalize(f2, f1); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } + + transition dummy() {} +} + +// --- Next Program --- // + +import inner.aleo; +import mid.aleo; +program outer.aleo { + async transition outer_1(a: u32) -> Future { + mid.aleo/dummy(); + let f1: Future = mid.aleo/mid(0u32); + let f2: Future = mid.aleo/mid(1u32); + let f:Future = finalize(f1, f2); + return f; + } + + async transition outer_2(a: u32) -> Future { + let f1: Future = mid.aleo/mid(0u32); + mid.aleo/dummy(); + let f2: Future = mid.aleo/mid(1u32); + let f:Future = finalize(f1, f2); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/compiler/futures/pass_in_out_of_order.leo b/tests/tests/compiler/futures/pass_in_out_of_order.leo new file mode 100644 index 0000000000..b3bcbf38cd --- /dev/null +++ b/tests/tests/compiler/futures/pass_in_out_of_order.leo @@ -0,0 +1,31 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ +program test.aleo { + mapping foo: u32 => u32; + async transition main_inner(public a: u32, public b: u32) -> Future { + return finalize(a, b); + } + + async function finalize(a: u32, b: u32) { + Mapping::set(foo, a, b); + } +} + +// --- Next Program --- // + +import test.aleo; +program basic.aleo { + async transition main(public a: u32, b: u32) -> Future { + let f1: Future = test.aleo/main_inner(0u32, 0u32); + let f2: Future = test.aleo/main_inner(1u32, 1u32); + let f:Future = finalize(f2, f1); + return f; + } + + async function finalize(f1: Future, f2: Future) { + f1.await(); + f2.await(); + } +} diff --git a/tests/tests/interpreter/arithmetic.leo b/tests/tests/interpreter/arithmetic.leo new file mode 100644 index 0000000000..676f82bae1 --- /dev/null +++ b/tests/tests/interpreter/arithmetic.leo @@ -0,0 +1,15 @@ +/* +namespace = "Leo" +expectation = "Pass" +*/ + +program test.aleo { + function f(x: u32, y: u32) -> u32 { + return x + (17u32 * y); + + } + + transition main() -> u32 { + return f(12u32, 100u32); + } +} diff --git a/tests/tests/interpreter/hash.leo b/tests/tests/interpreter/hash.leo new file mode 100644 index 0000000000..bdffddf13b --- /dev/null +++ b/tests/tests/interpreter/hash.leo @@ -0,0 +1,15 @@ +/* +namespace = "Leo" +expectation = "Pass" +*/ + +program test.aleo { + transition main() -> field { + let a: field = 1234567890field; + return BHP256::hash_to_field(a) + * SHA3_256::hash_to_field(a) + * Poseidon2::hash_to_field(a) + * Poseidon4::hash_to_field(a) + * Poseidon8::hash_to_field(a); + } +} diff --git a/utils/disassembler/Cargo.toml b/utils/disassembler/Cargo.toml index ee1eb21975..712806d782 100644 --- a/utils/disassembler/Cargo.toml +++ b/utils/disassembler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-disassembler" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "A disassembler for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/utils/disassembler/src/lib.rs b/utils/disassembler/src/lib.rs index b97752ddce..72444bf4c3 100644 --- a/utils/disassembler/src/lib.rs +++ b/utils/disassembler/src/lib.rs @@ -26,11 +26,11 @@ use snarkvm::{ use std::str::FromStr; pub fn disassemble, Command: CommandTrait>( - program: ProgramCore, + program: &ProgramCore, ) -> Stub { let program_id = ProgramId::from(program.id()); Stub { - imports: program.imports().into_iter().map(|(id, _)| ProgramId::from(id)).collect(), + imports: program.imports().iter().map(|(id, _)| ProgramId::from(id)).collect(), stub_id: program_id, consts: Vec::new(), structs: [ @@ -48,7 +48,7 @@ pub fn disassemble, Command: Comman .concat(), mappings: program .mappings() - .into_iter() + .iter() .map(|(id, m)| (Identifier::from(id).name, Mapping::from_snarkvm(m))) .collect(), functions: [ @@ -88,7 +88,7 @@ pub fn disassemble, Command: Comman pub fn disassemble_from_str(name: &str, program: &str) -> Result { match Program::::from_str(program) { - Ok(p) => Ok(disassemble(p)), + Ok(p) => Ok(disassemble(&p)), Err(_) => Err(UtilError::snarkvm_parsing_error(name, Default::default())), } } @@ -109,7 +109,7 @@ mod tests { let program = Program::::credits(); match program { Ok(p) => { - let disassembled = disassemble(p); + let disassembled = disassemble(&p); println!("{}", disassembled); } Err(e) => { diff --git a/utils/retriever/Cargo.toml b/utils/retriever/Cargo.toml index 491d48f4c6..e5d23c8824 100644 --- a/utils/retriever/Cargo.toml +++ b/utils/retriever/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-retriever" -version = "2.3.0" +version = "2.4.1" authors = [ "The Leo Team " ] description = "A retriever for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/utils/retriever/src/program_context/manifest.rs b/utils/retriever/src/program_context/manifest.rs index d4fb93ba8d..fc5a2aa788 100644 --- a/utils/retriever/src/program_context/manifest.rs +++ b/utils/retriever/src/program_context/manifest.rs @@ -22,11 +22,18 @@ use std::path::Path; // Struct representation of program's `program.json` specification #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Manifest { - program: String, - version: String, - description: String, - license: String, - dependencies: Option>, + /// The name of the program + pub program: String, + /// The version of the program. + pub version: String, + /// The description of the program. + pub description: String, + /// The license of the program. + pub license: String, + /// The dependencies of the program. + pub dependencies: Option>, + /// The dev dependencies of the program. These dependencies are only available during testing. + pub dev_dependencies: Option>, } impl Manifest { @@ -36,6 +43,7 @@ impl Manifest { description: &str, license: &str, dependencies: Option>, + dev_dependencies: Option>, ) -> Self { Self { program: program.to_owned(), @@ -43,6 +51,7 @@ impl Manifest { description: description.to_owned(), license: license.to_owned(), dependencies, + dev_dependencies, } } @@ -53,6 +62,7 @@ impl Manifest { description: "".to_owned(), license: "MIT".to_owned(), dependencies: None, + dev_dependencies: None, } } @@ -76,6 +86,10 @@ impl Manifest { &self.dependencies } + pub fn dev_dependencies(&self) -> &Option> { + &self.dev_dependencies + } + pub fn write_to_dir(&self, path: &Path) -> Result<(), PackageError> { // Serialize the manifest to a JSON string. let contents = serde_json::to_string_pretty(&self) diff --git a/utils/retriever/src/retriever/mod.rs b/utils/retriever/src/retriever/mod.rs index fdd29a1436..753def99e4 100644 --- a/utils/retriever/src/retriever/mod.rs +++ b/utils/retriever/src/retriever/mod.rs @@ -291,40 +291,33 @@ impl Retriever { // Creates the stub of the program, caches it, and writes the local `leo.lock` file pub fn process_local(&mut self, name: Symbol, recursive: bool) -> Result<(), UtilError> { let cur_context = self.contexts.get_mut(&name).unwrap(); - // Don't need to disassemble the main file - if name != self.name { - // Disassemble the program - let compiled_path = cur_context.compiled_file_path(); - if !compiled_path.exists() { - return Err(UtilError::build_file_does_not_exist(compiled_path.to_str().unwrap(), Default::default())); - } - let mut file = File::open(compiled_path).unwrap_or_else(|_| { - panic!("Failed to open file {}", cur_context.compiled_file_path().to_str().unwrap()) - }); - let mut content = String::new(); - file.read_to_string(&mut content).map_err(|err| { - UtilError::util_file_io_error( - format!("Could not read {}", cur_context.compiled_file_path().to_str().unwrap()), - err, - Default::default(), - ) - })?; + // Disassemble the program + let compiled_path = cur_context.compiled_file_path(); + if !compiled_path.exists() { + return Err(UtilError::build_file_does_not_exist(compiled_path.to_str().unwrap(), Default::default())); + } + let mut file = File::open(compiled_path) + .unwrap_or_else(|_| panic!("Failed to open file {}", cur_context.compiled_file_path().to_str().unwrap())); + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|err| { + UtilError::util_file_io_error( + format!("Could not read {}", cur_context.compiled_file_path().to_str().unwrap()), + err, + Default::default(), + ) + })?; - // Cache the disassembled stub - let stub: Stub = disassemble_from_str::(&name.to_string(), &content)?; - if cur_context.add_stub(stub.clone()) { - Err(UtilError::duplicate_dependency_name_error(stub.stub_id.name.name, Default::default()))?; - } + // Cache the disassembled stub + let stub: Stub = disassemble_from_str::(&name.to_string(), &content)?; + if cur_context.add_stub(stub.clone()) { + Err(UtilError::duplicate_dependency_name_error(stub.stub_id.name.name, Default::default()))?; + } - // Cache the hash - cur_context.add_checksum(); + // Cache the hash + cur_context.add_checksum(); - // Only write lock file when recursive building - if recursive { - self.write_lock_file(&name)?; - } - } else { - // Write lock file + // Only write lock file when recursive building or when building the top-level program. + if recursive || name == self.name { self.write_lock_file(&name)?; } @@ -416,11 +409,16 @@ fn retrieve_local(name: &String, path: &PathBuf) -> Result, Util ))?; } - let dependencies = match program_data.dependencies() { + let mut dependencies = match program_data.dependencies() { Some(deps) => deps.clone(), None => Vec::new(), }; + // Add the dev dependencies, if they exist. + if let Some(deps) = program_data.dev_dependencies() { + dependencies.extend(deps.clone()) + } + Ok(dependencies) }