diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6716eb1..3f2689b 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -42,7 +42,11 @@ jobs: - name: Install golangci-lint uses: golangci/golangci-lint-action@v7 with: - version: latest + # Pinned (not `latest`) for determinism. MUST match GOLANGCI_VERSION in + # the Makefile and the `version:` in .golangci.yml's schema. `latest` + # drifts between releases and shifts the default linter set, which is a + # "passes locally, fails CI" trap. Bump all three together. + version: v2.12.2 - name: Run linting run: make lint diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..b1d7811 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,17 @@ +# golangci-lint configuration (schema v2). +# +# DETERMINISM, not stricter-than-today: this is exactly golangci-lint v2.12.2's +# enabled-by-default linter set, encoded explicitly so a binary bump can't +# silently shift it. Keep in sync with GOLANGCI_VERSION in the Makefile. +# See README "Before you push" for the full pinning/parity rationale. +version: "2" + +linters: + # `default: none` + the explicit list below pins the enabled set. + default: none + enable: + - errcheck + - govet + - ineffassign + - staticcheck + - unused diff --git a/Makefile b/Makefile index 9c52625..4e8da85 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,11 @@ SOLC_EVM_VERSION := paris # Falls back to grepping go.mod if `go list` is unavailable. GETH_VERSION := $(shell go list -m -f '{{.Version}}' github.com/ethereum/go-ethereum 2>/dev/null || grep -E 'github.com/ethereum/go-ethereum ' go.mod | awk '{print $$2}') +# Pinned golangci-lint version. Keep in sync with the workflow `version:` and +# `.golangci.yml` (bump all three together); an unpinned `latest` drifts into a +# "passes locally, fails CI" trap. See README "Before you push". +GOLANGCI_VERSION := 2.12.2 + # Find all .sol files in contracts directory SOL_FILES := $(wildcard $(CONTRACTS_DIR)/*.sol) CONTRACT_NAMES := $(basename $(notdir $(SOL_FILES))) @@ -52,23 +57,29 @@ BIN_FILES := $(addprefix $(BUILD_DIR)/, $(addsuffix .bin, $(CONTRACT_NAMES))) BINDING_FILES := $(addprefix $(BINDINGS_DIR)/, $(addsuffix .go, $(CONTRACT_NAMES))) SCENARIO_TEMPLATE_FILES := $(addprefix $(SCENARIOS_DIR)/, $(addsuffix .go, $(CONTRACT_NAMES))) -.PHONY: generate generate-bindings check-bindings install-abigen clean help build-cli install setup-node build test lint +.PHONY: generate generate-bindings check-bindings install-abigen install-lint clean help build-cli install setup-node build test lint verify # Default target help: @echo "Available targets:" + @echo " verify - Run exactly what CI gates on: lint + test + check-bindings" @echo " build - Build the seiload CLI (alias for build-cli)" - @echo " test - Run tests with coverage" - @echo " lint - Run linting and static analysis" + @echo " test - Run tests with coverage (race detector enabled)" + @echo " lint - Run linting and static analysis (golangci-lint $(GOLANGCI_VERSION))" @echo " setup-node - Install nvm, Node.js 20, and solc" @echo " generate - Generate Go bindings and scenario templates for all contracts" @echo " generate-bindings - Regenerate ONLY the Go bindings (no scenarios/factory)" @echo " check-bindings - Fail if committed bindings are out of sync with contracts" + @echo " install-tools - Install the full pinned toolchain (solc, abigen, golangci-lint)" @echo " install-abigen - Install abigen pinned to the go.mod go-ethereum version" + @echo " install-lint - Install golangci-lint pinned to $(GOLANGCI_VERSION)" @echo " clean - Remove generated files" @echo " help - Show this help message" @echo " build-cli - Build the seiload CLI" @echo " install - Install the seiload CLI" + @echo "" + @echo "Before pushing: run 'make verify' (local CI parity). Run 'make install-tools'" + @echo "first to get the pinned toolchain (golangci-lint $(GOLANGCI_VERSION) etc.)." # Setup Node.js environment with nvm setup-node: @@ -187,8 +198,15 @@ check-bindings: fi @echo "✅ Bindings are in sync with contracts" +# Install golangci-lint pinned to GOLANGCI_VERSION for CI parity (CI pins the +# same version via golangci-lint-action). +install-lint: + @echo "📦 Installing golangci-lint@v$(GOLANGCI_VERSION) ..." + @go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v$(GOLANGCI_VERSION) + @echo "✅ Installed golangci-lint@v$(GOLANGCI_VERSION)" + # Install tools (optional convenience target) -install-tools: setup-node install-abigen +install-tools: setup-node install-abigen install-lint @echo "✅ Tools installation complete" # Build the seiload CLI binary @@ -215,8 +233,18 @@ test: @go tool cover -func=coverage.out @echo "✅ Tests passed" -# Run linting and static analysis +# Run linting. Expects golangci-lint == GOLANGCI_VERSION (`make install-lint`); +# warns (not fails) on a mismatch, since a different binary can shift results. lint: @echo "🔍 Running linting and static analysis..." + @have=$$(golangci-lint version --short 2>/dev/null || golangci-lint --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1); \ + if [ -n "$$have" ] && [ "$$have" != "$(GOLANGCI_VERSION)" ]; then \ + echo "⚠️ golangci-lint $$have on PATH != pinned $(GOLANGCI_VERSION). Run 'make install-lint' for CI parity."; \ + fi @golangci-lint run @echo "✅ Linting and static analysis passed" + +# Local CI parity: lint + test + check-bindings — the gating jobs in +# build-and-test.yml + bindings-check.yml. Green = those CI jobs pass. +verify: lint test check-bindings + @echo "✅ verify passed (lint + test + check-bindings) — local CI parity" diff --git a/README.md b/README.md index 2a6f645..9ef5b7b 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,28 @@ blocks height=5191 time(p50=2s p99=5s max=8s) gas(p50=21000 p99=50000 max=100000 ## Development +### Before you push + +Run the full local CI gate in one command: + +```bash +make verify # lint + test + check-bindings (exactly what CI gates on) +``` + +A green `make verify` means the gating CI jobs (`build-and-test`, `bindings-check`) +will pass. Install the pinned toolchain once first so your local results match CI: + +```bash +make install-tools # full toolchain: Node (via nvm), solc, abigen, golangci-lint (pinned to v2.12.2) +# or, for the linter only: +make install-lint # golangci-lint pinned to v2.12.2 +``` + +`golangci-lint` is pinned to a specific version (Makefile `GOLANGCI_VERSION`, +the workflow's `golangci-lint-action` `version:`, and `.golangci.yml`); `make lint` +warns if the binary on your PATH differs. A drifting/unpinned linter is the usual +"passes locally, fails CI" trap — `make install-lint` gives you the exact CI version. + ### Build ```bash make build @@ -177,7 +199,7 @@ make build ### Test ```bash -make test +make test # runs with -race ``` ### Lint