Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .githooks/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Installs the repo's git hooks by pointing core.hooksPath at .githooks/
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
git -C "$REPO_ROOT" config core.hooksPath .githooks
chmod +x "$REPO_ROOT"/.githooks/*
echo "✓ Git hooks installed (.githooks/)"
108 changes: 108 additions & 0 deletions .githooks/pre-commit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the value of adding githhooks if we can't rely on them being installed and we're running these checks in CI anyways? Does it speed up developement? As this is basically duplicating what is setu in CI, lets add comments explaining why we're doing this.

Copy link
Collaborator Author

@redpanda-f redpanda-f Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my system, it fixes before commits, things like fmt and others. These do not strictly duplicate CI, but there is an argument here. We can instead deduplicate and run the same script with FIX=0 and FIX=1 I guess.

Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────
# pre-commit hook for foc-devnet
#
# Checks staged files before each commit:
# - Rust: cargo fmt, cargo clippy
# - Shell: shfmt, shellcheck, executable bit
#
# Install: bash .githooks/install.sh
# Skip once: git commit --no-verify
#
# Auto-fix mode (formats code and re-stages):
# FIX=1 git commit
# ─────────────────────────────────────────────────────────────
set -euo pipefail

FIX="${FIX:-0}"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m'

FAIL=0

pass() { printf "${GREEN}✓${NC} %s\n" "$1"; }
fail() { printf "${RED}✗${NC} %s\n" "$1"; FAIL=1; }
skip() { printf "${YELLOW}⊘${NC} %s (skipped — tool not found)\n" "$1"; }
fixed() { printf "${BLUE}⟳${NC} %s (auto-fixed & re-staged)\n" "$1"; }

# Collect staged files
STAGED=$(git diff --cached --name-only --diff-filter=ACM)

# ── Rust checks ──────────────────────────────────────────────
STAGED_RS=$(echo "$STAGED" | grep '\.rs$' || true)
HAS_RS=$(echo "$STAGED_RS" | grep -c '\.rs$' || true)
if [[ $HAS_RS -gt 0 ]]; then
if command -v cargo &>/dev/null; then
if cargo fmt --all -- --check &>/dev/null; then
pass "cargo fmt"
elif [[ "$FIX" == "1" ]]; then
cargo fmt --all
echo "$STAGED_RS" | xargs -r git add
fixed "cargo fmt"
else
fail "cargo fmt — run 'cargo fmt' or FIX=1 git commit"
fi

if cargo clippy --all-targets --all-features -- -D warnings &>/dev/null; then
pass "cargo clippy"
else
fail "cargo clippy — fix warnings before committing"
fi
else
skip "cargo (Rust checks)"
fi
fi

# ── Shell checks ─────────────────────────────────────────────
STAGED_SH=$(echo "$STAGED" | grep '\.sh$' || true)
if [[ -n "$STAGED_SH" ]]; then
# Executable bit
for f in $STAGED_SH; do
if [[ -f "$f" && ! -x "$f" ]]; then
if [[ "$FIX" == "1" ]]; then
chmod +x "$f"
git add "$f"
fixed "${f} +x"
else
fail "${f} is not executable — run 'chmod +x ${f}'"
fi
fi
done

# shfmt
if command -v shfmt &>/dev/null; then
if shfmt -d -i 2 -bn $STAGED_SH &>/dev/null; then
pass "shfmt"
elif [[ "$FIX" == "1" ]]; then
shfmt -w -i 2 -bn $STAGED_SH
echo "$STAGED_SH" | xargs -r git add
fixed "shfmt"
else
fail "shfmt — run 'shfmt -w -i 2 -bn <file>' or FIX=1 git commit"
fi
else
skip "shfmt"
fi

# SC — no auto-fix available
if command -v shellcheck &>/dev/null; then
if shellcheck -S warning $STAGED_SH &>/dev/null; then
pass "shellcheck"
else
fail "shellcheck — run 'shellcheck -S warning <file>' to see issues"
fi
else
skip "shellcheck"
fi
fi

# ── Result ───────────────────────────────────────────────────
if [[ $FAIL -ne 0 ]]; then
echo ""
printf "${RED}Pre-commit checks failed.${NC} Fix issues above or skip with: git commit --no-verify\n"
exit 1
fi
84 changes: 80 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ on:
branches: ['*']
pull_request:
branches: [main]
schedule:
# Nightly at 03:00 UTC
- cron: '0 3 * * *'
workflow_dispatch:
inputs:
reporting:
description: 'Create GitHub issue with scenario report'
type: boolean
default: false
skip_report_on_pass:
description: 'Skip filing issue when all scenarios pass'
type: boolean
default: true

jobs:
fmt-clippy:
Expand All @@ -26,9 +39,38 @@ jobs:
- name: Run clippy
run: cargo clippy --all-targets --all-features -- -D warnings

foc-start-test:
shellcheck:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- uses: actions/checkout@v4

- name: Install shfmt
run: sudo snap install shfmt

# Ensure all .sh files under scenarios/ are executable
- name: Check executable permissions
run: |
bad=$(find scenarios -name '*.sh' ! -perm -u+x)
if [ -n "$bad" ]; then
echo "Missing +x on:" >&2; echo "$bad" >&2; exit 1
fi

# Lint with shellcheck
- name: shellcheck
run: find scenarios -name '*.sh' -exec shellcheck -S warning {} +

# Verify consistent formatting (indent=2, binary ops start of line)
- name: shfmt
run: shfmt -d -i 2 -bn scenarios/

foc-devnet-test:
runs-on: ["self-hosted", "linux", "x64", "16xlarge+gpu"]
timeout-minutes: 60
timeout-minutes: 100
permissions:
contents: read
issues: write

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -151,14 +193,28 @@ jobs:
if: steps.cache-docker-images.outputs.cache-hit == 'true'
run: |
rm -rf ~/.foc-devnet
./foc-devnet init --no-docker-build
if [[ "${{ github.event_name }}" == "schedule" ]]; then
./foc-devnet init --no-docker-build \
--curio latestCommit \
--filecoin-services latestCommit \
--lotus latestTag
else
./foc-devnet init --no-docker-build
fi

# If Docker images are not cached, do full init (downloads YugabyteDB and builds all images)
- name: "EXEC: {Initialize without cache}, independent"
if: steps.cache-docker-images.outputs.cache-hit != 'true'
run: |
rm -rf ~/.foc-devnet
./foc-devnet init
if [[ "${{ github.event_name }}" == "schedule" ]]; then
./foc-devnet init \
--curio latestCommit \
--filecoin-services latestCommit \
--lotus latestTag
else
./foc-devnet init
fi

# CACHE-DOCKER: Build Docker images if not cached
- name: "EXEC: {Build Docker images}, DEP: {C-docker-images-cache}"
Expand Down Expand Up @@ -346,6 +402,26 @@ jobs:
node check-balances.js "$DEVNET_INFO"
echo "✓ All examples ran well"

# Run scenario tests against the live devnet
- name: "TEST: {Run scenario tests}"
if: steps.start_cluster.outcome == 'success'
env:
# Enable reporting for nightly schedule or when explicitly requested
REPORTING: ${{ github.event_name == 'schedule' || inputs.reporting == true }}
# By default, don't file an issue if everything passes
SKIP_REPORT_ON_PASS: ${{ inputs.skip_report_on_pass != false }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: bash scenarios/run.sh

# Upload scenario report as artifact
- name: "EXEC: {Upload scenario report}"
if: always() && steps.start_cluster.outcome == 'success'
uses: actions/upload-artifact@v4
with:
name: scenario-report
path: ~/.foc-devnet/state/latest/scenario_*.md
if-no-files-found: ignore

# Clean shutdown
- name: "EXEC: {Stop cluster}, independent"
run: ./foc-devnet stop
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ target/
contracts/MockUSDFC/lib/
contracts/MockUSDFC/broadcast/
artifacts/
.vscode/
.vscode/
*__pycache__/
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ base32 = "0.4"
crc32fast = "1.3"
bip32 = "0.5"
rand = "0.8"
semver = "1.0"
bls-signatures = "0.15"
names = { version = "0.14", default-features = false }
shellexpand = "3"
Expand Down
55 changes: 55 additions & 0 deletions README_ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -1296,3 +1296,58 @@ docker run --rm --network host \
--broadcast
```

## Scenario Tests

Scenario tests are lightweight shell scripts that validate scenarios on the devnet after startup. They share a single running devnet and execute serially in a defined order.

### Running scenarios

```bash
# Run all scenarios against a running devnet
bash scenarios/run.sh

# Run a single scenario
bash scenarios/test_basic_balances.sh

# Point at a specific devnet run
DEVNET_INFO=~/.foc-devnet/state/<run-id>/devnet-info.json bash scenarios/run.sh
```

Reports are written to `~/.foc-devnet/state/latest/scenario_<timestamp>.md`.

### Writing a new scenario

1. Create `scenarios/test_<name>.sh`:

```bash
#!/usr/bin/env bash
set -euo pipefail
SCENARIO_DIR="$(cd "$(dirname "$0")" && pwd)"
source "${SCENARIO_DIR}/lib.sh"
scenario_start "<name>"

# Use helpers: jq_devnet, assert_eq, assert_gt, assert_not_empty, assert_ok
RPC_URL=$(jq_devnet '.info.lotus.host_rpc_url')
BALANCE=$(cast balance 0x... --rpc-url "$RPC_URL")
assert_gt "$BALANCE" 0 "account has funds"

scenario_end
```

2. Add `test_<name>` to the `SCENARIOS` array in `scenarios/order.sh`.
3. `chmod +x scenarios/test_<name>.sh`

### Available assertion helpers (from `lib.sh`)

| Helper | Usage | Description |
|--------|-------|-------------|
| `assert_eq` | `assert_eq "$a" "$b" "msg"` | Equality check |
| `assert_gt` | `assert_gt "$a" "$b" "msg"` | Integer greater-than (handles wei-scale) |
| `assert_not_empty` | `assert_not_empty "$v" "msg"` | Value is non-empty |
| `assert_ok` | `assert_ok cmd arg... "msg"` | Command exits 0 |
| `jq_devnet` | `jq_devnet '.info.lotus.host_rpc_url'` | Query devnet-info.json |

### CI integration

Scenarios run automatically in CI after the devnet starts. On nightly runs (or manual dispatch with `reporting` enabled), failures automatically create a GitHub issue with a full report.

Loading