diff --git a/.github/assets/revive-dev-node-polkavm-resolc.json b/.github/assets/revive-dev-node-polkavm-resolc.json new file mode 100644 index 00000000..2bbac41f --- /dev/null +++ b/.github/assets/revive-dev-node-polkavm-resolc.json @@ -0,0 +1,3 @@ +{ + "fixtures/solidity/simple/algorithm/cryptography/caesar_cypher.sol::2::Y M0 S-": "Failed" +} diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 04657bc4..e8e546f5 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -37,6 +37,9 @@ on: env: CARGO_TERM_COLOR: always RUST_MUSL_CROSS_IMAGE: messense/rust-musl-cross@sha256:2a8837c43bf12e246f1ebd05191de9ee27fcd22f9ca81511ccd4cf75dc16d71c + # Empty string is interpreted as the latest published release (default behavior). + # To test a specific draft release, use the full name (e.g. "llvm-21.1.8-revive.5789e11"). + LLVM_VERSION: "" jobs: build: @@ -51,6 +54,23 @@ jobs: resolc-x86_64-apple-darwin_sha: ${{ steps.set-output.outputs.resolc-x86_64-apple-darwin_sha }} resolc-x86_64-pc-windows-msvc_url: ${{ steps.set-output.outputs.resolc-x86_64-pc-windows-msvc_url }} resolc-x86_64-pc-windows-msvc_sha: ${{ steps.set-output.outputs.resolc-x86_64-pc-windows-msvc_sha }} + # Queue jobs for the same target and commit instead of running in parallel or cancelling + # the first job. This allows the second workflow to use cached builds from the first if + # it is triggered by the same commit. + # + # Example: Workflow A and Workflow B both call this workflow for the same commit: + # + # Time | Workflow A | Workflow B + # -----|---------------------------|--------------------------- + # T1 | Job starts, cache miss | + # T2 | Building... | Job queued (same group) + # T3 | Build done, cache saved | Job queued + # T4 | Job finishes | Job starts, cache hit + # T5 | | Skips build, job finishes + concurrency: + # Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group. + group: reusable-build-${{ matrix.target }}-${{ github.sha }} + cancel-in-progress: false strategy: matrix: target: @@ -81,18 +101,31 @@ jobs: if: runner.os == 'Windows' run: git config --system core.longpaths true - uses: actions/checkout@v4 + + # Cache build artifacts by target and commit (restores if available, saves on cache miss). + - name: Check Build Cache + id: cache + uses: actions/cache@v5 + with: + # Use glob to match binaries with and without extensions. + path: resolc-${{ matrix.target }}* + key: build-${{ matrix.target }}-${{ github.sha }} + - uses: actions-rust-lang/setup-rust-toolchain@v1 + if: ${{ steps.cache.outputs.cache-hit != 'true' }} with: rustflags: "" cache-key: ${{ matrix.target }} - name: Download LLVM + if: ${{ steps.cache.outputs.cache-hit != 'true' }} uses: ./.github/actions/get-llvm with: target: ${{ matrix.target }} + version: ${{ env.LLVM_VERSION }} - name: Build (Native) - if: ${{ matrix.type == 'native' }} + if: ${{ steps.cache.outputs.cache-hit != 'true' && matrix.type == 'native' }} shell: bash run: | export LLVM_SYS_211_PREFIX=$PWD/llvm-${{ matrix.target }} @@ -100,7 +133,7 @@ jobs: mv target/release/resolc resolc-${{ matrix.target }} || mv target/release/resolc.exe resolc-${{ matrix.target }}.exe - name: Build (MUSL) - if: ${{ matrix.type == 'musl' }} + if: ${{ steps.cache.outputs.cache-hit != 'true' && matrix.type == 'musl' }} run: | docker run -v $PWD:/opt/revive $RUST_MUSL_CROSS_IMAGE /bin/bash -c " cd /opt/revive @@ -142,32 +175,55 @@ jobs: outputs: resolc_web_js_url: ${{ steps.set-output.outputs.resolc_web_js_url }} resolc_web_js_sha: ${{ steps.set-output.outputs.resolc_web_js_sha }} + # Queue jobs for the same target and commit instead of running in parallel or cancelling + # the first job. This allows the second workflow to use cached builds from the first if + # it is triggered by the same commit. + concurrency: + # Use `reusable-build` and not `${{ github.workflow }}` here so that callers share the same concurrency group. + group: reusable-build-wasm32-unknown-emscripten-${{ github.sha }} + cancel-in-progress: false env: RELEASE_RESOLC_WASM_URI: https://github.com/paritytech/revive/releases/download/${{ github.ref_name }}/resolc.wasm steps: - uses: actions/checkout@v4 + + # Cache build artifacts by commit (restores if available, saves on cache miss). + - name: Check Build Cache + id: cache + uses: actions/cache@v5 + with: + path: resolc-wasm32-unknown-emscripten + key: build-wasm32-unknown-emscripten-${{ github.sha }} + # Pin to 1.92.0 until LLVM WASM libraries are rebuilt with -fwasm-exceptions # See: https://github.com/paritytech/revive/issues/XXX - uses: actions-rust-lang/setup-rust-toolchain@v1 + if: ${{ steps.cache.outputs.cache-hit != 'true' }} with: toolchain: "1.92.0" target: wasm32-unknown-emscripten rustflags: "" - name: Download Host LLVM + if: ${{ steps.cache.outputs.cache-hit != 'true' }} uses: ./.github/actions/get-llvm with: target: x86_64-unknown-linux-gnu + version: ${{ env.LLVM_VERSION }} - name: Download Wasm LLVM + if: ${{ steps.cache.outputs.cache-hit != 'true' }} uses: ./.github/actions/get-llvm with: target: wasm32-unknown-emscripten + version: ${{ env.LLVM_VERSION }} - name: Download EMSDK + if: ${{ steps.cache.outputs.cache-hit != 'true' }} uses: ./.github/actions/get-emsdk - name: Build + if: ${{ steps.cache.outputs.cache-hit != 'true' }} run: | export LLVM_SYS_211_PREFIX=$PWD/llvm-x86_64-unknown-linux-gnu export REVIVE_LLVM_TARGET_PREFIX=$PWD/llvm-wasm32-unknown-emscripten @@ -175,6 +231,14 @@ jobs: make install-wasm chmod -x ./target/wasm32-unknown-emscripten/release/resolc.wasm + - name: Compress Artifact + if: ${{ steps.cache.outputs.cache-hit != 'true' }} + run: | + mkdir -p resolc-wasm32-unknown-emscripten + mv ./target/wasm32-unknown-emscripten/release/resolc.js ./resolc-wasm32-unknown-emscripten/ + mv ./target/wasm32-unknown-emscripten/release/resolc.wasm ./resolc-wasm32-unknown-emscripten/ + mv ./target/wasm32-unknown-emscripten/release/resolc_web.js ./resolc-wasm32-unknown-emscripten/ + - name: Set Up Node.js uses: actions/setup-node@v3 with: @@ -185,8 +249,11 @@ jobs: mkdir -p solc curl -sSLo solc/soljson.js https://github.com/argotorg/solidity/releases/download/v0.8.34/soljson.js node -e " - const soljson = require('solc/soljson'); - const createRevive = require('./target/wasm32-unknown-emscripten/release/resolc.js'); + // Make sure to require './solc/soljson' and not 'solc/soljson' in order to use + // the explicitly downloaded solc, rather than resolving an npm package potentially + // installed in prior workflow jobs. + const soljson = require('./solc/soljson'); + const createRevive = require('./resolc-wasm32-unknown-emscripten/resolc.js'); const compiler = createRevive(); compiler.soljson = soljson; @@ -224,13 +291,6 @@ jobs: if(!bytecode.startsWith('50564d')) { process.exit(1); } " - - name: Compress Artifact - run: | - mkdir -p resolc-wasm32-unknown-emscripten - mv ./target/wasm32-unknown-emscripten/release/resolc.js ./resolc-wasm32-unknown-emscripten/ - mv ./target/wasm32-unknown-emscripten/release/resolc.wasm ./resolc-wasm32-unknown-emscripten/ - mv ./target/wasm32-unknown-emscripten/release/resolc_web.js ./resolc-wasm32-unknown-emscripten/ - - name: Upload artifacts (Release) if: ${{ inputs.is_release }} uses: actions/upload-artifact@v4 diff --git a/.github/workflows/test-builds.yml b/.github/workflows/test-builds.yml new file mode 100644 index 00000000..7fac1a90 --- /dev/null +++ b/.github/workflows/test-builds.yml @@ -0,0 +1,192 @@ +name: Verify Reproducible Builds + +on: + workflow_call: + +env: + # The compiler modes to use for compilation by retester. + MODES: "Y M0 S+, Y M3 S+, Y Mz S+" + SOLC_VERSION: "0.8.33" + +jobs: + build: + uses: ./.github/workflows/reusable-build.yml + with: + is_release: false + retention_days: 1 + + create-macos-universal: + needs: build + runs-on: macos-15 + steps: + - name: Download MacOS artifacts + uses: actions/download-artifact@v4 + with: + pattern: resolc-*-apple-darwin + merge-multiple: true + + - name: Create MacOS Fat Binary + run: | + lipo resolc-aarch64-apple-darwin resolc-x86_64-apple-darwin -create -output resolc-universal-apple-darwin + + - name: Upload Universal Binary + uses: actions/upload-artifact@v4 + with: + name: resolc-universal-apple-darwin + path: resolc-universal-apple-darwin + retention-days: 1 + + compile-and-export-hashes-native: + needs: [build, create-macos-universal] + strategy: + fail-fast: false + matrix: + include: + - platform: linux + target: x86_64-unknown-linux-musl + runner: ubuntu-24.04 + - platform: macos + target: universal-apple-darwin + runner: macos-15 + - platform: windows + target: x86_64-pc-windows-msvc + runner: windows-2022 + runs-on: ${{ matrix.runner }} + steps: + # For some deeply nested contracts in `resolc-compiler-tests`, Windows + # will generate a "Filename too long" error. This step enables long paths. + - name: Enable Git Long Paths (Windows) + if: ${{ runner.os == 'Windows' }} + run: git config --system core.longpaths true + + - name: Checkout revive + uses: actions/checkout@v4 + with: + path: revive + submodules: recursive + + - name: Download Binary + uses: actions/download-artifact@v4 + with: + name: resolc-${{ matrix.target }} + path: resolc-bin + + - name: Make resolc Executable + shell: bash + run: chmod +x "resolc-bin/resolc-${{ matrix.target }}${{ runner.os == 'Windows' && '.exe' || '' }}" + + - name: Compile Contracts + id: compile + uses: ./revive/revive-differential-tests/.github/actions/compile-contracts + with: + revive-differential-tests-path: revive/revive-differential-tests + resolc-path: resolc-bin/resolc-${{ matrix.target }}${{ runner.os == 'Windows' && '.exe' || '' }} + solc-version: ${{ env.SOLC_VERSION }} + modes: ${{ env.MODES }} + upload-artifact-name: compilation-report-${{ matrix.platform }} + + - name: Export Bytecode Hashes + uses: ./revive/revive-differential-tests/.github/actions/export-bytecode-hashes + with: + revive-differential-tests-path: revive/revive-differential-tests + report-path: ${{ steps.compile.outputs.report-path }} + remove-prefix: ${{ steps.compile.outputs.contracts-source-prefix }} + platform-label: ${{ matrix.platform }} + upload-artifact-name: hashes-${{ matrix.platform }} + + compare-hashes: + needs: compile-and-export-hashes-native + runs-on: ubuntu-24.04 + steps: + - name: Checkout revive + uses: actions/checkout@v4 + with: + path: revive + submodules: true + + - name: Compare Bytecode Hashes + uses: ./revive/revive-differential-tests/.github/actions/compare-bytecode-hashes + with: + revive-differential-tests-path: revive/revive-differential-tests + hash-artifact-names: "hashes-linux, hashes-macos, hashes-windows" + modes: ${{ env.MODES }} + upload-artifact-name: hash-comparison-result + + run-differential-tests: + needs: build + runs-on: parity-large + timeout-minutes: 75 + env: + # TODO: Which Polkadot SDK commit do we want to use? + # The below one is as of March. 25, 2026. + POLKADOT_SDK_REF: "9d8a2fefd2414591c4a243460f3ffc5195a71deb" + RUSTFLAGS: "-Awarnings" + steps: + - name: Checkout revive + uses: actions/checkout@v4 + with: + path: revive + submodules: recursive + + - name: Download Binary + uses: actions/download-artifact@v4 + with: + name: resolc-x86_64-unknown-linux-musl + path: ./resolc-bin + + - name: Make resolc Executable + shell: bash + run: chmod +x ./resolc-bin/resolc-x86_64-unknown-linux-musl + + - name: Checkout Polkadot SDK + uses: actions/checkout@v4 + with: + repository: paritytech/polkadot-sdk + ref: ${{ env.POLKADOT_SDK_REF }} + path: polkadot-sdk + submodules: recursive + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential pkg-config libssl-dev protobuf-compiler llvm clang libclang-dev + + - name: Install forklift + run: | + curl -fsSLo forklift https://github.com/paritytech/forklift/releases/download/0.15.0/forklift_0.15.0_linux_amd64 + chmod +x forklift + sudo mv forklift /usr/local/bin/ + + - name: Configure forklift + run: | + mkdir -p .forklift + cp polkadot-sdk/.forklift/config.toml .forklift/config.toml + + - name: Set Up Rust Toolchain + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: "1.90.0" + target: "wasm32-unknown-unknown" + components: "rust-src" + rustflags: "" + + - name: Run Differential E2E Tests + uses: ./revive/revive-differential-tests/.github/actions/run-differential-tests + with: + revive-differential-tests-path: revive/revive-differential-tests + polkadot-sdk-path: ./polkadot-sdk + cargo-command: "forklift cargo" + platform: revive-dev-node-polkavm-resolc + resolc-path: ./resolc-bin/resolc-x86_64-unknown-linux-musl + solc-version: ${{ env.SOLC_VERSION }} + expectations-file-path: ./revive/.github/assets/revive-dev-node-polkavm-resolc.json + + # Sentinel job that depends on the jobs we want to allow completing (instead of + # being canceled once the other job chains complete). Thus, run-differential-tests + # will always run even if compare-hashes finishes first, and vice versa. + wait-for-jobs: + if: ${{ always() }} + needs: [compare-hashes, run-differential-tests] + runs-on: ubuntu-24.04 + steps: + - run: echo "All jobs finished" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2d0b95b3..496d93e2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,3 +60,8 @@ jobs: - name: Test docs run: make doc + + test-builds: + # Only run this if `test` successfully completes. + needs: test + uses: ./.github/workflows/test-builds.yml diff --git a/revive-differential-tests b/revive-differential-tests index 004a36f4..4148afc6 160000 --- a/revive-differential-tests +++ b/revive-differential-tests @@ -1 +1 @@ -Subproject commit 004a36f4e88228ee1331cb598c8873ab7315e3f0 +Subproject commit 4148afc60640c4b85e3daaf4325bce7288dc77d4