Canonical source:
.github/workflows/contracts.ymlThis document mirrors that workflow. If the workflow changes, update this file in the same pull request.
File: .github/workflows/contracts.yml
Triggers: push and pull_request to main
The workflow runs two parallel/independent jobs on ubuntu-latest:
This job runs a smoke check on formatting, building, and testing the onchain contracts tree.
| # | Step | Command | Working directory |
|---|---|---|---|
| 1 | Install Rust (stable + rustfmt) | managed by dtolnay/rust-toolchain@stable |
— |
| 2 | Cache Cargo registry | managed by Swatinem/rust-cache@v2 |
— |
| 3 | Check formatting | cargo fmt --all -- --check |
onchain/ |
| 4 | Build workspace | cargo build --workspace --verbose |
onchain/ |
| 5 | Test workspace | cargo test --workspace --verbose |
onchain/ |
| 6 | Install wasm32 target | rustup target add wasm32-unknown-unknown |
— |
| 7 | Build contracts to WASM | cargo build --workspace --release --target wasm32-unknown-unknown --verbose |
onchain/ |
| 8 | Run WASM size regression check | cargo run --release --manifest-path tools/wasm_size_check/Cargo.toml -- --baseline … --wasm-dir … --tolerance-pct 5 --fail-on-new --report … |
repo root |
| 9 | Upload size report artifact | managed by actions/upload-artifact@v4 |
— |
Steps 1–2 are handled automatically by GitHub Actions and have no equivalent
local command. Steps 3–8 are the checks contributors must pass. Step 9 is a
diagnostic convenience — its presence is gated on if: always() so it is
preserved on failure for post-mortem download.
This job builds and runs tools/doc_checker against the full docs/ and onchain/contracts/ tree.
It runs with the --strict and --events flags to promote any documentation gaps into hard failures.
| Step | Command | Working directory |
|---|---|---|
| 1. Install Rust | managed by dtolnay/rust-toolchain@stable |
— |
| 2. Cache Cargo registry | managed by Swatinem/rust-cache@v2 |
— |
| 3. Run doc_checker | ./tools/doc_checker/run_ci.py |
— |
Run the same checks CI executes, in the same order, before opening a PR.
| Requirement | How to install |
|---|---|
| Rust (stable) | rustup install stable && rustup default stable |
rustfmt component |
rustup component add rustfmt |
| WASM target | rustup target add wasm32-unknown-unknown |
No Stellar CLI is required to run the WASM build because we delegate to
cargo build --target wasm32-unknown-unknown directly. This is the only
target the Soroban host accepts; see docs/build-targets.md for the
rationale.
1. Contract checks (formatting, build, test)
cd onchain
# Formatting — must produce no diff
cargo fmt --all -- --check
# Build — all workspace crates must compile
cargo build --workspace --verbose
# Tests — all workspace tests must pass
cargo test --workspace --verbose
# 4. WASM build (step 7 in CI)
cargo build --workspace --release --target wasm32-unknown-unknownAnd then from the repository root:
# 5. WASM size regression check (step 8 in CI)
cargo run --release --manifest-path tools/wasm_size_check/Cargo.toml -- \
--baseline benchmarks/wasm_sizes.json \
--wasm-dir onchain/target/wasm32-unknown-unknown/release \
--tolerance-pct 5 \
--fail-on-newAll five commands must exit with code 0 for a PR to be mergeable.
Formatting failure
cargo fmt --all -- --check exits non-zero when any file would be
reformatted. Fix by running the formatter without --check:
cd onchain
cargo fmt --allThen commit the result before pushing.
Build failure
Resolve compiler errors reported by cargo build. The workspace uses
edition = "2021" and the stable Rust channel; ensure your toolchain is
up to date:
rustup update stableTest failure
Test output is printed with --verbose. Read the failure message and fix
the broken test or the code under test.
WASM size regression failure
The wasm_size_check step exits non-zero when any contract's compiled
size grew beyond the configured tolerance without a corresponding
baseline refresh. See WASM Size Budget Policy below for the policy
and update procedure.
Source of truth:
benchmarks/wasm_sizes.json(committed). Thewasm_size_checkbinary is a pure checker; it does not invokecargo build.
The Soroban host enforces a hard upper bound on contract bytecode size at
deployment time. An unnoticed size regression can push a contract closer
to (or past) that limit and only surface as a deployment failure —
potentially on mainnet. CI must therefore catch regressions before they
merge.
- CI builds every contract in the
onchainworkspace towasm32-unknown-unknownin release mode (step 7 above). - The committed
benchmarks/wasm_sizes.jsonfile records the size, SHA-256 (sha256:<hex>), and capture date for every successfully built.wasm. - After the build, CI invokes
wasm_size_check(step 8 above) and compares observed sizes against the baseline. - The job fails (
exit 1) if any contract:- Grows by more than the configured tolerance (currently 5 % of
the baseline size, computed as
delta_bytes / baseline_bytes, strictly greater than the threshold), without a refresh of itsbenchmarks/wasm_sizes.jsonentry in the same PR. - Has the same size as its baseline but a different SHA-256 — a strong signal the baseline entry was copy/pasted from a stale run.
- Has no entry in the baseline (a brand-new contract that has not
been bootstrapped yet), gated by
--fail-on-new. - Has a baseline entry but no
.wasmon disk — the contract was removed without pruning the baseline (override with--allow-missingonly for temporary experiments).
- Grows by more than the configured tolerance (currently 5 % of
the baseline size, computed as
- The job passes for any contract that:
- Exactly equals its baseline.
- Grew but stays within the tolerance.
- Shrank — shrinking is always a pass but reported in the table so reviewers are aware code was removed.
When a PR legitimately changes a contract's compiled size, refresh the baseline and commit the result in the same PR:
# 1. Build to wasm32 as usual.
cargo build --workspace --release --target wasm32-unknown-unknown
# 2. Refresh the committed baseline.
cargo run --release --manifest-path tools/wasm_size_check/Cargo.toml -- \
--baseline benchmarks/wasm_sizes.json \
--wasm-dir onchain/target/wasm32-unknown-unknown/release \
--update-baseline
# 3. Verify the change is intentional.
git diff benchmarks/wasm_sizes.json
# 4. Commit + push (in the same PR as the source change).
git add benchmarks/wasm_sizes.json
git commit -m "chore(wasm-size): refresh baseline for <list-of-changed-contracts>"
git pushA PR that introduces a regression without a matching baseline refresh will fail CI at step 8 with a clear table showing which contract(s) regressed, by how many bytes, and the percent delta.
The baseline file benchmarks/wasm_sizes.json is committed and may be
empty on first merge of this feature. After the first PR lands, follow
the update procedure above to populate it. A PR that adds a brand-new
contract crate must include a baseline entry for it (otherwise
--fail-on-new will trip CI).
The 5 % tolerance is a starting point chosen to allow genuine algorithmic improvements without forcing a baseline refresh on every minor change. Bumping the tolerance is a policy change and must:
- Be justified in the PR description with a size delta report.
- Be reviewed by a maintainer who understands the Soroban size budget.
- Update this document in the same PR.
The checker accepts a --tolerance-pct value at the command line so
individual PRs can override the default without modifying the workflow
file (use sparingly; prefer updating the central default).
See tools/wasm_size_check/README.md for the full set of flags:
| Flag | Default | Effect |
|---|---|---|
--tolerance-pct <n> |
5 |
Maximum allowed percent growth |
--update-baseline |
off | Refresh the baseline with current measurements |
--fail-on-new |
off | Fail when a .wasm has no baseline entry |
--allow-missing |
off | Skip baseline entries with no .wasm (instead of failing) |
--report <path> |
stdout | Also write the Markdown report to this path |
Step 8 also writes artifacts/wasm_size_report.md (and uploads it as
the wasm-size-report artifact on every run, including failed ones).
This is intended for post-mortem inspection when CI fails — open the
artifact in the GitHub Actions UI to see the full regression table.
File: .github/workflows/security-scan.yml
Triggers: schedule (weekly, Monday 06:00 UTC) and workflow_dispatch
The workflow runs a single job (semver-checks) on ubuntu-latest that
installs cargo-semver-checks and runs check-release against every
contract crate under onchain/contracts/.
Each crate is compared against its last tagged release (e.g.
stello_pay_contract-v0.1.0). If no tag exists for the current
Cargo.toml version, the crate is skipped (first release).
| Step | Command / Action |
|---|---|
| 1. Checkout full history | actions/checkout@v7 with fetch-depth: 0 |
| 2. Install Rust stable | dtolnay/rust-toolchain@stable |
| 3. Cache Cargo artifacts | Swatinem/rust-cache@v2 |
4. Install cargo-semver-checks |
taiki-e/install-action@v2 |
| 5. Semver check per crate | cargo semver-checks check-release -p <crate> --baseline-rev <tag> |
Any of the following is a breaking change and must be accompanied by a
version bump in Cargo.toml:
- Removing or renaming a
#[contractimpl]method. - Adding, removing, or reordering parameters.
- Changing a parameter or return type.
- Removing or renaming a public struct, enum, or variant.
- Narrowing the visibility of a public item.
Additive changes (new methods, new types) are allowed without a version bump.
Prerequisites:
cargo install cargo-semver-checksCheck a single crate against its last tagged release:
cd onchain
cargo semver-checks check-release -p stello_pay_contract \
--baseline-rev stello_pay_contract-v0.0.0Compare against the previous commit (useful during development):
cargo semver-checks check-release -p stello_pay_contract \
--baseline-rev HEAD~1After bumping a crate's version in Cargo.toml, create a matching tag so
the scheduled workflow can use it as a baseline:
git tag stello_pay_contract-v0.1.0
git push origin stello_pay_contract-v0.1.0Tag format: <crate_name>-v<semver> (e.g. rbac-v0.1.0,
compliance_checker-v0.1.0).
The following are not part of the automated CI pipeline and are therefore not required to pass before merging:
cargo clippy— linting is not enforced by the workflow.- Coverage reporting — no
cargo llvm-covstep exists in the current workflow. stellar contract build— CI uses rawcargo build --target wasm32-unknown-unknown.stellar contract buildis functionally equivalent but is not a dependency of CI.- Per-package test runs — CI uses
--workspace; there are no per-crate steps.
If any of the above are added to
.github/workflows/contracts.ymlin the future, this section and the Run locally section above must both be updated.
File: .github/workflows/auto-assign.yml
Triggers: issue_comment (created)
This workflow automatically assigns an issue to a contributor when they
comment with an assignment phrase (e.g. /assign, I'd like to work on this).
It is a repository-management workflow only and does not perform any code
quality checks. Contributors do not need to run anything locally to satisfy it.
Tests on main must be either active or deleted. Do not leave Rust test files
with a .disabled suffix or similar opt-out extension in contract test
directories. If a test breaks during SDK or API migration, either update it in
the same change or delete it when active coverage already supersedes it.