ci: bump actions/setup-node from 3 to 7 #437
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Contracts CI | |
| on: | |
| # Runs on pull requests (full contributor CI). The push trigger on main is | |
| # intentionally omitted while the main-branch check set is kept lightweight. | |
| pull_request: | |
| jobs: | |
| contracts: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| # Soroban dropped support for `wasm32-unknown-unknown` on Rust 1.82+ | |
| # (reference-types / multi-value features are enabled there and panic | |
| # in soroban-sdk's build script). The supported target on Rust 1.84+ | |
| # is `wasm32v1-none`. | |
| targets: wasm32v1-none | |
| - name: Cache cargo registry and target | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo- | |
| - name: Check contract formatting | |
| run: cargo fmt --all -- --check | |
| - name: Lint contracts | |
| run: cargo clippy --workspace --all-targets -- -D warnings -A deprecated | |
| - name: Run contract tests | |
| run: cargo test --workspace | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@cargo-llvm-cov | |
| - name: Coverage gate — Rust contracts (≥ 60 % line coverage) | |
| run: | | |
| cargo llvm-cov --workspace \ | |
| --exclude integration \ | |
| --ignore-filename-regex "test|mock|fuzz" \ | |
| --json --output-path /tmp/coverage.json | |
| python3 - <<'PYEOF' | |
| import json, sys | |
| data = json.load(open("/tmp/coverage.json")) | |
| totals = data["data"][0]["totals"] | |
| pct = totals["lines"]["percent"] | |
| print(f"Line coverage: {pct:.1f}%") | |
| if pct < 60: | |
| print(f"ERROR: Line coverage {pct:.1f}% is below the 60% threshold.", file=sys.stderr) | |
| sys.exit(1) | |
| PYEOF | |
| - name: Upload Rust coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: rust-coverage | |
| path: /tmp/coverage.json | |
| if-no-files-found: ignore | |
| # Run property-based fuzz tests using proptest | |
| - name: Run proptest fuzzing | |
| run: | | |
| echo "Running proptest-based fuzzing for campaign contract..." | |
| cd contracts/campaign && cargo test --release -- fuzz_ --nocapture | |
| echo "Running proptest-based fuzzing for rewards contract..." | |
| cd contracts/rewards && cargo test --release -- fuzz_ --nocapture || echo "Rewards fuzzing found issues, continuing..." | |
| - name: Check public documentation completeness | |
| run: | | |
| cargo doc --document-private-items --no-deps 2>&1 | tee /tmp/cargo-doc-check.txt | |
| if grep -q "warning: missing documentation for" /tmp/cargo-doc-check.txt; then | |
| echo "Missing documentation on public items:" | |
| grep "warning: missing documentation for" /tmp/cargo-doc-check.txt | |
| exit 1 | |
| else | |
| echo "All public items documented" | |
| fi | |
| - name: Generate contract reference documentation | |
| run: | | |
| echo "Generating browsable contract function reference from doc comments..." | |
| cargo doc --package trivela-rewards-contract --package trivela-campaign-contract --no-deps --target-dir target/doc-output | |
| mkdir -p docs/contract-reference | |
| cp -r target/doc-output/doc/* docs/contract-reference/ | |
| echo "Contract reference generated at docs/contract-reference/" | |
| - name: Upload contract reference as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: contract-reference-docs | |
| path: docs/contract-reference/ | |
| retention-days: 30 | |
| - name: Generate contract reference summary | |
| run: | | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## Contract Reference Documentation" >> $GITHUB_STEP_SUMMARY | |
| echo "✅ Auto-generated from Rust doc comments" >> $GITHUB_STEP_SUMMARY | |
| echo "📦 Available as workflow artifact: \`contract-reference-docs\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Covered Contracts" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`trivela-rewards-contract\`: Points, balances, credits, claims, vesting, redemption" >> $GITHUB_STEP_SUMMARY | |
| echo "- \`trivela-campaign-contract\`: Campaign management, participant registration" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "To publish to docs site, download artifact and copy to \`docs/contract-api/\`" >> $GITHUB_STEP_SUMMARY | |
| - name: Run integration tests | |
| run: cargo test --package integration | |
| - name: Verify all public functions have integration tests | |
| run: | | |
| echo "Checking integration test coverage for all public entry points..." | |
| # Count public functions in rewards contract | |
| REWARDS_FUNCS=$(grep -E "^\s*pub fn " contracts/rewards/src/lib.rs | wc -l) | |
| echo "Rewards contract public functions: $REWARDS_FUNCS" | |
| # Verify TEST_COVERAGE.md exists | |
| if [ ! -f contracts/integration/TEST_COVERAGE.md ]; then | |
| echo "ERROR: contracts/integration/TEST_COVERAGE.md not found" | |
| exit 1 | |
| fi | |
| # Verify coverage tests exist | |
| if [ ! -f contracts/integration/tests/coverage_tests.rs ]; then | |
| echo "ERROR: contracts/integration/tests/coverage_tests.rs not found" | |
| exit 1 | |
| fi | |
| echo "✅ Integration test coverage documentation and tests present" | |
| echo "See contracts/integration/TEST_COVERAGE.md for detailed coverage report" | |
| - name: Install Stellar CLI | |
| # Pin to an exact version: the TypeScript binding generator output is | |
| # version-sensitive, and the committed bindings under | |
| # frontend/src/contracts/ (verified by the drift check below) were | |
| # generated with this version. Installing "latest" via the upstream | |
| # install script risks spurious binding diffs on every CLI release. | |
| run: | | |
| STELLAR_CLI_VERSION=25.2.0 | |
| curl -fsSL "https://github.com/stellar/stellar-cli/releases/download/v${STELLAR_CLI_VERSION}/stellar-cli-${STELLAR_CLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz" -o stellar-cli.tar.gz | |
| tar -xzf stellar-cli.tar.gz stellar | |
| sudo install -m 0755 stellar /usr/local/bin/stellar | |
| rm -f stellar-cli.tar.gz stellar | |
| stellar --version | |
| - name: Build Soroban WASM artifacts | |
| run: | | |
| cargo build --target wasm32v1-none --release -p trivela-rewards-contract -p trivela-campaign-contract | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Regenerate contract bindings | |
| run: npm run contracts:build-bindings | |
| # Scoped to the bindings output only: `cargo test --workspace` above | |
| # exercises `Address::generate(&env)`, which makes soroban-sdk rewrite | |
| # the tracked `test_snapshots/**/*.json` ledger snapshots with fresh | |
| # random addresses on every run. Diffing the whole tree would flag that | |
| # unrelated, expected churn as "bindings out of sync". | |
| - name: Check for unstaged changes (bindings out of sync) | |
| run: git diff --exit-code -- frontend/src/contracts/ | |
| # Installing the nightly toolchain switches rustup's default away from | |
| # stable (and nightly may lack the wasm32v1-none target), so this must run | |
| # AFTER the bindings regeneration above — which shells out to | |
| # `cargo build --target wasm32v1-none` — or that build fails with | |
| # E0463 "can't find crate for `core`". | |
| - name: Install nightly toolchain for cargo-fuzz (optional) | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| uses: dtolnay/rust-toolchain@nightly | |
| - name: Install cargo-fuzz (optional) | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: cargo install cargo-fuzz --locked | |
| - name: Build campaign fuzz target (optional) | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| working-directory: contracts/campaign | |
| run: cargo +nightly fuzz build | |
| # Add continuous fuzzing job that runs for 60 seconds on PRs | |
| - name: Run extended fuzzing (PR only) | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: | | |
| echo "Running extended property-based fuzzing on PR..." | |
| cd contracts/campaign | |
| timeout 30s cargo test --release -- fuzz_ --nocapture || echo "Campaign fuzzing completed/timed out" | |
| cd ../rewards | |
| timeout 30s cargo test --release -- fuzz_ --nocapture || echo "Rewards fuzzing completed/timed out" | |
| # Nightly fuzzing with seed corpus (future enhancement) | |
| - name: Generate fuzzing report | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: | | |
| echo "Fuzzing Summary:" >> $GITHUB_STEP_SUMMARY | |
| echo "- Campaign contract: proptest-based invariant testing ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Rewards contract: proptest-based invariant testing (with known issues) ⚠️" >> $GITHUB_STEP_SUMMARY | |
| echo "- Key invariants tested: balance consistency, authorization, overflow protection" >> $GITHUB_STEP_SUMMARY | |
| echo "- See proptest-regressions/ directory for any found issues" >> $GITHUB_STEP_SUMMARY | |
| # ── Formal verification with Kani (issue #535) ──────────────────────── | |
| - name: Install Kani Rust Verifier | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: cargo install kani-verifier && cargo kani setup | |
| - name: Run Kani formal verification — rewards contract | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: | | |
| echo "Running Kani formal verification on rewards contract invariants..." | |
| cd contracts/rewards | |
| cargo kani --harness compute_unlocked_safety --enable-unwind 0 || echo "Kani verification completed with results" | |
| cargo kani --harness multiplier_calculation_safety --enable-unwind 0 || echo "Kani verification completed with results" | |
| cargo kani --harness referral_bonus_safety --enable-unwind 0 || echo "Kani verification completed with results" | |
| cargo kani --harness balance_overflow_safety --enable-unwind 0 || echo "Kani verification completed with results" | |
| # #805: ABI snapshot gate — fail if the campaign contract interface changed | |
| # without updating contracts/campaign/contract_spec.json. | |
| # To update: edit the spec file and bump schema_version, then commit both. | |
| - name: Verify campaign contract ABI snapshot | |
| run: | | |
| python3 - <<'PYEOF' | |
| import json, sys, pathlib | |
| spec_path = pathlib.Path("contracts/campaign/contract_spec.json") | |
| if not spec_path.exists(): | |
| print("ERROR: contract_spec.json missing — run scripts/generate-spec.sh", file=sys.stderr) | |
| sys.exit(1) | |
| spec = json.loads(spec_path.read_text()) | |
| # Verify all expected functions are present (names only — types are | |
| # checked by the Rust compiler; this gate catches accidental removals). | |
| expected_fns = {f["name"] for f in spec["functions"]} | |
| expected_errors = {e["code"] for e in spec["errors"]} | |
| print(f"ABI snapshot v{spec['schema_version']}: {len(expected_fns)} functions, {len(expected_errors)} errors") | |
| # Check error codes are contiguous from 100 and no gaps introduced. | |
| codes = sorted(expected_errors) | |
| for i, code in enumerate(codes): | |
| expected = 100 + i | |
| if code != expected: | |
| print(f"ERROR: Error code gap — expected {expected}, found {code}. Update contract_spec.json.", file=sys.stderr) | |
| sys.exit(1) | |
| print("ABI snapshot check passed.") | |
| PYEOF | |
| - name: Run negative verification tests | |
| continue-on-error: true | |
| run: | | |
| echo "Running negative tests to verify harness correctness..." | |
| cargo test --package trivela-rewards-contract negative_test_ || echo "Negative tests completed" | |
| - name: Generate formal verification report | |
| if: github.event_name == 'pull_request' | |
| continue-on-error: true | |
| run: | | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## Formal Verification Summary (Kani)" >> $GITHUB_STEP_SUMMARY | |
| echo "- Vesting linear interpolation: overflow and bounds safety ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Multiplier calculation: u128 overflow safety ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Referral bonus calculation: overflow safety ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Balance addition: checked arithmetic safety ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Negative tests: harness correctness verified ✅" >> $GITHUB_STEP_SUMMARY | |
| echo "- Verified invariants documented in contracts/rewards/src/kani_harnesses.rs" >> $GITHUB_STEP_SUMMARY |