Skip to content
Merged
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
153 changes: 153 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: release

# Triggered by pushing an `audit-frozen-v*` tag (or a `v*` semver release tag).
# Builds the mainnet and devnet SBF binaries, records their SHA-256 hashes,
# and attaches the .so files + a manifest to the GitHub Release.
#
# The release artifacts are the source of truth for what gets deployed to
# mainnet — production deploys must use a binary downloaded from a release,
# not a locally-built one. See docs/MAINNET_DEPLOY.md.

on:
push:
tags:
- 'audit-frozen-v*'
- 'v*.*.*'

jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: write # for creating GitHub Releases

steps:
- uses: actions/checkout@v4
with:
# Full history so source_revision in security_txt embeds the right SHA.
fetch-depth: 0

- name: Install Solana toolchain
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"

- name: Pin Solana CLI to the version declared in Cargo.toml
run: |
DECLARED=$(grep -A1 'workspace.metadata.cli' Cargo.toml | grep solana | sed -E 's/.*"([^"]+)".*/\1/')
INSTALLED=$(solana --version | awk '{print $2}')
echo "Declared: $DECLARED"
echo "Installed: $INSTALLED"
if [ "$DECLARED" != "$INSTALLED" ]; then
echo "::warning::Installed Solana CLI ($INSTALLED) does not match declared ($DECLARED). Verified-build hashes may differ from what consumers reproduce."
fi

- name: Cache cargo build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: release-${{ hashFiles('**/Cargo.lock') }}-${{ github.ref_name }}

- name: Build mainnet binary
working-directory: program
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
run: cargo build-sbf --features mainnet

- name: Hash + stage mainnet artifact
run: |
mkdir -p release-artifacts
cp target/deploy/lazorkit_program.so release-artifacts/lazorkit_program-mainnet.so
MAINNET_SHA=$(shasum -a 256 release-artifacts/lazorkit_program-mainnet.so | awk '{print $1}')
echo "MAINNET_SHA=$MAINNET_SHA" >> "$GITHUB_ENV"
echo "mainnet sha256: $MAINNET_SHA"

- name: Build devnet binary
working-directory: program
env:
GITHUB_SHA: ${{ github.sha }}
GITHUB_REF_NAME: ${{ github.ref_name }}
run: cargo build-sbf --features devnet

- name: Hash + stage devnet artifact
run: |
cp target/deploy/lazorkit_program.so release-artifacts/lazorkit_program-devnet.so
DEVNET_SHA=$(shasum -a 256 release-artifacts/lazorkit_program-devnet.so | awk '{print $1}')
echo "DEVNET_SHA=$DEVNET_SHA" >> "$GITHUB_ENV"
echo "devnet sha256: $DEVNET_SHA"

- name: Verify binaries differ
run: |
if [ "$MAINNET_SHA" = "$DEVNET_SHA" ]; then
echo "::error::mainnet and devnet binaries are identical — dual-cluster mechanism broken"
exit 1
fi

- name: Stage IDL + keypair
run: |
cp program/idl.json release-artifacts/idl.json
# The keypair file is regenerated per build; useful as a record but
# NOT for deployment (the actual mainnet keypair is held off-CI).
if [ -f target/deploy/lazorkit_program-keypair.json ]; then
cp target/deploy/lazorkit_program-keypair.json release-artifacts/build-keypair.json
fi

- name: Write release manifest
run: |
cat > release-artifacts/MANIFEST.txt <<EOF
LazorKit program-v2 release manifest
tag: ${{ github.ref_name }}
commit: ${{ github.sha }}
built: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
solana-cli: $(solana --version)
rust-toolchain: $(rustc --version)

mainnet binary: lazorkit_program-mainnet.so
mainnet sha256: $MAINNET_SHA
mainnet program ID: LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi

devnet binary: lazorkit_program-devnet.so
devnet sha256: $DEVNET_SHA
devnet program ID: FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao

To reproduce these hashes locally:
git checkout ${{ github.ref_name }}
cd program
cargo build-sbf --features mainnet # → mainnet sha256 above
cargo build-sbf --features devnet # → devnet sha256 above

To deploy: see docs/MAINNET_DEPLOY.md
EOF
cat release-artifacts/MANIFEST.txt

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
body: |
**Tag:** `${{ github.ref_name }}`
**Commit:** `${{ github.sha }}`

**Mainnet binary:** `lazorkit_program-mainnet.so`
**Mainnet sha256:** `${{ env.MAINNET_SHA }}`
**Mainnet program ID:** `LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi` (slot shared with `lazorkit-protocol`)

**Devnet binary:** `lazorkit_program-devnet.so`
**Devnet sha256:** `${{ env.DEVNET_SHA }}`
**Devnet program ID:** `FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao`

See [`MANIFEST.txt`](./MANIFEST.txt) for build environment + reproduction
commands and [`docs/MAINNET_DEPLOY.md`](../docs/MAINNET_DEPLOY.md) for
deployment procedure.
files: |
release-artifacts/lazorkit_program-mainnet.so
release-artifacts/lazorkit_program-devnet.so
release-artifacts/idl.json
release-artifacts/MANIFEST.txt
draft: ${{ startsWith(github.ref_name, 'audit-frozen-') }}
prerelease: ${{ startsWith(github.ref_name, 'audit-frozen-') }}
122 changes: 122 additions & 0 deletions .github/workflows/sbf-cluster-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: SBF cluster feature check

# Verifies the Pattern D feature-flag mechanism in `assertions/src/lib.rs`:
#
# 1. `cargo build-sbf --features mainnet` succeeds and produces a binary
# embedding the mainnet vanity ID.
# 2. `cargo build-sbf --features devnet` succeeds and produces a binary
# embedding the devnet ID.
# 3. The two binaries differ (otherwise the feature flag has been
# neutralised by a refactor and Pattern D no longer protects against
# cross-cluster deploys).
# 4. `cargo build-sbf` with no feature flag fails with the expected
# `compile_error!` (otherwise nothing prevents an unflagged build
# from silently embedding whichever ID happens to be the default).
#
# Runs on every PR touching the program / assertions crate or this workflow.

on:
pull_request:
paths:
- 'program/**'
- 'assertions/**'
- '.github/workflows/sbf-cluster-check.yml'
push:
branches: [main]

jobs:
cluster-check:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Install Solana toolchain
run: |
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"

- name: Cache cargo build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: sbf-${{ hashFiles('**/Cargo.lock') }}

- name: Build mainnet binary
working-directory: program
run: cargo build-sbf --features mainnet

- name: Record mainnet hash
id: mainnet
run: |
M=$(shasum -a 256 target/deploy/lazorkit_program.so | awk '{print $1}')
echo "sha=$M" >> "$GITHUB_OUTPUT"
echo "mainnet SBF: $M"

- name: Build devnet binary
working-directory: program
run: cargo build-sbf --features devnet

- name: Record devnet hash
id: devnet
run: |
D=$(shasum -a 256 target/deploy/lazorkit_program.so | awk '{print $1}')
echo "sha=$D" >> "$GITHUB_OUTPUT"
echo "devnet SBF: $D"

- name: Verify binaries differ
run: |
if [ "${{ steps.mainnet.outputs.sha }}" = "${{ steps.devnet.outputs.sha }}" ]; then
echo "ERROR: mainnet + devnet SBF binaries are identical."
echo " Pattern D's compile-time cluster switch has been"
echo " neutralised — likely a refactor removed the cfg gate"
echo " on declare_id! in assertions/src/lib.rs."
exit 1
fi
echo "✓ binaries differ as expected"

- name: Verify no-feature build fails with compile_error!
working-directory: program
run: |
# Capture exit code separately — `cmd | tee` returns tee's exit
# (always 0), masking cargo's failure. `set -o pipefail` would
# also work, but capturing to a file gives us the log to search
# afterwards regardless of pipeline state.
set +e
cargo build-sbf > /tmp/build.log 2>&1
BUILD_EXIT=$?
set -e
cat /tmp/build.log
if [ "$BUILD_EXIT" -eq 0 ]; then
echo "ERROR: cargo build-sbf without --features mainnet/devnet succeeded."
echo " The compile_error! in assertions/src/lib.rs is no longer firing."
exit 1
fi
if ! grep -q "pick exactly one cluster" /tmp/build.log; then
echo "ERROR: build failed but not with the expected compile_error message."
echo " Expected: 'pick exactly one cluster — --features mainnet OR --features devnet'"
exit 1
fi
echo "✓ no-feature build correctly rejected by compile_error!"

- name: Verify both-features build fails
working-directory: program
run: |
set +e
cargo build-sbf --features mainnet --features devnet > /tmp/build-both.log 2>&1
BUILD_EXIT=$?
set -e
cat /tmp/build-both.log
if [ "$BUILD_EXIT" -eq 0 ]; then
echo "ERROR: cargo build-sbf with BOTH features succeeded."
echo " The compile_error! mutual-exclusion guard is broken."
exit 1
fi
if ! grep -q "pick exactly one cluster" /tmp/build-both.log; then
echo "ERROR: build failed but not with the expected compile_error message."
exit 1
fi
echo "✓ both-features build correctly rejected"
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- Session action permissions: 8 immutable permission rules attachable at session creation — `SolLimit`, `SolRecurringLimit`, `SolMaxPerTx`, `TokenLimit`, `TokenRecurringLimit`, `TokenMaxPerTx`, `ProgramWhitelist`, `ProgramBlacklist`. Action discriminators (1, 2, 3, 4, 5, 6, 10, 11) and the 11-byte header layout match `lazorkit-protocol` so the unified SDK can encode actions identically for both builds.
- `SessionAccount` is now variable-size: a session can carry a trailing action buffer (max 16 actions, ≤ 2048 bytes) validated at creation time.
- `CreateSession` instruction data accepts the new `[actions_len: u16][actions: N]` extension after the legacy 40-byte args; old 40-byte clients continue to work via the legacy parser branch.
- Pre-CPI action enforcement at `Execute` time: program whitelist/blacklist checks against each CPI target.
- Post-CPI action enforcement: SOL/token spending caps with saturating arithmetic; recurring-window resets aligned to slot boundaries; per-execute SOL outflow tracked across all CPIs for `SolMaxPerTx`.
- Vault-invariant defenses against `System::Assign` / `SetAuthority` / `Approve` escapes: vault owner + data-length snapshotted pre-CPI and verified unchanged post-CPI; vault-owned token accounts on listed mints have their owner / delegate / close_authority fields snapshotted and verified.
- Anti-CPI guard for session-authenticated `Execute`: stack-height must be 1 (rejects wrapper programs chaining through `Execute`).
- Error codes 3020–3029 (action validation + enforcement) and 3030–3032 (`SessionVaultOwnerChanged`, `SessionVaultDataLenChanged`, `SessionTokenAuthorityChanged`).
- Dual-cluster Cargo features (`mainnet`, `devnet`): the embedded program ID is chosen at compile time via a feature flag with a `compile_error!` if neither / both is set. The `mainnet` feature embeds `LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi` (same slot as `lazorkit-protocol`) for the foundation deployment; `devnet` keeps `FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao`.
- `security.txt` block embedded via `solana-security-txt` macro: links to SECURITY.md, contact email, source repo, source revision (from `GITHUB_SHA`), and the Accretion audit PDF.
- Zero-copy `CompactInstructionRef` parser (`parse_compact_instructions_ref_with_len`) used by the Execute hot path — no per-instruction `Vec<u8>` allocations for account-index bytes or instruction data.
- Cherry-pick guardrails: `scripts/fee-paths.txt` declares forbidden fee-surface paths and symbols, `scripts/check-no-fee.sh` verifies the working tree (used by CI), `scripts/strip-fee.sh` auto-removes fee files post-cherry-pick.
- CI workflow `check-no-fee` runs the verifier on every PR.
- CI workflow `sbf-cluster-check` builds both mainnet and devnet SBF binaries, verifies their hashes differ, and asserts that an unflagged `cargo build-sbf` fails with the expected `compile_error!`.
- `scripts/build-all.sh <devnet|mainnet>` now drives a feature-flagged build + IDL regen + SDK regen in one step. The previous `scripts/sync-program-id.sh` is removed (program ID is now a compile-time feature, not a sed target).
- `solana-security-txt` and `default-env` dependencies, `[workspace.metadata.cli]` pinning Solana CLI 3.0.4 for verified builds.
- Unified SDK API with discriminated union signer types (`ed25519()`, `secp256r1()`, `session()` helper constructors)
- `CreateWalletOwner` union type: single `createWallet()` method for both Ed25519 and Secp256r1
- `AdminSigner` union type for admin operations (addAuthority, removeAuthority, transferOwnership, createSession)
Expand Down
Loading
Loading