diff --git a/.github/workflows/check-no-fee.yml b/.github/workflows/check-no-fee.yml new file mode 100644 index 0000000..7bea3a1 --- /dev/null +++ b/.github/workflows/check-no-fee.yml @@ -0,0 +1,15 @@ +name: check-no-fee + +on: + pull_request: + push: + branches: [main] + +jobs: + check-no-fee: + name: Verify no fee/admin surface + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run check-no-fee.sh + run: bash scripts/check-no-fee.sh diff --git a/Cargo.toml b/Cargo.toml index d62befa..4304dac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ resolver = "2" members = ["program", "no-padding", "assertions"] exclude = ["tests-e2e"] +[workspace.metadata.cli] +solana = "3.0.4" + [workspace.dependencies] pinocchio = { version = "0.9", features = ["std"] } pinocchio-pubkey = { version = "0.3" } diff --git a/scripts/check-no-fee.sh b/scripts/check-no-fee.sh new file mode 100755 index 0000000..8121f19 --- /dev/null +++ b/scripts/check-no-fee.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Verify that program-v2 contains no fee/admin surface. +# +# Reads scripts/fee-paths.txt and fails if: +# - any PATH rule matches a file or directory that exists +# - any SYMBOL rule (ERE regex) matches inside program/src/**/*.rs +# +# Designed to run in CI (Linux, GNU grep) and locally (macOS, BSD grep). +# Exit codes: +# 0 = clean +# 1 = violation found +# 2 = invalid invocation / missing fee-paths.txt + +set -euo pipefail + +REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +RULES_FILE="$REPO_ROOT/scripts/fee-paths.txt" +SOURCE_ROOT="$REPO_ROOT/program/src" + +if [[ ! -f "$RULES_FILE" ]]; then + echo "error: $RULES_FILE not found" >&2 + exit 2 +fi + +violations=0 + +# ---- PATH rules: file/dir must not exist --------------------------------- +while IFS= read -r line; do + # Strip comments and trim + line="${line%%#*}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [[ -z "$line" ]] && continue + + if [[ "$line" == PATH\ * ]]; then + target="${line#PATH }" + if [[ -e "$REPO_ROOT/$target" ]]; then + echo "✗ forbidden path exists: $target" >&2 + violations=$((violations + 1)) + fi + fi +done < "$RULES_FILE" + +# ---- SYMBOL rules: regex must not match in program/src/**.rs ------------- +if [[ -d "$SOURCE_ROOT" ]]; then + while IFS= read -r line; do + line="${line%%#*}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [[ -z "$line" ]] && continue + + if [[ "$line" == SYMBOL\ * ]]; then + pattern="${line#SYMBOL }" + # -E extended regex; -r recursive; --include filters; -l list files; + # -n line numbers; -H show filename. Use -lE first to detect quickly. + if matches=$(grep -rEn --include='*.rs' -- "$pattern" "$SOURCE_ROOT" 2>/dev/null); then + echo "✗ forbidden symbol /$pattern/ matched:" >&2 + # Indent each match line for readability + printf '%s\n' "$matches" | sed 's/^/ /' >&2 + violations=$((violations + 1)) + fi + fi + done < "$RULES_FILE" +fi + +if (( violations > 0 )); then + echo "" >&2 + echo "FAIL: $violations fee-surface violation(s) found." >&2 + echo "Either remove the offending file/symbol, or update scripts/fee-paths.txt" >&2 + echo "if the rule itself is wrong." >&2 + exit 1 +fi + +echo "✓ program-v2 source is fee-surface clean." diff --git a/scripts/fee-paths.txt b/scripts/fee-paths.txt new file mode 100644 index 0000000..9747676 --- /dev/null +++ b/scripts/fee-paths.txt @@ -0,0 +1,51 @@ +# ============================================================================ +# Fee surface declaration — must NEVER exist in program-v2. +# +# Read by: +# scripts/check-no-fee.sh — fails CI if any rule below matches +# scripts/strip-fee.sh — auto-deletes PATH entries after cherry-pick +# +# Format (one rule per line, prefix-tagged): +# PATH +# Fails if the file or directory exists. +# strip-fee.sh removes it via `git rm -rf`. +# SYMBOL +# Fails if the regex matches in any tracked .rs file under program/src/. +# strip-fee.sh DOES NOT auto-edit source — the user must hand-resolve. +# +# Lines starting with `#` and blank lines are ignored. +# +# Why this file exists: +# program-v2 is the "no-profit" foundation clone of lazorkit-protocol. +# When cherry-picking feature commits from lazorkit-protocol, the fee/admin +# paths must be excluded. This file is the single source of truth for what +# counts as "fee surface". +# ============================================================================ + +# --- State accounts (fee/admin storage) ------------------------------------- +PATH program/src/state/protocol_config.rs +PATH program/src/state/treasury_shard.rs +PATH program/src/state/integrator_record.rs + +# --- Processor module (admin-only instructions) ---------------------------- +PATH program/src/processor/protocol + +# --- Type names (fee state structs) ---------------------------------------- +SYMBOL \bProtocolConfig\b +SYMBOL \bTreasuryShard\b +SYMBOL \bFeeRecord\b +SYMBOL \bIntegratorRecord\b + +# --- Function names (fee collection internals) ----------------------------- +SYMBOL \btry_collect_fee\b + +# --- Field names (fee config) ---------------------------------------------- +SYMBOL \bcreation_fee\b +SYMBOL \bexecution_fee\b + +# --- Instruction handler module paths (admin-only) ------------------------- +SYMBOL \binitialize_protocol\b +SYMBOL \bupdate_protocol\b +SYMBOL \bregister_integrator\b +SYMBOL \bwithdraw_treasury\b +SYMBOL \binitialize_treasury_shard\b diff --git a/scripts/strip-fee.sh b/scripts/strip-fee.sh new file mode 100755 index 0000000..07bb174 --- /dev/null +++ b/scripts/strip-fee.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# Post-cherry-pick cleanup: remove fee/admin surface that may have been +# introduced by an upstream patch from lazorkit-protocol. +# +# Workflow: +# 1. Apply the upstream patch: git am --3way path/to/feature.mbox +# or: git apply path/to/feature.patch && git add -A +# 2. Run this script: ./scripts/strip-fee.sh +# 3. Hand-resolve any remaining symbol leaks reported below. +# 4. Verify: ./scripts/check-no-fee.sh +# 5. Commit the cleaned result. +# +# Behavior: +# - PATH rules are auto-removed via `git rm -rf` (when present). +# - SYMBOL rules are NOT auto-edited — stripping a struct/function reference +# from the middle of a file requires understanding context. The script +# prints each offending file:line and exits non-zero so the user notices. +# +# Use --dry-run to see what would happen without modifying anything. + +set -euo pipefail + +REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +RULES_FILE="$REPO_ROOT/scripts/fee-paths.txt" +SOURCE_ROOT="$REPO_ROOT/program/src" + +DRY_RUN=0 +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=1 +fi + +if [[ ! -f "$RULES_FILE" ]]; then + echo "error: $RULES_FILE not found" >&2 + exit 2 +fi + +cd "$REPO_ROOT" + +removed=0 +symbol_leaks=0 + +# ---- PATH rules: auto-remove via git rm ---------------------------------- +echo "→ Removing forbidden paths..." +while IFS= read -r line; do + line="${line%%#*}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [[ -z "$line" ]] && continue + + if [[ "$line" == PATH\ * ]]; then + target="${line#PATH }" + if [[ -e "$target" ]]; then + if (( DRY_RUN )); then + echo " [dry-run] would remove: $target" + else + # Use git rm if tracked, otherwise plain rm. -f ignores missing. + if git ls-files --error-unmatch -- "$target" >/dev/null 2>&1; then + git rm -rf --quiet -- "$target" + else + rm -rf -- "$target" + fi + echo " removed: $target" + fi + removed=$((removed + 1)) + fi + fi +done < "$RULES_FILE" + +if (( removed == 0 )); then + echo " (no forbidden paths present)" +fi + +# ---- SYMBOL rules: report only, don't edit ------------------------------- +echo "" +echo "→ Scanning for forbidden symbols in program/src/**.rs..." +if [[ -d "$SOURCE_ROOT" ]]; then + while IFS= read -r line; do + line="${line%%#*}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + [[ -z "$line" ]] && continue + + if [[ "$line" == SYMBOL\ * ]]; then + pattern="${line#SYMBOL }" + if matches=$(grep -rEn --include='*.rs' -- "$pattern" "$SOURCE_ROOT" 2>/dev/null); then + echo " ✗ /$pattern/ leaked into source:" + printf '%s\n' "$matches" | sed 's/^/ /' + symbol_leaks=$((symbol_leaks + 1)) + fi + fi + done < "$RULES_FILE" +fi + +echo "" +if (( symbol_leaks > 0 )); then + echo "FAIL: $symbol_leaks symbol leak(s) require manual cleanup." >&2 + echo "Edit the files above to remove fee references, then re-run check-no-fee.sh." >&2 + exit 1 +fi + +if (( DRY_RUN )); then + echo "✓ dry-run complete. $removed path(s) would be removed." +else + echo "✓ strip complete. $removed path(s) removed, no symbol leaks." + echo " Run ./scripts/check-no-fee.sh to verify." +fi