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
70 changes: 38 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ env:
RUST_NIGHTLY_TOOLCHAIN: 'nightly-2026-09-01'
NODE_VERSION: '22.23.2'
PNPM_VERSION: '10.28.2'
STELLAR_CLI_VERSION: '22.0.1'
# sha256 of stellar-cli-${STELLAR_CLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz
STELLAR_CLI_SHA256: '02cb00cf57ce8f432e0cc1653a1b603353a97079c22a8370dac60c21eca18fe3'
STELLAR_CLI_VERSION: '26.1.0'
# sha256 of stellar-cli-${STELLAR_CLI_VERSION}-x86_64-unknown-linux-gnu.tar.gz,
# as published by the upstream release.
STELLAR_CLI_SHA256: 'e18d5a7629102e1ccc07241acbcbebfc05b1c02476ce7d3204ba2d7418be5c0c'
FOUNDRY_VERSION: 'v1.8.3'
SLITHER_VERSION: '0.11.4'
KANI_VERSION: '0.68.0'
Expand Down Expand Up @@ -233,24 +234,26 @@ jobs:
path: |
stellar/bench/data/crossover.csv
stellar/bench/data/crossover-chart.md
# WASM build depends on soroban-sdk 22.0.11 compiling under current
# stable rustc, which fails with 162 unresolved-import errors in the
# SDK's transitive deps. This is a soroban-sdk 22.0.11 bug fixed in
# 23.x/27.x — bumping is a major API migration owned separately.
# Keep the step visible but non-blocking so it doesn't gate wave PRs.
- name: Build WASM (soroban-sdk 22 vs current rustc; non-blocking)
continue-on-error: true
# The 162 errors this step used to produce were not a soroban-sdk/rustc
# incompatibility: a plain `cargo build` in a virtual workspace selects
# every member, and bench, bench-crossover and integration-tests enable
# soroban-sdk/testutils, which the SDK refuses to compile for wasm32.
# default-members in stellar/Cargo.toml limits the default build to the
# contract crates. See SUPPLY_CHAIN.md for the pins and upgrade path.
- name: Build WASM
run: cargo build --target wasm32-unknown-unknown --release
- name: Optimize and Check WASM Size
continue-on-error: true
run: |
if ! ls target/wasm32-unknown-unknown/release/*.wasm > /dev/null 2>&1; then
echo "No WASM artifacts (build step above did not produce output); skipping optimize."
exit 0
shopt -s nullglob
artifacts=(target/wasm32-unknown-unknown/release/*.wasm)
if [ ${#artifacts[@]} -eq 0 ]; then
echo "Error: the WASM build produced no artifacts."
exit 1
fi
for wasm in target/wasm32-unknown-unknown/release/*.wasm; do
soroban contract optimize --wasm "$wasm"
opt_wasm="${wasm%.*}_optimized.wasm"
for wasm in "${artifacts[@]}"; do
stellar contract optimize --wasm "$wasm"
# `contract optimize` writes "<name>.optimized.wasm" beside the input.
opt_wasm="${wasm%.wasm}.optimized.wasm"
SIZE=$(stat -c%s "$opt_wasm")
echo "$opt_wasm size is $SIZE bytes"
if [ "$SIZE" -gt 112640 ]; then
Expand All @@ -262,30 +265,33 @@ jobs:
- name: Run Upgrade Authority Enforcement Tests
run: cargo test upgrade_auth --workspace

# Bindings depend on a successful workspace WASM build (currently
# non-blocking; see the note on the Build WASM step). Mark this
# non-blocking too so bindings-drift detection doesn't spuriously
# fail wave PRs.
- name: Generate Stellar TypeScript bindings
id: bindings
continue-on-error: true
run: pnpm bindings:stellar
working-directory: .

# Scoped to the generated artifacts: `cargo test --workspace` earlier in
# this job rewrites soroban test_snapshots, and that churn must not be
# attributed to the bindings.
- name: Check for bindings drift (diff detection)
if: steps.bindings.outcome == 'success'
run: git diff --exit-code
working-directory: .
# ABI extraction depends on the same soroban-sdk 22 WASM build as above.
# When the SDK bump (23.x/27.x) lands, remove continue-on-error to
# enforce ABI snapshot freshness on every PR.
run: |
if [ -n "$(git status --porcelain --untracked-files=all -- stellar/bindings)" ]; then
git --no-pager status --short --untracked-files=all -- stellar/bindings
git --no-pager diff -- stellar/bindings
echo "Error: Stellar bindings are out of date. Run pnpm bindings:stellar and commit the result."
exit 1
fi

- name: Extract ABI snapshots
id: abi
continue-on-error: true
run: ./abi/update.sh
- name: Check ABI snapshots
if: steps.abi.outcome == 'success'
run: git diff --exit-code abi/
run: |
if [ -n "$(git status --porcelain --untracked-files=all -- abi/)" ]; then
git --no-pager status --short --untracked-files=all -- abi/
git --no-pager diff --stat -- abi/
echo "Error: ABI snapshots are out of date. Run stellar/abi/update.sh and commit the result."
exit 1
fi

stellar-kani:
needs: changes
Expand Down
60 changes: 57 additions & 3 deletions SUPPLY_CHAIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Declared once per workflow in a top-level `env:` block headed
| Rust (Solana) | `1.91.0` action / `1.89.0` `solana/rust-toolchain.toml` | `ci.yml` | rustup channel manifest signatures |
| Node.js | `22.23.2` | `ci.yml`, `audit-freeze.yml`, `stellar-verification.yml`, `supply-chain.yml` | `actions/setup-node` release checksums |
| pnpm | `10.28.2` | same as Node.js, and `packageManager` in `package.json` | corepack npm registry signature |
| stellar-cli (CI) | `22.0.1` | `ci.yml` (`STELLAR_CLI_SHA256`) | `sha256sum -c` against the pinned hash |
| stellar-cli (CI) | `26.1.0` | `ci.yml` (`STELLAR_CLI_SHA256`) | `sha256sum -c` against the pinned hash |
| stellar-cli (Futurenet) | `28.0.0` | `integration-futurenet.yml` | `cargo install --locked` (crates.io checksums) |
| Foundry | `v1.8.3` | `ci.yml` | `foundry-rs/foundry-toolchain` |
| slither-analyzer | `0.11.4`, solc `0.8.28` | `ci.yml` | PyPI via `crytic/slither-action` |
Expand All @@ -59,6 +59,52 @@ Declared once per workflow in a top-level `env:` block headed
| cargo-tarpaulin | `0.37.4` | `coverage.yml` | `cargo install --locked` |
| cosign | `v2.2.4` | `stellar-attestation.yml` | `sigstore/cosign-installer` checksum |

### Stellar artifact checks

The `stellar` job builds the contract WASM, optimizes it, regenerates
`stellar/bindings/typescript/**` and `stellar/abi/*.json`, and fails the PR if
either set of checked-in artifacts moves. Nothing in that chain is
`continue-on-error` any more, so the three constraints below decide the pins.

- **The wasm32 target set must stay free of `soroban-sdk/testutils`.** The SDK
guards that feature with `compile_error!` on wasm, and a plain `cargo build` in
a virtual workspace selects *every* member, including the host-only `bench`,
`bench-crossover` and `integration-tests` crates that enable it.
`default-members` in `stellar/Cargo.toml` is what keeps
`cargo build --target wasm32-unknown-unknown --release` building only the ten
deployable members (nine cdylibs and the `wraith-metrics` helper library, which
emits no WASM). A new member that enables `testutils` has to stay out of that
list; otherwise the wasm build fails with hundreds of errors inside the SDK
rather than in the new crate, which reads like a toolchain bug.
- **Contract code may not use host-only SDK APIs.** Converting an `Address` into
an `xdr::ScAddress` exists only behind `cfg(not(target_family = "wasm"))`, and
linking `alloc` unconditionally leaves the cdylib without a global allocator.
Both failures surface in this blocking step, not in `cargo test`.
- **`contract optimize` needs a wasm-opt that accepts bulk memory.** rustc emits
`memory.copy`/`memory.fill` for four of the nine contracts on every toolchain
tested (1.88.0 and 1.98.1), and stellar-cli 22.0.1's bundled optimizer rejects
them during validation, so the size gate could never pass for those artifacts.
26.1.0 validates all nine and is the CI pin.

The upgrade path, in short:

- 26.1.0 is the version that produced the bindings already committed here. Its
template emits `@stellar/stellar-sdk` `^14.5.0` and no `networks` block, which
is what every checked-in client looks like. Pinning a different CLI therefore
regenerates all five clients and their `package.json`, so a CLI bump is an
artifact-regeneration PR with reviewable output, never a one-line config bump.
- Do not move past 26.x without checking both outputs. stellar-cli 28.1.0 renames
the `type_` key to `type` in `contract info interface --output json-formatted`,
which rewrites every `stellar/abi/*.json` snapshot, and marks
`contract bindings typescript` deprecated in favour of the JavaScript SDK
generator. Each needs a decision and a fresh baseline before the pin moves.
28.1.0's optimizer produces the same payloads as 26.1.0, so the size gate is
not what holds the pin back.
- The release container (next section) still builds with stellar-cli 22.0.0,
which cannot validate those four contracts, so `stellar/build/build.sh` needs
the same bump. That changes deployed hashes, so it belongs to a planned
redeploy rather than to this check; tracked as a known gap below.

### Release build (Stellar reproducible build)

| Input | Pin | Where |
Expand Down Expand Up @@ -185,8 +231,16 @@ any other `.github/` change.
- `model-checking/kani-github-action` and `heyAyushh/setup-anchor` call further
actions by moving tag internally. We pin the outer action. Upgrading those
inner actions requires bumping the outer SHA.
- stellar-cli `22.0.1` publishes neither checksums nor build provenance. The
pinned hash was taken from the GitHub release asset on first use.
- stellar-cli `22.0.1`, the previous CI pin, published neither checksums nor
build provenance, and its optimizer cannot validate the workspace's
bulk-memory contracts. `26.1.0` publishes a sha256 digest per release asset;
`STELLAR_CLI_SHA256` is that upstream digest, and CI re-checks the downloaded
archive against it.
- The release container's stellar-cli `22.0.0` has the same optimizer as the old
CI pin, so `stellar/build/build.sh` aborts on the four contracts that use bulk
memory. Bumping it changes the optimized bytes, which changes the hashes
`stellar/build/verify.js` compares against deployed contracts, so it belongs to
a planned redeploy rather than to the CI pin above.
- The repository dependency graph is not enabled. Enabling it (Settings → Code
security) would additionally allow `actions/dependency-review-action` for
license policy. The OSV review above does not require it.
18 changes: 18 additions & 0 deletions stellar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ members = [
"integration-tests",
"contracts/governance",
]
# A plain `cargo build` from this directory produces the deployable contract
# artifacts, so it must select only the contract crates. `bench`,
# `bench-crossover` and `integration-tests` are host-only: they enable
# soroban-sdk/testutils in [dependencies], and the SDK refuses to compile
# testutils for wasm32, so including them in the default set breaks every wasm
# build (see SUPPLY_CHAIN.md). Reach them with --workspace or -p.
default-members = [
"stealth-announcer",
"stealth-registry",
"stealth-sender",
"stealth-splitter",
"stealth-batch-sender",
"stealth-vault",
"wraith-names",
"wraith-asset-policy",
"wraith-metrics",
"contracts/governance",
]
resolver = "2"

[workspace.dependencies]
Expand Down
18 changes: 12 additions & 6 deletions stellar/DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ STELLAR_ADMIN_SECRET=SCVAL... \
### Requirements

- `STELLAR_ADMIN_SECRET` env var (Stellar secret key, must be funded on futurenet)
- `stellar-cli >= 22.0.1` (or `soroban-cli`)
- `stellar-cli >= 26.1.0` (or `soroban-cli`)
- Rust `wasm32-unknown-unknown` target installed
- The admin account must have sufficient XLM balance (≥ 10 XLM recommended)

Expand Down Expand Up @@ -102,13 +102,19 @@ This script runs on the `stellar-nightly` schedule in CI. See
|---|---|---|
| Rust toolchain | stable ≥ 1.78 | `rustup update stable` |
| wasm32 target | any | `rustup target add wasm32-unknown-unknown` |
| stellar-cli | 22.0.1 | `cargo install stellar-cli --locked` |
| stellar-cli | 26.1.0 | `cargo install stellar-cli --locked` |
| Funded identity | — | see section below |

> **Why 22.0.1?** Earlier versions do not support the `--fee` flag on contract
> invocations used by the `init` call. Attempting to deploy with an older CLI
> will succeed for upload/deploy but fail silently on init with a fee-budget
> error that is only visible in the node logs.
> **Why 26.1.0?** Two reasons, both learned the hard way. Earlier versions do
> not support the `--fee` flag on contract invocations used by the `init`
> call, so a deploy succeeds for upload/deploy but fails silently on init with a
> fee-budget error that is only visible in the node logs. And the 22.x CLI cannot
> optimize this workspace: rustc emits bulk-memory instructions for four of the
> nine contracts, which the wasm-opt bundled with 22.x rejects during validation.
> CI pins the same 26.1.0 and regenerates the TypeScript bindings with it, so a
> different CLI still deploys fine but will not reproduce
> `stellar/bindings/typescript`. See
> [`../SUPPLY_CHAIN.md`](../SUPPLY_CHAIN.md).

---

Expand Down
6 changes: 6 additions & 0 deletions stellar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ To run tests for all contracts:
cargo test
```

`cargo test` from this directory covers the crates in `default-members`, which
is the deployable set. The host-only members (`bench`, `bench-crossover`,
`integration-tests`) enable `soroban-sdk/testutils` and are excluded from the
default target set so that the wasm build stays clean; add `--workspace` to run
them too.

## Audits & Formal Verification

Per-contract audit write-ups for the contracts that custody user funds:
Expand Down
Loading
Loading