Skip to content

fix: repair the source corruption that left main unable to compile #383

fix: repair the source corruption that left main unable to compile

fix: repair the source corruption that left main unable to compile #383

Workflow file for this run

name: CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
types: [ opened, synchronize, reopened, labeled, unlabeled ]
jobs:
changelog:
name: changelog & version check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Check CHANGELOG enforcement and version matching
if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip-changelog')
run: |
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.sha }}"
# Find changed files in PR
CHANGED_FILES=$(git diff --name-only $BASE $HEAD)
# Check if contract source files are modified
CONTRACT_CHANGED=$(echo "$CHANGED_FILES" | grep -E '^contracts/.*/src/' || true)
if [ -n "$CONTRACT_CHANGED" ]; then
echo "Contract source files modified:"
echo "$CONTRACT_CHANGED"
# Check if CHANGELOG.md is in modified files
if ! echo "$CHANGED_FILES" | grep -q '^CHANGELOG.md$'; then
echo "Error: CHANGELOG.md was not updated for contract source changes."
echo "Add a changelog entry under ## [Unreleased], or add the 'skip-changelog' label to your PR for internal changes."
exit 1
fi
echo "CHANGELOG.md update confirmed."
fi
- name: Verify CHANGELOG released version matches workspace Cargo.toml
run: |
# Extract highest released version from CHANGELOG.md (excluding Unreleased)
CHANGELOG_VER=$(grep -E '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md | head -n1 | sed -E 's/## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/')
# Extract version from receipt-anchor and refund-vault Cargo.toml
RECEIPT_VER=$(grep '^version =' contracts/receipt-anchor/Cargo.toml | head -n1 | cut -d '"' -f 2)
REFUND_VER=$(grep '^version =' contracts/refund-vault/Cargo.toml | head -n1 | cut -d '"' -f 2)
echo "CHANGELOG top released version: $CHANGELOG_VER"
echo "receipt-anchor version: $RECEIPT_VER"
echo "refund-vault version: $REFUND_VER"
if [ "$CHANGELOG_VER" != "$RECEIPT_VER" ] || [ "$CHANGELOG_VER" != "$REFUND_VER" ]; then
echo "Error: CHANGELOG.md top released version ($CHANGELOG_VER) does not match crate versions ($RECEIPT_VER, $REFUND_VER)."
exit 1
fi
echo "Version matching confirmed."
fmt:
name: fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Run fmt
run: cargo fmt --all -- --check
clippy:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
targets: wasm32v1-none
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}
- name: Build the wasms the lint targets import
# `--all-targets` compiles the test targets too, and those pull in
# three contracts via `contractimport!`. Same set as the test job.
run: |
cargo build --target wasm32v1-none --release \
-p receipt-shard -p receipt-anchor -p refund-vault
- name: Run clippy
run: cargo clippy --locked --all-targets -- -D warnings
test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-test-${{ hashFiles('**/Cargo.lock') }}
- name: Build the wasms the tests import
# Three contracts are pulled in via `contractimport!`: receipt-shard
# and receipt-anchor for the shard-factory tests, and refund-vault for
# refund-vault-factory's deploy_vault tests. All three must exist as
# built wasm before `cargo test`, or the importing crate fails to
# compile with a bare "No such file or directory".
run: |
cargo build --target wasm32v1-none --release \
-p receipt-shard -p receipt-anchor -p refund-vault
- name: Run Tests
run: cargo test --locked --workspace
benchmarks:
name: benchmark-budgets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-bench-${{ hashFiles('**/Cargo.lock') }}
- name: Run Budget Measurement Tests
# Placeholder for actual budget enforcement (Issue #66).
# Ensures that the testutils budget harness compiles and works.
run: cargo test --locked -p testutils --profile release-with-logs
budget:
name: budget-limits
runs-on: ubuntu-latest
env:
# Stated tolerance for committed baselines. A measured value that exceeds
# baseline * (1 + tolerance) fails the build. Wasm size: 10%. Budget
# assertions (Tier A macros / Tier B --check): 15% (see budget.toml).
WASM_SIZE_TOLERANCE: 0.10
# Pinned tool versions (Tollcraft). Bump these deliberately when the
# upstream tools release and you have re-measured the baselines.
SOROBAN_COST_LINTER_TAG: v0.1.1
SOROBAN_COST_LINTER_NIGHTLY: nightly-2026-04-16
DYLINT_VERSION: 6.0.1
# soroban-budget-assert is distributed from git (not crates.io). Pin to
# the released tag: upstream `main` is not guaranteed to compile (it can
# carry in-progress work, e.g. #592 broke cargo-budget-report's main.rs).
SOROBAN_BUDGET_ASSERT_REF: v0.1.0
# Funded testnet identity for the Tier B network-simulated report. Empty in
# forks / untrusted runs, so the network step is skipped there.
STELLAR_TESTNET_SECRET: ${{ secrets.STELLAR_TESTNET_SECRET }}
steps:
- uses: actions/checkout@v7
- name: Setup Rust (stable + wasm)
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-budget-${{ hashFiles('**/Cargo.lock') }}
# --- Build every contract to WASM (needed by the Tier A macro suite) ---
- name: Wasm build
# Exclude `testutils`: it is a std crate (no `#![no_std]`) and its
# soroban-sdk `testutils` feature is not supported on wasm, so it
# cannot compile for wasm32v1-none (same rationale as the build-wasm
# job). Only the contract crates are needed for the Tier A macros.
run: cargo build --locked --workspace --exclude testutils --target wasm32v1-none --release
# --- WASM size ceiling (committed baseline + tolerance) ---
- name: Enforce WASM size budget
run: |
TOL=$WASM_SIZE_TOLERANCE
FAILED=0
PCT=$(awk "BEGIN{printf \"%.0f\", $TOL*100}")
echo "### WASM Build Sizes" >> $GITHUB_STEP_SUMMARY
echo "| Contract | Size (bytes) | Budget (bytes) | +${PCT}% ceiling | Status |" >> $GITHUB_STEP_SUMMARY
echo "|---|---|---|---|---|" >> $GITHUB_STEP_SUMMARY
for pair in "receipt_anchor:receipt_anchor.wasm" "refund_vault:refund_vault.wasm" "refund_policy_time:refund_policy_time.wasm" "refund_policy_vdf:refund_policy_vdf.wasm" "refund_vault_factory:refund_vault_factory.wasm"; do
name="${pair%%:*}"; wasm="${pair##*:}"
SIZE=$(stat -c%s "target/wasm32v1-none/release/$wasm" 2>/dev/null || stat -f%z "target/wasm32v1-none/release/$wasm")
BUDGET=$(jq -r ".$name" .wasm-budget.json)
CEIL=$(awk "BEGIN{printf \"%d\", $BUDGET*(1+$TOL)}")
if [ "$SIZE" -gt "$CEIL" ]; then
echo "| \`$wasm\` | $SIZE | $BUDGET | $CEIL | ❌ Exceeded |" >> $GITHUB_STEP_SUMMARY
FAILED=1
else
echo "| \`$wasm\` | $SIZE | $BUDGET | $CEIL | ✅ Within Budget |" >> $GITHUB_STEP_SUMMARY
fi
done
if [ "$FAILED" -ne 0 ]; then
echo "Error: WASM size budget exceeded beyond the stated tolerance." >&2
exit 1
fi
# --- Stage 1: soroban-cost-linter (static analysis, findings surfaced) ---
- name: Install soroban-cost-linter (pinned)
run: |
rustup toolchain install "$SOROBAN_COST_LINTER_NIGHTLY" --component rustc-dev --component llvm-tools-preview
cargo install cargo-dylint dylint-link --version "$DYLINT_VERSION" --locked
cargo +"$SOROBAN_COST_LINTER_NIGHTLY" install \
--git https://github.com/Tollcraft/soroban-cost-linter.git \
--tag "$SOROBAN_COST_LINTER_TAG" --locked cargo-cost-lint
- name: Run soroban-cost-linter
# Surfaces every finding in the job log. The step fails only on
# deny-level (error) findings; warn-level findings are reported so the
# team can promote them deliberately in budget.toml. Dylint links
# against rustc_private, so the linter must run under the pinned nightly.
run: |
echo "### soroban-cost-linter findings" >> $GITHUB_STEP_SUMMARY
cargo +"$SOROBAN_COST_LINTER_NIGHTLY" cost-lint --workspace --format text | tee -a "$GITHUB_STEP_SUMMARY" || true
# --- Stage 2: soroban-budget-assert Tier A (local, per-PR gate) ---
- name: Run Tier A budget assertions
# Compiles the contracts for the host and runs the `#[budget_cpu_lt(N)]`
# macro tests. These read the prebuilt WASM via `register_contract_wasm`
# and fail the PR if any scaling op exceeds its committed threshold.
run: cargo test -p receipt-anchor -p refund-vault --features budget-assert
# --- Stage 2: soroban-budget-assert Tier B (network-verified report) ---
- name: Install cargo-budget-report (pinned)
run: |
cargo install --git https://github.com/Tollcraft/soroban-budget-assert.git \
--tag "$SOROBAN_BUDGET_ASSERT_REF" --locked cargo-budget-report
- name: Install stellar-cli (Tier B only)
# `cargo budget-report` simulates against testnet via the stellar CLI.
if: ${{ env.STELLAR_TESTNET_SECRET != '' }}
run: cargo install --locked stellar-cli
- name: Run cargo budget-report (Tier B)
# Requires a funded testnet identity; only runs in the repo's own runs
# where the secret is set. `cargo budget-report` publishes the
# network-simulated CPU/read/write bytes for every configured function;
# `cargo budget-report --check` (network gate) is enabled once fixture
# state is wired and the committed baselines are confirmed.
if: ${{ env.STELLAR_TESTNET_SECRET != '' }}
run: |
echo "$STELLAR_TESTNET_SECRET" | stellar keys add alice --network testnet --secret-key-stdin
cargo budget-report --check --json | tee budget-report.json
echo "### Tier B budget report" >> $GITHUB_STEP_SUMMARY
cat budget-report.json >> $GITHUB_STEP_SUMMARY
- name: Upload Tier B report
if: ${{ env.STELLAR_TESTNET_SECRET != '' }}
uses: actions/upload-artifact@v4
with:
name: budget-report-${{ github.sha }}
path: budget-report.json
build-wasm:
name: build-wasm
runs-on: ubuntu-latest
env:
# Same stated tolerance as the budget-limits job so both size gates
# pass/fail consistently: measured value may exceed the committed
# baseline by up to (1 + tolerance) before the build fails.
WASM_SIZE_TOLERANCE: 0.10
steps:
- uses: actions/checkout@v7
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32v1-none
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-wasm-${{ hashFiles('**/Cargo.lock') }}
- name: Wasm build
# Build the deployable contract crates only: building the `testutils`
# workspace member for the wasm target activates soroban-sdk's
# `testutils` feature, which is not supported on wasm and fails with
# `compile_error!("'testutils' feature is not supported on 'wasm'")`.
run: cargo build --locked --workspace --exclude testutils --target wasm32v1-none --release
- name: Report WASM sizes & enforce size budget
run: |
TOL=$WASM_SIZE_TOLERANCE
FAILED=0
PCT=$(awk "BEGIN{printf \"%.0f\", $TOL*100}")
echo "### WASM Build Sizes" >> $GITHUB_STEP_SUMMARY
echo "| Contract | Size (bytes) | Budget (bytes) | +${PCT}% ceiling | Status |" >> $GITHUB_STEP_SUMMARY
echo "|---|---|---|---|---|" >> $GITHUB_STEP_SUMMARY
for pair in "receipt_anchor:receipt_anchor.wasm" "refund_vault:refund_vault.wasm" "refund_policy_time:refund_policy_time.wasm" "refund_policy_vdf:refund_policy_vdf.wasm" "refund_vault_factory:refund_vault_factory.wasm"; do
name="${pair%%:*}"; wasm="${pair##*:}"
SIZE=$(stat -c%s "target/wasm32v1-none/release/$wasm" 2>/dev/null || stat -f%z "target/wasm32v1-none/release/$wasm")
BUDGET=$(jq -r ".$name" .wasm-budget.json)
CEIL=$(awk "BEGIN{printf \"%d\", $BUDGET*(1+$TOL)}")
if [ "$SIZE" -gt "$CEIL" ]; then
echo "| \`$wasm\` | $SIZE | $BUDGET | $CEIL | ❌ Exceeded |" >> $GITHUB_STEP_SUMMARY
FAILED=1
else
echo "| \`$wasm\` | $SIZE | $BUDGET | $CEIL | ✅ Within Budget |" >> $GITHUB_STEP_SUMMARY
fi
done
if [ "$FAILED" -ne 0 ]; then
echo "Error: WASM size budget exceeded beyond the stated tolerance." >&2
exit 1
fi
- name: Upload WASM Artifacts
uses: actions/upload-artifact@v7
with:
name: wasm-artifacts-${{ github.sha }}
path: |
target/wasm32v1-none/release/receipt_anchor.wasm
target/wasm32v1-none/release/refund_vault.wasm
vector-parity:
name: vector parity (cross-repo)
runs-on: ubuntu-latest
# Keep the two repos' Merkle vectors from silently drifting. Divergence must
# fail a build, not produce a warning (issue #53). Triggered on every push
# to main, on PRs, on a daily schedule (so a stale SDK copy is caught even
# when nothing changes here), and can be fired from accensa-app via
# repository_dispatch / workflow_dispatch.
concurrency:
group: vector-parity-${{ github.ref }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v7
- name: Verify local fixture is in sync with its source of truth
# Regenerates src/vectors.rs from merkle-vectors.json and fails if it
# drifted, and fails if merkle-vectors.json.sha256 is stale. This is the
# in-repo half of the parity guarantee.
run: |
node contracts/receipt-anchor/scripts/build-vectors.mjs --check
- name: Fetch SDK vector fixture from accensa-app (pinned ref)
id: fetch
env:
ACCENSA_APP_REF: ${{ vars.ACCELSA_APP_REF || 'main' }}
# On a PR we have not yet synced accensa-app, so a hash mismatch must
# not block the PR itself — it is reported as a warning. On main,
# schedule, dispatch and manual runs the same mismatch is a hard
# failure, which is the whole point of issue #53.
STRICT: ${{ github.event_name == 'pull_request' && 'false' || 'true' }}
run: |
URL="https://raw.githubusercontent.com/accensa/accensa-app/${ACCELSA_APP_REF}/packages/sdk/merkle-vectors.json"
echo "Fetching ${URL}"
if curl -fsSL "$URL" -o /tmp/sdk-merkle-vectors.json; then
SDK_HASH=$(sha256sum /tmp/sdk-merkle-vectors.json | cut -d' ' -f1)
LOCAL_HASH=$(tr -d '[:space:]' < contracts/receipt-anchor/merkle-vectors.json.sha256)
echo "SDK hash (accensa-app@${ACCELSA_APP_REF}): ${SDK_HASH}"
echo "Local hash (this repo): ${LOCAL_HASH}"
if [ "$SDK_HASH" != "$LOCAL_HASH" ]; then
if [ "$STRICT" = "true" ]; then
echo "::error::merkle-vectors.json diverged from accensa-app@${ACCELSA_APP_REF}."
echo "::error::Bring the two repos in sync: update both copies of merkle-vectors.json"
echo "::error::AND both committed .sha256 files, then pin ACCENSA_APP_REF. The"
echo "::error::cross-implementation proof-of-parity is broken until you do."
exit 1
fi
echo "::warning::merkle-vectors.json diverged from accensa-app@${ACCELSA_APP_REF}."
echo "::warning::This is expected until accensa-app vendors the new canonical vectors"
echo "::warning::(see cross-repo/accensa-app/README.md). Once both repos carry the"
echo "::warning::same hash this becomes a hard failure instead of a warning."
else
echo "Vector fixture matches accensa-app@${ACCELSA_APP_REF}."
fi
else
echo "::warning::Could not fetch accensa-app@${ACCELSA_APP_REF} merkle-vectors.json."
echo "::warning::This usually means accensa-app has not yet vendored the canonical"
echo "::warning::vectors (see docs/CONFORMANCE.md). The divergence guard is active but"
echo "::warning::cannot verify until the SDK copy exists. Open the accensa-app PR."
fi
notify-vector-parity:
name: notify accensa-app on vector change
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: vector-parity
steps:
- name: Ping accensa-app to re-check parity
# When this repo's vectors change, tell accensa-app's parity job to run
# so a stale SDK copy is caught promptly rather than only on its schedule.
uses: peter-evans/repository-dispatch@v3
continue-on-error: true
with:
token: ${{ secrets.ACCELSA_APP_PAT }}
repository: accensa/accensa-app
event-type: accensa-contracts-vector-update
client-payload: '{"ref": "${{ github.sha }}"}'