Skip to content

Commit 5d68d56

Browse files
authored
Merge pull request #53 from lazor-kit/chore/docs-refresh
docs: refresh markdown for current codebase (dual-cluster IDs, action permissions, current test counts)
2 parents 2e549c2 + e007788 commit 5d68d56

95 files changed

Lines changed: 11743 additions & 7395 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
name: release
2+
3+
# Triggered by pushing an `audit-frozen-v*` tag (or a `v*` semver release tag).
4+
# Builds the mainnet and devnet SBF binaries, records their SHA-256 hashes,
5+
# and attaches the .so files + a manifest to the GitHub Release.
6+
#
7+
# The release artifacts are the source of truth for what gets deployed to
8+
# mainnet — production deploys must use a binary downloaded from a release,
9+
# not a locally-built one. See docs/MAINNET_DEPLOY.md.
10+
11+
on:
12+
push:
13+
tags:
14+
- 'audit-frozen-v*'
15+
- 'v*.*.*'
16+
17+
jobs:
18+
release:
19+
runs-on: ubuntu-latest
20+
timeout-minutes: 20
21+
permissions:
22+
contents: write # for creating GitHub Releases
23+
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
# Full history so source_revision in security_txt embeds the right SHA.
28+
fetch-depth: 0
29+
30+
- name: Install Solana toolchain
31+
run: |
32+
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
33+
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"
34+
35+
- name: Pin Solana CLI to the version declared in Cargo.toml
36+
run: |
37+
DECLARED=$(grep -A1 'workspace.metadata.cli' Cargo.toml | grep solana | sed -E 's/.*"([^"]+)".*/\1/')
38+
INSTALLED=$(solana --version | awk '{print $2}')
39+
echo "Declared: $DECLARED"
40+
echo "Installed: $INSTALLED"
41+
if [ "$DECLARED" != "$INSTALLED" ]; then
42+
echo "::warning::Installed Solana CLI ($INSTALLED) does not match declared ($DECLARED). Verified-build hashes may differ from what consumers reproduce."
43+
fi
44+
45+
- name: Cache cargo build
46+
uses: actions/cache@v4
47+
with:
48+
path: |
49+
~/.cargo/registry
50+
~/.cargo/git
51+
target
52+
key: release-${{ hashFiles('**/Cargo.lock') }}-${{ github.ref_name }}
53+
54+
- name: Build mainnet binary
55+
working-directory: program
56+
env:
57+
GITHUB_SHA: ${{ github.sha }}
58+
GITHUB_REF_NAME: ${{ github.ref_name }}
59+
run: cargo build-sbf --features mainnet
60+
61+
- name: Hash + stage mainnet artifact
62+
run: |
63+
mkdir -p release-artifacts
64+
cp target/deploy/lazorkit_program.so release-artifacts/lazorkit_program-mainnet.so
65+
MAINNET_SHA=$(shasum -a 256 release-artifacts/lazorkit_program-mainnet.so | awk '{print $1}')
66+
echo "MAINNET_SHA=$MAINNET_SHA" >> "$GITHUB_ENV"
67+
echo "mainnet sha256: $MAINNET_SHA"
68+
69+
- name: Build devnet binary
70+
working-directory: program
71+
env:
72+
GITHUB_SHA: ${{ github.sha }}
73+
GITHUB_REF_NAME: ${{ github.ref_name }}
74+
run: cargo build-sbf --features devnet
75+
76+
- name: Hash + stage devnet artifact
77+
run: |
78+
cp target/deploy/lazorkit_program.so release-artifacts/lazorkit_program-devnet.so
79+
DEVNET_SHA=$(shasum -a 256 release-artifacts/lazorkit_program-devnet.so | awk '{print $1}')
80+
echo "DEVNET_SHA=$DEVNET_SHA" >> "$GITHUB_ENV"
81+
echo "devnet sha256: $DEVNET_SHA"
82+
83+
- name: Verify binaries differ
84+
run: |
85+
if [ "$MAINNET_SHA" = "$DEVNET_SHA" ]; then
86+
echo "::error::mainnet and devnet binaries are identical — dual-cluster mechanism broken"
87+
exit 1
88+
fi
89+
90+
- name: Stage IDL + keypair
91+
run: |
92+
cp program/idl.json release-artifacts/idl.json
93+
# The keypair file is regenerated per build; useful as a record but
94+
# NOT for deployment (the actual mainnet keypair is held off-CI).
95+
if [ -f target/deploy/lazorkit_program-keypair.json ]; then
96+
cp target/deploy/lazorkit_program-keypair.json release-artifacts/build-keypair.json
97+
fi
98+
99+
- name: Write release manifest
100+
run: |
101+
cat > release-artifacts/MANIFEST.txt <<EOF
102+
LazorKit program-v2 release manifest
103+
tag: ${{ github.ref_name }}
104+
commit: ${{ github.sha }}
105+
built: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
106+
solana-cli: $(solana --version)
107+
rust-toolchain: $(rustc --version)
108+
109+
mainnet binary: lazorkit_program-mainnet.so
110+
mainnet sha256: $MAINNET_SHA
111+
mainnet program ID: LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi
112+
113+
devnet binary: lazorkit_program-devnet.so
114+
devnet sha256: $DEVNET_SHA
115+
devnet program ID: FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao
116+
117+
To reproduce these hashes locally:
118+
git checkout ${{ github.ref_name }}
119+
cd program
120+
cargo build-sbf --features mainnet # → mainnet sha256 above
121+
cargo build-sbf --features devnet # → devnet sha256 above
122+
123+
To deploy: see docs/MAINNET_DEPLOY.md
124+
EOF
125+
cat release-artifacts/MANIFEST.txt
126+
127+
- name: Create GitHub Release
128+
uses: softprops/action-gh-release@v2
129+
with:
130+
name: ${{ github.ref_name }}
131+
tag_name: ${{ github.ref_name }}
132+
body: |
133+
**Tag:** `${{ github.ref_name }}`
134+
**Commit:** `${{ github.sha }}`
135+
136+
**Mainnet binary:** `lazorkit_program-mainnet.so`
137+
**Mainnet sha256:** `${{ env.MAINNET_SHA }}`
138+
**Mainnet program ID:** `LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi` (slot shared with `lazorkit-protocol`)
139+
140+
**Devnet binary:** `lazorkit_program-devnet.so`
141+
**Devnet sha256:** `${{ env.DEVNET_SHA }}`
142+
**Devnet program ID:** `FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao`
143+
144+
See [`MANIFEST.txt`](./MANIFEST.txt) for build environment + reproduction
145+
commands and [`docs/MAINNET_DEPLOY.md`](../docs/MAINNET_DEPLOY.md) for
146+
deployment procedure.
147+
files: |
148+
release-artifacts/lazorkit_program-mainnet.so
149+
release-artifacts/lazorkit_program-devnet.so
150+
release-artifacts/idl.json
151+
release-artifacts/MANIFEST.txt
152+
draft: ${{ startsWith(github.ref_name, 'audit-frozen-') }}
153+
prerelease: ${{ startsWith(github.ref_name, 'audit-frozen-') }}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: SBF cluster feature check
2+
3+
# Verifies the Pattern D feature-flag mechanism in `assertions/src/lib.rs`:
4+
#
5+
# 1. `cargo build-sbf --features mainnet` succeeds and produces a binary
6+
# embedding the mainnet vanity ID.
7+
# 2. `cargo build-sbf --features devnet` succeeds and produces a binary
8+
# embedding the devnet ID.
9+
# 3. The two binaries differ (otherwise the feature flag has been
10+
# neutralised by a refactor and Pattern D no longer protects against
11+
# cross-cluster deploys).
12+
# 4. `cargo build-sbf` with no feature flag fails with the expected
13+
# `compile_error!` (otherwise nothing prevents an unflagged build
14+
# from silently embedding whichever ID happens to be the default).
15+
#
16+
# Runs on every PR touching the program / assertions crate or this workflow.
17+
18+
on:
19+
pull_request:
20+
paths:
21+
- 'program/**'
22+
- 'assertions/**'
23+
- '.github/workflows/sbf-cluster-check.yml'
24+
push:
25+
branches: [main]
26+
27+
jobs:
28+
cluster-check:
29+
runs-on: ubuntu-latest
30+
timeout-minutes: 15
31+
steps:
32+
- uses: actions/checkout@v4
33+
34+
- name: Install Solana toolchain
35+
run: |
36+
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
37+
echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH"
38+
39+
- name: Cache cargo build
40+
uses: actions/cache@v4
41+
with:
42+
path: |
43+
~/.cargo/registry
44+
~/.cargo/git
45+
target
46+
key: sbf-${{ hashFiles('**/Cargo.lock') }}
47+
48+
- name: Build mainnet binary
49+
working-directory: program
50+
run: cargo build-sbf --features mainnet
51+
52+
- name: Record mainnet hash
53+
id: mainnet
54+
run: |
55+
M=$(shasum -a 256 target/deploy/lazorkit_program.so | awk '{print $1}')
56+
echo "sha=$M" >> "$GITHUB_OUTPUT"
57+
echo "mainnet SBF: $M"
58+
59+
- name: Build devnet binary
60+
working-directory: program
61+
run: cargo build-sbf --features devnet
62+
63+
- name: Record devnet hash
64+
id: devnet
65+
run: |
66+
D=$(shasum -a 256 target/deploy/lazorkit_program.so | awk '{print $1}')
67+
echo "sha=$D" >> "$GITHUB_OUTPUT"
68+
echo "devnet SBF: $D"
69+
70+
- name: Verify binaries differ
71+
run: |
72+
if [ "${{ steps.mainnet.outputs.sha }}" = "${{ steps.devnet.outputs.sha }}" ]; then
73+
echo "ERROR: mainnet + devnet SBF binaries are identical."
74+
echo " Pattern D's compile-time cluster switch has been"
75+
echo " neutralised — likely a refactor removed the cfg gate"
76+
echo " on declare_id! in assertions/src/lib.rs."
77+
exit 1
78+
fi
79+
echo "✓ binaries differ as expected"
80+
81+
- name: Verify no-feature build fails with compile_error!
82+
working-directory: program
83+
run: |
84+
# Capture exit code separately — `cmd | tee` returns tee's exit
85+
# (always 0), masking cargo's failure. `set -o pipefail` would
86+
# also work, but capturing to a file gives us the log to search
87+
# afterwards regardless of pipeline state.
88+
set +e
89+
cargo build-sbf > /tmp/build.log 2>&1
90+
BUILD_EXIT=$?
91+
set -e
92+
cat /tmp/build.log
93+
if [ "$BUILD_EXIT" -eq 0 ]; then
94+
echo "ERROR: cargo build-sbf without --features mainnet/devnet succeeded."
95+
echo " The compile_error! in assertions/src/lib.rs is no longer firing."
96+
exit 1
97+
fi
98+
if ! grep -q "pick exactly one cluster" /tmp/build.log; then
99+
echo "ERROR: build failed but not with the expected compile_error message."
100+
echo " Expected: 'pick exactly one cluster — --features mainnet OR --features devnet'"
101+
exit 1
102+
fi
103+
echo "✓ no-feature build correctly rejected by compile_error!"
104+
105+
- name: Verify both-features build fails
106+
working-directory: program
107+
run: |
108+
set +e
109+
cargo build-sbf --features mainnet --features devnet > /tmp/build-both.log 2>&1
110+
BUILD_EXIT=$?
111+
set -e
112+
cat /tmp/build-both.log
113+
if [ "$BUILD_EXIT" -eq 0 ]; then
114+
echo "ERROR: cargo build-sbf with BOTH features succeeded."
115+
echo " The compile_error! mutual-exclusion guard is broken."
116+
exit 1
117+
fi
118+
if ! grep -q "pick exactly one cluster" /tmp/build-both.log; then
119+
echo "ERROR: build failed but not with the expected compile_error message."
120+
exit 1
121+
fi
122+
echo "✓ both-features build correctly rejected"

CHANGELOG.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88

99
### Added
1010

11+
- End-to-end vitest tests for session-action enforcement (`tests-sdk/tests/12-actions.test.ts`, 9 cases): `programWhitelist` allow + reject (3021), `programBlacklist` allow + reject (3022), `solMaxPerTx` allow at-cap + reject over-cap (3023), `solLimit` lifetime budget exhaustion (3024), and combined-rules enforcement. Runs against a live `solana-test-validator` with the foundation binary loaded and uses `@lazorkit/sdk-legacy`'s `Actions` builder to dogfood the full encode → on-chain enforce path.
12+
- `docs/audit/` artifacts for an Accretion delta-audit follow-up: `DELTA_BRIEF.md` summarises the changes from the previous audited baseline by phase with explicit audit asks; `program-src.diff` is the full unified diff of `program/`; `program-src.diff.stat` is a per-file changed-line summary; `upstream-parity.txt` reports byte-identity vs the already-audited `lazorkit-protocol` per file (13/19 changed files identical).
13+
- Local git tags `audit-baseline-2026-02-accretion` (previous Accretion-audited state, commit `d1eaaeb`) and `audit-pending-v1` (the current consolidated state ready for delta review).
14+
- 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.
15+
- `SessionAccount` is now variable-size: a session can carry a trailing action buffer (max 16 actions, ≤ 2048 bytes) validated at creation time.
16+
- `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.
17+
- Pre-CPI action enforcement at `Execute` time: program whitelist/blacklist checks against each CPI target.
18+
- 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`.
19+
- 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.
20+
- Anti-CPI guard for session-authenticated `Execute`: stack-height must be 1 (rejects wrapper programs chaining through `Execute`).
21+
- Error codes 3020–3029 (action validation + enforcement) and 3030–3032 (`SessionVaultOwnerChanged`, `SessionVaultDataLenChanged`, `SessionTokenAuthorityChanged`).
22+
- 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`.
23+
- `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.
24+
- 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.
25+
- 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.
26+
- CI workflow `check-no-fee` runs the verifier on every PR.
27+
- 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!`.
28+
- `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).
29+
- `solana-security-txt` and `default-env` dependencies, `[workspace.metadata.cli]` pinning Solana CLI 3.0.4 for verified builds.
1130
- Unified SDK API with discriminated union signer types (`ed25519()`, `secp256r1()`, `session()` helper constructors)
1231
- `CreateWalletOwner` union type: single `createWallet()` method for both Ed25519 and Secp256r1
1332
- `AdminSigner` union type for admin operations (addAuthority, removeAuthority, transferOwnership, createSession)
@@ -36,7 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
3655
- Odometer counter replay protection for Secp256r1 (monotonic u32 per authority)
3756
- program_id included in challenge hash (cross-program replay prevention)
3857
- rpId stored on authority account at creation (saves ~14 bytes per transaction)
39-
- TypeScript SDK (`sdk/solita-client`) with Solita code generation
58+
- TypeScript SDK: standardised on `@lazorkit/sdk-legacy` (lives in sibling `lazorkit-protocol` repo); the in-tree `sdk/solita-client` has been removed
4059
- Integration + security test suite (`tests-sdk/`) with 56 tests across 11 files
4160
- Benchmark script for CU and transaction size measurements
4261
- CompactInstructions accounts hash for anti-reordering protection
@@ -51,6 +70,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5170

5271
### Changed
5372

73+
- Secp256r1 auth payload format: replaces the older `typeAndFlags` byte at `auth_payload[13]` with full raw `clientDataJSON` embedded in the payload. The on-chain auth verifier now parses the JSON directly rather than reconstructing it from `typeAndFlags + rpId`. Aligns with `lazorkit-protocol` byte-for-byte and is required for binary-swap compatibility at the shared mainnet slot.
74+
- Secp256r1 authority on-chain layout: replaces the previously stored variable-length raw `rpId` with a precomputed 32-byte `rpIdHash` (SHA-256 digest computed at registration). New layout: `header(48) + cred_hash(32) + pubkey(33) + rpIdHash(32) = 145 bytes`. Saves one `sol_sha256` syscall per `Execute`. Existing wallets created on the upstream commercial binary remain readable after binary swap.
75+
- Shank IDL declarations on the `ProgramIx` enum (account metadata: `writable` modifiers, account positions, descriptions) resynced with `lazorkit-protocol`. Five fee-related variants (disc 10–14: `InitializeProtocol`, `UpdateProtocol`, `RegisterPayer`, `WithdrawTreasury`, `InitializeTreasuryShard`) stripped — `program-v2` keeps disc 0–9 only. Runtime not affected (`@lazorkit/sdk-legacy` uses hand-written builders rather than the generated IDL).
5476
- SDK API: unified all methods via discriminated unions (breaking: removed `createWalletEd25519`, `createWalletSecp256r1`, `addAuthoritySecp256r1`, `removeAuthoritySecp256r1`, `executeEd25519`, `executeSecp256r1`, `executeSession`, `createSessionSecp256r1`, `transferOwnershipSecp256r1`, `authorizeSecp256r1`)
5577
- SDK API: all methods now return `{ instructions: TransactionInstruction[]; ...extraPdas }` consistently
5678
- SDK API: `createSession` now takes `sessionKey: PublicKey` instead of `Uint8Array`
@@ -71,6 +93,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
7193

7294
### Fixed
7395

96+
- `tests-sdk` integration tests now pass `PROGRAM_ID` explicitly to the `LazorKitClient` constructor. `@lazorkit/sdk-legacy`'s URL-based program-ID inference defaulted localhost to the commercial devnet ID (`4h3X…`); against a local validator loading the foundation binary at the keypair's pubkey this caused all txs to fail with "Attempt to load a program that does not exist". `tests/common.ts` now resolves `PROGRAM_ID` from (1) `PROGRAM_ID` env override, (2) the keypair file at `target/deploy/lazorkit_program-keypair.json`, or (3) the foundation devnet fallback `FLb7…`.
97+
- `tests-sdk/tests/08-deferred.test.ts` builds the `Authorize` `signed_payload` as `instructions_hash || accounts_hash || expiry_offset (u16 LE)` to match what the on-chain verifier hashes. The test code was missing the 2-byte expiry buffer at all 6 sign sites, causing all 7 deferred tests to fail with `InvalidMessageHash` (3005). After the fix, all 65 vitest E2E tests pass against a live validator.
7498
- Authorize signed payload now includes `expiry_offset` (66 bytes total), preventing relayers from modifying the expiry window
7599
- `sol_assert_bytes_eq` now uses the `len` parameter instead of `left.len()` (latent OOB read on-chain)
76100
- `reclaim_deferred` uses `checked_add` for lamports (consistent with `execute_deferred` and `manage_authority`)

0 commit comments

Comments
 (0)