Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion .github/workflows/check-pr-description.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Write PR body to file
env:
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Cache dependencies
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/
Expand All @@ -22,6 +22,10 @@ jobs:
target/
key: ${{ runner.os }}-cargo-docs-${{ hashFiles('**/Cargo.lock') }}

# docs.sh -> build.sh -> wasm-opt
- name: Ensure wasm-opt
run: ./scripts/wasm-opt.sh --ensure-tool

- name: Build documentation
# the `--enable-index-page` flag creates an index.html file at the root of the docs
# the `-Zunstable-options` flag is needed to use `--enable-index-page`
Expand All @@ -30,7 +34,7 @@ jobs:

- name: Upload documentation files as artifact
id: docs-artifact
uses: actions/upload-pages-artifact@v5
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
with:
path: target/doc
retention-days: 10
Expand All @@ -47,4 +51,4 @@ jobs:
steps:
- name: Deploy to Documentation to GitHub Pages
id: docs-artifact
uses: actions/deploy-pages@v5
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5
57 changes: 32 additions & 25 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Cache dependencies
uses: actions/cache/restore@v6
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/
Expand All @@ -28,20 +28,20 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: actions-rust-lang/setup-rust-toolchain@v2
- uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
with:
toolchain: stable
components: clippy, rustfmt
# This runs the pre-commit hooks defined in .pre-commit-config.yaml
# TODO: figure out how to update to use pre-commit.ci
- uses: pre-commit/action@v3.0.1
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1

clippy_linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Cache dependencies
uses: actions/cache/restore@v6
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/
Expand All @@ -57,26 +57,26 @@ jobs:
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Run formatting check
run: ./scripts/fmt.sh

check_wasm_exports:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Check WASM contract exports
run: ./scripts/check-wasm-exports.sh

check_ledger_objects_generated:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Use Node.js v24
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24.x

Expand All @@ -91,7 +91,7 @@ jobs:
runs-on: ubuntu-latest
if: false
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Run host function audit
run: ./scripts/host-function-audit.sh
Expand All @@ -103,28 +103,28 @@ jobs:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v7
- uses: actions-rust-lang/setup-rust-toolchain@v2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2
- name: Install cargo-deny
uses: taiki-e/install-action@cargo-deny
uses: taiki-e/install-action@3ce020901a1c1292716d3268bf506c1ae55a84cd # cargo-deny
- name: Check RustSec advisories
run: ./scripts/cargo-deny.sh

validate_ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Validate UI
run: ./scripts/validate-ui.sh

build_and_test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Cache dependencies
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/
Expand All @@ -134,13 +134,17 @@ jobs:
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

# Compiles Binaryen on a cache miss; lands in the cached ~/.cargo/bin.
- name: Ensure wasm-opt
run: ./scripts/wasm-opt.sh --ensure-tool

- name: Run build and test
run: ./scripts/build-and-test.sh

run-markdown:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Run code in Markdown files
run: ./scripts/run-markdown.sh
Expand All @@ -149,15 +153,15 @@ jobs:
runs-on: ubuntu-latest
needs: build_and_test
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Use Node.js v24
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: 24.x

- name: Cache cargo dependencies
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/
Expand All @@ -169,7 +173,7 @@ jobs:

- name: Cache Node dependencies
id: cache-nodemodules
uses: actions/cache@v6
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
env:
cache-name: cache-node-modules
with:
Expand All @@ -184,6 +188,9 @@ jobs:
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
run: npm ci

- name: Ensure wasm-opt
run: ./scripts/wasm-opt.sh --ensure-tool

- name: Start rippled in Docker
run: ./scripts/docker-rippled.sh start

Expand All @@ -204,9 +211,9 @@ jobs:
env:
CARGO_TERM_COLOR: always
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Cache dependencies
uses: actions/cache/restore@v6
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: |
~/.cargo/bin/
Expand All @@ -218,6 +225,6 @@ jobs:
- name: Install Rust
run: rustup update stable
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
uses: taiki-e/install-action@72f835688cb6c15903b796f704b01e652a2b8835 # cargo-llvm-cov
- name: Generate code coverage
run: ./scripts/coverage.sh
22 changes: 21 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ DEVNET=true ./scripts/run-tests.sh # run against wss://w
./scripts/generate-ledger-objects.sh
```

Other scripts not part of the primary workflow above: `scripts/benchmark-gas.sh`, `scripts/check-wasm-exports.sh`, `scripts/docs.sh` (builds and deploys the GitHub Pages docs/UI site), `scripts/host-function-audit.sh` (see below), `scripts/run-markdown.sh`, `scripts/validate-ui.sh`, `scripts/cargo-deny.sh` (RustSec advisories on the library workspace; advisory-only in CI until the first clean run).
Other scripts not part of the primary workflow above: `scripts/benchmark-gas.sh`, `scripts/check-wasm-exports.sh`, `scripts/docs.sh` (builds and deploys the GitHub Pages docs/UI site), `scripts/host-function-audit.sh` (see below), `scripts/run-markdown.sh`, `scripts/validate-ui.sh`, `scripts/wasm-opt.sh` (called by `build.sh`; see below), `scripts/cargo-deny.sh` (RustSec advisories on the library workspace; advisory-only in CI until the first clean run).

Pre-commit hooks (`.pre-commit-config.yaml`) run `cargo fmt --all` and `cargo clippy --all-targets --all-features -- -Dclippy::all` on staged Rust files, plus prettier with `--no-semi --tab-width 2` for JS/MD/YAML.

Expand Down Expand Up @@ -151,6 +151,26 @@ panic = "abort" # no_std can't unwind; also avoids pulling in a panic handle

The library defines a custom `#[panic_handler]` for `target_arch = "wasm32"` (in `xrpl-common-stdlib/src/lib.rs`) that calls `core::arch::wasm32::unreachable()`. Dev profile uses `panic = "unwind"` so unit tests can run on the host.

`scripts/build.sh` finishes by running `scripts/wasm-opt.sh`, which rewrites the **release** artifacts of both WASM workspaces in place with Binaryen's `wasm-opt` (~30% smaller, which matters because rippled charges gas per instruction and caps contract size). In place, rather than a separate `.opt.wasm`, so `tests/runSingleTest.js`, `ui/embed-wasm.sh`, and the gas benchmark all exercise the bytes that would ship on-ledger. Debug artifacts are left alone.

There is deliberately **no result caching**. Writing in place breaks the hardlink cargo makes from `deps/`, so cargo re-uplifts the unoptimized artifact on every build β€” meaning each `build.sh` run is already exactly one `wasm-opt` pass over pristine output, and a skip-cache keyed on the file would never legitimately hit. An earlier stamp-file version of this was removed: it cost ~0.7s of savings that never materialized, and it silently skipped re-optimization when `WASM_OPT_FLAGS` or the wasm-opt version changed.

The flag set is `-Oz --mvp-features --enable-mutable-globals --strip-debug --strip-producers`. Starting from `--mvp-features` stops wasm-opt from emitting post-MVP instructions (bulk memory, SIMD, reference types) that rippled's engine would reject.

**Sign-extension is deliberately not enabled, even though `wasm32v1-none` allows it.** rippled rejects `i32.extend8_s`/`i64.extend16_s` and friends with `temINVALID_BYTECODE`, so the accepted feature set is narrower than the target's. This is not theoretical: with `--enable-sign-ext` the e2e suite failed on exactly the four contracts where `-Oz` chose to introduce such an instruction (`gas_benchmark`, `trace_escrow_account`, `trace_escrow_finish`, `trace_escrow_ledger_object`) and passed on the twelve where it didn't. Note that rustc itself emits no sign-extension instructions for any current contract β€” wasm-opt was the only source β€” so this is latent for hand-written code too: if a future rustc or a new contract starts emitting them, it will fail on-ledger with nothing to do with wasm-opt. Dropping the flag costs nothing; it made output 135 bytes smaller overall, since the `target_features` section shrinks too.

Don't widen the flag set without checking what the host actually accepts β€” the target's feature list is not the authority here.

The **tool** is mandatory β€” `scripts/build.sh` calls `wasm-opt.sh` outside its release/debug branch, so even a debug build fails if `wasm-opt` can't be obtained. It resolves one from `$WASM_OPT`, then `PATH`, then `$CARGO_HOME/bin`; finding none it runs `cargo install wasm-opt --version <pin> --locked` and fails the build if that fails. Having no release WASM to optimize is a different matter and exits 0 quietly, so a debug-only build isn't broken by the step β€” but a dir or file named explicitly on the command line that's missing or holds no `.wasm` is treated as a typo and fails. The pinned crate version lives in that script and is the single source of truth: `scripts/setup.sh` and both CI workflows call `./scripts/wasm-opt.sh --ensure-tool` to resolve/install up front rather than stalling a build mid-way. CI's `~/.cargo/bin` cache carries the compiled binary between runs.

The `wasm-opt` crate isn't a reimplementation β€” its binary links Binaryen's real `wasm_opt_main`, so it's the upstream CLI with upstream flags, just built from source (needs a C++17 toolchain, a few minutes on first install). It tracks Binaryen **116** while upstream is at 133; output measured within 0.4% of 132 on every example, so the lag costs nothing that matters. It's pinned because a different Binaryen means different bytes, which means different numbers out of `scripts/benchmark-gas.sh`. A native Binaryen already on `PATH` wins over the pin β€” respecting a developer's install beats a multi-minute compile.

A **cargo-managed** `wasm-opt` (the one at `$CARGO_HOME/bin`), by contrast, is version-checked against the pin and reinstalled if it doesn't match, then re-checked so a bad install fails loudly. This matters because CI caches `~/.cargo/bin` under a key derived from `Cargo.lock`, which bumping `WASM_OPT_CRATE_VERSION` in the script does not change β€” without the check, a pin bump would silently keep using the stale cached binary and the pin would be a lie. The expected Binaryen version is derived from the crate version, which is published as `0.<binaryen>.<patch>`; if that convention ever breaks, fix `PINNED_BINARYEN_VERSION` in the script. An explicit `$WASM_OPT` and a system Binaryen are both exempt from this check.

Note that `cargo install` is invoked with `RUSTFLAGS=""`: the repo-wide `-Dwarnings` would otherwise fail the build on any warning in a third-party crate.

Other knobs: `SKIP_WASM_OPT=true` (the one deliberate opt-out), `WASM_OPT_NO_INSTALL=true` (fail rather than auto-install), `WASM_OPT=<path>`, `WASM_OPT_FLAGS="..."`. The script also takes explicit dirs/files as arguments.

## Writing a contract

Minimal template (see `examples/smart-escrows/hello_world/src/lib.rs`):
Expand Down
5 changes: 4 additions & 1 deletion scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ You can also run individual test suites:
### Main Scripts

- **`build-and-test.sh`** - Build and test the native workspace, build WASM examples, and run craft
- **`build.sh`** - Build all examples (accepts `release` argument for release builds)
- **`build.sh`** - Build all examples (accepts `release` argument for release builds), then shrink the release WASM via `wasm-opt.sh`

### Helper Scripts

Expand All @@ -40,6 +40,7 @@ You can also run individual test suites:
- **`docker-rippled.sh`** - Start/stop/check a local rippled node in Docker, pinned to the same image CI uses; also used by CI itself, so this is the only place the container's `docker run`/health-check/logs logic lives (`start`/`stop`/`status`/`logs`)
- **`host-function-audit.sh`** - Audit host functions against XRPLd (requires Node.js)
- **`cargo-deny.sh`** - Check RustSec advisories on the library workspace (requires `cargo-deny`)
- **`wasm-opt.sh`** - Shrink the built release contracts in place with Binaryen's `wasm-opt` (run automatically at the end of `build.sh`; auto-installs a pinned `wasm-opt` via cargo if none is found, and fails the build if it can't; exits quietly when there's no release WASM to optimize). `--ensure-tool` resolves/installs the tool without optimizing anything.
- **`benchmark-gas.sh`** - Measure and compare gas costs of optimized helper functions
- **`generate-sfields.sh`** - Generate type-safe SField constants from rippled source (requires Node.js)

Expand Down Expand Up @@ -177,6 +178,7 @@ Actions workflows, ensuring perfect consistency between local and CI environment
- **Pre-commit not found**: Run `./scripts/setup.sh` to install dependencies
- **Node.js required**: Install Node.js for the host function audit, or skip that script
- **WASM target missing**: The scripts automatically install the `wasm32v1-none` target
- **Build pauses on "installing wasm-opt via cargo"**: expected on a machine with no `wasm-opt`; it compiles Binaryen from source once (a few minutes, needs a C++17 toolchain). Install a native Binaryen instead (`brew install binaryen` / `apt-get install binaryen`) to skip the compile, or set `SKIP_WASM_OPT=true` to build without the optimization step
- **Docker not found / daemon not reachable**: Install/start Docker, or set `NO_DOCKER=true` (use your own local rippled) or `DEVNET=true` (use WASM Devnet)

## Script Dependencies
Expand All @@ -185,6 +187,7 @@ Actions workflows, ensuring perfect consistency between local and CI environment
setup.sh (run first)
β”œβ”€β”€ build-and-test.sh
β”œβ”€β”€ build.sh
└── wasm-opt.sh (required; auto-installs a pinned wasm-opt via cargo)
β”œβ”€β”€ check-wasm-exports.sh
β”œβ”€β”€ clippy.sh
β”œβ”€β”€ run-tests.sh
Expand Down
3 changes: 3 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ else
fi
cd ..

echo "πŸ—œοΈ Optimizing release WASM..."
./scripts/wasm-opt.sh

echo "βœ… Build completed successfully!"
4 changes: 4 additions & 0 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ rustup default stable
echo "πŸ“¦ Adding wasm32v1-none target..."
rustup target add wasm32v1-none

# Up front, since installing the crate compiles Binaryen from source.
echo "πŸ“¦ Ensuring wasm-opt (Binaryen) is available..."
./scripts/wasm-opt.sh --ensure-tool

# Note: Pre-commit checks are handled by GitHub Actions using pre-commit/action@v3.0.1
# Local pre-commit installation is optional for development convenience
echo "ℹ️ Pre-commit checks are handled by GitHub Actions"
Expand Down
Loading
Loading