Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
f5af11e
feat: add solana_test_utils crate with SurfpoolManager
shahan-khatchadourian-anchorage Apr 14, 2026
282c423
test: add surfpool mainnet-fork integration test for Solana parser
shahan-khatchadourian-anchorage Apr 14, 2026
6284b2a
feat: add idl-meta tool for Python-free IDL metadata extraction
shahan-khatchadourian-anchorage Apr 14, 2026
641ab05
refactor: replace python3 with idl-meta in fuzz_all_idls.sh
shahan-khatchadourian-anchorage Apr 14, 2026
491ade0
test: add surfpool_fuzz_all_idls.sh for mainnet-fork IDL testing
shahan-khatchadourian-anchorage Apr 14, 2026
613c3b7
fix: update SurfpoolManager for current surfpool CLI
shahan-khatchadourian-anchorage Apr 14, 2026
82e3b3b
ci: add surfpool fuzz workflow gated on `surfpool` label
shahan-khatchadourian-anchorage May 7, 2026
14d1351
ci(surfpool): pin surfpool to v1.1.1, skip fork PRs, harden install gate
shahan-khatchadourian-anchorage May 7, 2026
e6f0f08
fix(surfpool): unblock Tokio worker, surface test diagnostics, fix CI…
shahan-khatchadourian-anchorage May 7, 2026
2a1523f
ci(surfpool): pin to commit SHA instead of tag
shahan-khatchadourian-anchorage May 7, 2026
33fc553
ci(surfpool): cap lints to warn during third-party install
shahan-khatchadourian-anchorage May 7, 2026
771e5ed
ci(surfpool): use upstream prebuilt binary instead of cargo install
shahan-khatchadourian-anchorage May 7, 2026
3daa361
ci(surfpool): guard against label-event re-trigger loops
shahan-khatchadourian-anchorage May 8, 2026
d853ace
test(surfpool): native cargo runner via build.rs + per-IDL macro
shahan-khatchadourian-anchorage May 7, 2026
c89ad3e
test(surfpool): consume embedded_idls consts directly, drop build-tim…
shahan-khatchadourian-anchorage May 8, 2026
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
106 changes: 106 additions & 0 deletions .github/workflows/surfpool-solana.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: "Surfpool Fuzz: Solana"

on:
pull_request:
types: [opened, synchronize, reopened, labeled]

env:
# Use the upstream prebuilt binary instead of `cargo install` (which is a
# ~10 minute compile). Pin the tarball's SHA256 so a force-pushed release
# asset can't silently change what we install.
SURFPOOL_VERSION: v1.1.1
SURFPOOL_SHA256: e17b44331ce3baa58fe530a061d6dc6df0e288521fec0bd5892333854ffeecf5

jobs:
surfpool:
# Two guards:
# 1. On `labeled` events only run when the label being added is `surfpool`.
# Without this, the workflow's own `surfpool-failure` add (and any other
# label change on a PR that happens to already carry `surfpool`) would
# re-fire the workflow.
# 2. Skip fork PRs: secrets aren't passed across forks, so HELIUS_API_KEY
# would be empty and `gh pr edit` would lack write permissions.
if: |
(
(github.event.action == 'labeled' && github.event.label.name == 'surfpool')
||
(github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'surfpool'))
)
&& github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest-4-cores
permissions:
pull-requests: write
env:
HELIUS_API_KEY: ${{ secrets.HELIUS_API_KEY }}
PROPTEST_CASES: 32
steps:
- name: git checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@fb51252c7ba57d633bc668f941da052e410add48 # v1.13.0
- name: Cache surfpool binary
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: ~/.local/bin/surfpool
key: ${{ runner.os }}-surfpool-bin-${{ env.SURFPOOL_VERSION }}-${{ env.SURFPOOL_SHA256 }}
- name: Cache Rust dependencies
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
src/target/
key: ${{ runner.os }}-cargo-surfpool-${{ hashFiles('src/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-surfpool-
- name: install protoc
uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
with:
version: "21.4"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: free disk space
run: |
sudo swapoff -a
sudo rm -f /swapfile
sudo apt clean
df -h
- name: Install surfpool
run: |
mkdir -p "${HOME}/.local/bin"
echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"
# `surfpool --version` is a stronger gate than `command -v`: a
# corrupted cached binary will fail this check and trigger redownload.
if "${HOME}/.local/bin/surfpool" --version >/dev/null 2>&1; then
exit 0
fi
tarball="$(mktemp)"
curl -fsSL -o "${tarball}" \
"https://github.com/txtx/surfpool/releases/download/${SURFPOOL_VERSION}/surfpool-linux-x64.tar.gz"
echo "${SURFPOOL_SHA256} ${tarball}" | sha256sum -c -
tar xzf "${tarball}" -C "${HOME}/.local/bin"
rm -f "${tarball}"
"${HOME}/.local/bin/surfpool" --version
- name: Run codegen
run: make -C src generated
- name: Run surfpool fuzz against all embedded IDLs
# Tests are gated by `#[ignore]` so they only run on `--ignored`.
# `--test-threads=1` serialises the surfpool spawns so each test owns
# its mainnet fork; otherwise 14 surfpools start simultaneously and
# contend for ports / RPC quota.
id: surfpool
continue-on-error: true
working-directory: src
run: cargo test -p visualsign-solana --test surfpool_fuzz -- --ignored --test-threads=1
- name: Label PR on surfpool failure
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [ "${{ steps.surfpool.outcome }}" = "failure" ]; then
gh pr edit "$PR_NUMBER" --add-label surfpool-failure || true
else
gh pr edit "$PR_NUMBER" --remove-label surfpool-failure || true
fi
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Raw tx bytes → ChainPlugin (CLI) or gRPC request
- Integration tests in `integration/tests/` use gRPC client against built binaries
- `test_utils` module in `visualsign` provides shared test helpers
- Place all `use` imports at the top of the test module, not inside individual test functions
- Surfpool-backed integration tests (`visualsign-solana/tests/surfpool_fuzz.rs`) are `#[ignore]` and require the `surfpool` binary on `$PATH`. Each `idl_test!(...)` invocation references a `pub const` from `solana_parser::solana::embedded_idls`, so IDL contents are baked in at compile time and the macro expands into one `#[tokio::test]` discoverable by cargo. Run with `HELIUS_API_KEY=<key> cargo test -p visualsign-solana --test surfpool_fuzz -- --ignored --test-threads=1`. Adding a new IDL: once it's exposed as a `pub const` upstream in `solana_parser`, add an `idl_test!(name, CONST)` line.

### Local Dev Container

Expand Down
35 changes: 7 additions & 28 deletions scripts/fuzz_all_idls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# PROPTEST_CASES=1000 ./scripts/fuzz_all_idls.sh
# ./scripts/fuzz_all_idls.sh /path/to/extra.json ... # append extra IDLs
#
# Requirements: cargo, python3
# Requirements: cargo (builds idl-meta from workspace automatically)

set -euo pipefail

Expand All @@ -42,25 +42,11 @@ CASES="${PROPTEST_CASES:-256}"

# ── Locate the solana_parser IDL directory via cargo metadata ─────────────────

IDL_DIR="$(python3 - "$WORKSPACE_TOML" <<'PY'
import json, os, subprocess, sys

manifest = sys.argv[1]
result = subprocess.run(
["cargo", "metadata", "--manifest-path", manifest, "--format-version", "1"],
capture_output=True, text=True, check=True,
)
data = json.loads(result.stdout)
for pkg in data["packages"]:
if pkg["name"] == "solana_parser":
idl_dir = os.path.join(os.path.dirname(pkg["manifest_path"]), "src", "solana", "idls")
if os.path.isdir(idl_dir):
print(idl_dir)
sys.exit(0)
print("error: solana_parser IDL directory not found", file=sys.stderr)
sys.exit(1)
PY
)"
# Build the idl-meta tool once (shares the workspace build cache).
cargo build --manifest-path "$WORKSPACE_TOML" -p idl-meta --quiet

IDL_META="cargo run --manifest-path $WORKSPACE_TOML -p idl-meta --quiet --"
IDL_DIR="$($IDL_META locate-idls --manifest-path "$WORKSPACE_TOML")"

# ── Collect IDL files: embedded + any extras passed as arguments ──────────────

Expand Down Expand Up @@ -93,14 +79,7 @@ for idl_file in "${IDL_FILES[@]}"; do
name="$(basename "$idl_file" .json)"

# Get instruction/type counts
read -r inst_count type_count < <(python3 -c "
import json, sys
try:
d = json.load(open(sys.argv[1]))
print(len(d.get('instructions', [])), len(d.get('types', [])))
except Exception:
print(0, 0)
" "$idl_file")
read -r inst_count type_count < <($IDL_META counts "$idl_file")

printf "%-30s %13s %7s " "$name" "$inst_count" "$type_count"

Expand Down
Loading
Loading