Skip to content

Add site link to README footer (2026-06-08) #6

Add site link to README footer (2026-06-08)

Add site link to README footer (2026-06-08) #6

Workflow file for this run

name: ci
# cf-invariants-pyth Phase-3.5 CI — invariants + Crucible + scorecards.
#
# The CI run IS the proof: zero local toolchain footprint, every push rebuilds
# the Pyth Solana Receiver program (anchor-lang 0.32.1 -> 1.0.1 port) AND its
# 2 planted twins, then runs the Crucible harness on every clean/planted pair,
# asserting clean=0 violations and planted>=1 per invariant class. Real
# scorecards are captured (ANSI-stripped) under findings/<invariant>/ and
# uploaded as an artifact. If any matrix cell stops being green, the artifact
# is broken — we never publish a manual "verified" claim.
#
# Classes proven by this CI:
# - two_step_governance — pyth_receiver_ref pair, invariant_two_step_governance_atomic
# - reclaim_rent_conservation — pyth_receiver_ref pair, invariant_reclaim_rent_returns_to_write_authority
#
# The 3rd invariant class designed in Phase-1 (`vaa_quorum_gating`) was
# retired in Phase-3 (D-cf-invariants-pyth-phase3-rescope-2026-06-04 = A)
# because driving the planted bug requires a guardian-signing VAA-crafter
# helper outside this harness's port-+-fixture pattern.
#
# Pinned upstream surfaces (matched to cf-invariants-jito's ci.yml):
# - Crucible: v0.2.0 (asymmetric-research/crucible)
# - anchor-lang: 1.0.1 (matches Crucible v0.2.0 workspace)
# - Solana CLI: v2.1.21 (Anza release; recent stable)
# - platform-tools: v1.52 (recent tag with linux-x86_64 asset; the
# bundled default in Agave v2.1.21 is v1.43,
# too old for anchor-lang 1.0.1 deps.
# NB: Agave v2.1.21 ships a buggy
# cargo-build-sbf that panics if
# ~/.cache/solana doesn't exist — we mkdir
# it explicitly before the first invocation.)
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: {}
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
CRUCIBLE_REF: v0.2.0
SOLANA_VERSION: v2.1.21
SOLANA_TOOLS_VERSION: v1.52
# Iteration budget per Crucible run. Small enough to fit a free-tier
# GitHub-hosted runner; large enough for the planted bug to fire
# (minimal counterexamples for these invariants are 1-2 actions).
CRUCIBLE_TIMEOUT: '30'
jobs:
workspace-check:
name: cf-invariants-pyth workspace check
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-rust
- name: cargo check --workspace
run: cargo check --workspace --locked || cargo check --workspace
build-sbf:
name: cargo build-sbf pyth-solana-receiver (Phase-3.5 marquee artifact)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-rust
- name: Install Anza / Solana CLI ${{ env.SOLANA_VERSION }}
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/${SOLANA_VERSION}/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
- name: Pre-create platform-tools cache dir
run: mkdir -p "$HOME/.cache/solana"
- name: Verify solana / cargo-build-sbf on PATH
run: |
solana --version
cargo-build-sbf --version
- name: cargo build-sbf pyth-solana-receiver
run: |
cargo build-sbf \
--tools-version "${SOLANA_TOOLS_VERSION}" \
--manifest-path programs/pyth-solana-receiver/Cargo.toml
ls -l target/deploy/pyth_solana_receiver.so
- name: Upload built program artifact
uses: actions/upload-artifact@v4
with:
name: pyth_solana_receiver-so
path: target/deploy/pyth_solana_receiver.so
harness:
name: build + run Crucible harness (2 invariants × clean+planted)
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-rust
- name: Install Anza / Solana CLI ${{ env.SOLANA_VERSION }}
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/${SOLANA_VERSION}/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> $GITHUB_PATH
- name: Pre-create platform-tools cache dir
run: mkdir -p "$HOME/.cache/solana"
- name: Verify solana / cargo-build-sbf on PATH
run: |
solana --version
cargo-build-sbf --version
- name: Clone Crucible @ ${{ env.CRUCIBLE_REF }} (sibling to this repo)
# Path deps in references/*/fuzz/*/Cargo.toml resolve to
# `../../../../../crucible/...`, i.e. <repo-root>/../crucible.
# We use $GITHUB_WORKSPACE/../crucible to satisfy that.
working-directory: ${{ github.workspace }}/..
run: |
git clone --depth 1 --branch "${CRUCIBLE_REF}" \
https://github.com/asymmetric-research/crucible.git crucible
(cd crucible && git rev-parse HEAD)
# Second rust-cache step (Crucible build dir under <repo-root>/../crucible)
# is intentionally inline — the composite action targets the workspace
# cache; this one needs a distinct cache key for the pinned upstream.
- name: Cache cargo (Crucible build)
uses: Swatinem/rust-cache@v2
with:
workspaces: ../crucible -> target
key: crucible-${{ env.CRUCIBLE_REF }}
- name: Build + install Crucible CLI from v0.2.0 source
working-directory: ${{ github.workspace }}/../crucible
run: |
cargo install --path crates/crucible-fuzz-cli --locked || \
cargo install --path crates/crucible-fuzz-cli
which crucible
crucible --version || crucible --help | head -3
# -----------------------------------------------------------------
# Build the clean reference's .so ONCE — the same artifact backs
# both fuzz fixtures under references/pyth_receiver_ref/fuzz/*.
# -----------------------------------------------------------------
- name: cargo build-sbf pyth_receiver_ref (clean)
run: |
cargo build-sbf \
--tools-version "${SOLANA_TOOLS_VERSION}" \
--manifest-path references/pyth_receiver_ref/programs/pyth-solana-receiver/Cargo.toml
ls -l references/pyth_receiver_ref/target/deploy/pyth_solana_receiver.so
# -----------------------------------------------------------------
# Build the 2 planted twins.
# -----------------------------------------------------------------
- name: cargo build-sbf pyth_receiver_ref_planted_two_step_governance
run: |
cargo build-sbf \
--tools-version "${SOLANA_TOOLS_VERSION}" \
--manifest-path references/pyth_receiver_ref_planted_two_step_governance/programs/pyth-solana-receiver/Cargo.toml
ls -l references/pyth_receiver_ref_planted_two_step_governance/target/deploy/pyth_solana_receiver.so
- name: cargo build-sbf pyth_receiver_ref_planted_reclaim_rent_conservation
run: |
cargo build-sbf \
--tools-version "${SOLANA_TOOLS_VERSION}" \
--manifest-path references/pyth_receiver_ref_planted_reclaim_rent_conservation/programs/pyth-solana-receiver/Cargo.toml
ls -l references/pyth_receiver_ref_planted_reclaim_rent_conservation/target/deploy/pyth_solana_receiver.so
# -----------------------------------------------------------------
# Sync fixture sources clean → planted (the fixture source IS the
# contract; both variants must run the same probe code so the only
# behavioral difference is the planted bug).
# -----------------------------------------------------------------
- name: Sync fixture sources clean → planted (each invariant)
run: |
for inv in pyth_two_step_governance pyth_reclaim_rent_conservation; do
for planted in pyth_receiver_ref_planted_two_step_governance \
pyth_receiver_ref_planted_reclaim_rent_conservation; do
cp "references/pyth_receiver_ref/fuzz/${inv}/src/main.rs" \
"references/${planted}/fuzz/${inv}/src/main.rs"
done
done
# -----------------------------------------------------------------
# Pair 1 — two_step_governance
# -----------------------------------------------------------------
- name: Crucible run — two_step_governance CLEAN (expect 0 violations)
id: two_step_governance_clean
working-directory: references/pyth_receiver_ref/fuzz/pyth_two_step_governance
run: |
set -o pipefail
mkdir -p "$GITHUB_WORKSPACE/ci-out"
crucible run pyth_solana_receiver invariant_two_step_governance_atomic \
--release --timeout "${CRUCIBLE_TIMEOUT}" \
2>&1 | tee "$GITHUB_WORKSPACE/ci-out/two_step_governance_clean.out"
if grep -q -E 'FUZZ_FINDING|INVARIANT VIOLATED' "$GITHUB_WORKSPACE/ci-out/two_step_governance_clean.out"; then
echo "::error::two_step_governance clean reported a violation — accept_governance_authority_transfer should atomically clear target"
exit 1
fi
- name: Crucible run — two_step_governance PLANTED (expect >=1 violation)
id: two_step_governance_planted
working-directory: references/pyth_receiver_ref_planted_two_step_governance/fuzz/pyth_two_step_governance
run: |
set +e
crucible run pyth_solana_receiver invariant_two_step_governance_atomic \
--release --timeout "${CRUCIBLE_TIMEOUT}" \
2>&1 | tee "$GITHUB_WORKSPACE/ci-out/two_step_governance_planted.out"
rc=${PIPESTATUS[0]}
set -e
if grep -q -E 'FUZZ_FINDING|INVARIANT VIOLATED' "$GITHUB_WORKSPACE/ci-out/two_step_governance_planted.out"; then
echo "two_step_governance planted reported a violation (expected)."
exit 0
elif [ "$rc" -ne 0 ]; then
echo "two_step_governance planted exited non-zero ($rc); treating as detected violation."
exit 0
else
echo "::error::two_step_governance planted ran clean — the dropped target-clear was not surfaced within ${CRUCIBLE_TIMEOUT}s."
exit 1
fi
# -----------------------------------------------------------------
# Pair 2 — reclaim_rent_conservation
# -----------------------------------------------------------------
- name: Crucible run — reclaim_rent_conservation CLEAN (expect 0 violations)
id: reclaim_rent_conservation_clean
working-directory: references/pyth_receiver_ref/fuzz/pyth_reclaim_rent_conservation
run: |
set -o pipefail
crucible run pyth_solana_receiver invariant_reclaim_rent_returns_to_write_authority \
--release --timeout "${CRUCIBLE_TIMEOUT}" \
2>&1 | tee "$GITHUB_WORKSPACE/ci-out/reclaim_rent_conservation_clean.out"
if grep -q -E 'FUZZ_FINDING|INVARIANT VIOLATED' "$GITHUB_WORKSPACE/ci-out/reclaim_rent_conservation_clean.out"; then
echo "::error::reclaim_rent_conservation clean reported a violation — WrongWriteAuthority constraint should reject attacker"
exit 1
fi
- name: Crucible run — reclaim_rent_conservation PLANTED (expect >=1 violation)
id: reclaim_rent_conservation_planted
working-directory: references/pyth_receiver_ref_planted_reclaim_rent_conservation/fuzz/pyth_reclaim_rent_conservation
run: |
set +e
crucible run pyth_solana_receiver invariant_reclaim_rent_returns_to_write_authority \
--release --timeout "${CRUCIBLE_TIMEOUT}" \
2>&1 | tee "$GITHUB_WORKSPACE/ci-out/reclaim_rent_conservation_planted.out"
rc=${PIPESTATUS[0]}
set -e
if grep -q -E 'FUZZ_FINDING|INVARIANT VIOLATED' "$GITHUB_WORKSPACE/ci-out/reclaim_rent_conservation_planted.out"; then
echo "reclaim_rent_conservation planted reported a violation (expected)."
exit 0
elif [ "$rc" -ne 0 ]; then
echo "reclaim_rent_conservation planted exited non-zero ($rc); treating as detected violation."
exit 0
else
echo "::error::reclaim_rent_conservation planted ran clean — the dropped write_authority constraint was not surfaced within ${CRUCIBLE_TIMEOUT}s."
exit 1
fi
# -----------------------------------------------------------------
# Aggregate scorecards from real CI output. Renders findings/*/
# markdown the same way cf-invariants-jito does — diff-friendly
# ANSI-stripped raw output + summary.
# -----------------------------------------------------------------
- name: Capture scorecards from real CI output
if: always()
run: |
TC="rustc $(rustc --version | awk '{print $2}'), solana $(solana --version | awk '{print $2}'), platform-tools ${SOLANA_TOOLS_VERSION}, crucible v0.2.0 (anchor-lang 1.0.1)"
STAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
render_scorecard() {
local inv="$1" variant="$2" total="$3" violated="$4" class="$5"
local out_dir="findings/${inv}_${variant}"
local raw="$GITHUB_WORKSPACE/ci-out/${inv}_${variant}.out"
mkdir -p "$out_dir"
{
echo "# cf-invariants-pyth scorecard (CI capture, ${inv} ${variant} variant)"
echo
echo "Captured by \`.github/workflows/ci.yml\` on ${STAMP}."
echo "Toolchain: ${TC}."
echo "Crucible timeout: ${CRUCIBLE_TIMEOUT}s."
echo "Invariant class: \`${class}\`."
echo
echo "## Summary"
echo
echo "- Invariants total: **${total}**"
echo "- Invariants violated: **${violated}**"
echo "- AI-suggested invariants in this run: **0**"
echo "- Crucible version: \`0.2.0\`"
echo
echo "## Raw output"
echo
echo '```'
if [ -f "$raw" ]; then
sed -E 's/\x1b\[[0-9;]*[A-Za-z]//g' "$raw"
else
echo "(no output captured)"
fi
echo '```'
} > "$out_dir/scorecard.md"
}
render_scorecard "two_step_governance" "clean" "1" "0" "two_step_governance"
render_scorecard "two_step_governance" "planted" "1" ">=1" "two_step_governance"
render_scorecard "reclaim_rent_conservation" "clean" "1" "0" "reclaim_rent_conservation"
render_scorecard "reclaim_rent_conservation" "planted" "1" ">=1" "reclaim_rent_conservation"
- name: Upload scorecards + raw output as artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: crucible-scorecards
path: |
ci-out/*.out
findings/two_step_governance_clean/scorecard.md
findings/two_step_governance_planted/scorecard.md
findings/reclaim_rent_conservation_clean/scorecard.md
findings/reclaim_rent_conservation_planted/scorecard.md