Guidance for AI agents working with Rust code in the Optimism monorepo. See dev-workflow.md for tool versions, PR workflow, and other cross-language guidance.
All Rust code lives under rust/. This is a unified Cargo workspace — always run Rust commands from this directory. The workspace contains three main component groups:
- Kona — Proof system and rollup node (
rust/kona/) - Op-Reth — OP Stack execution client built on reth (
rust/op-reth/) - Op-Alloy / Alloy extensions — OP Stack types and providers
Check rust/Cargo.toml for the full workspace member list, dependency versions, and lint configuration. The Rust toolchain version is pinned in rust/rust-toolchain.toml.
Workspace tool config lives at rust/.config/ — nextest.toml (test settings, JUnit output) and zepter.yaml (feature-propagation lint). nextest, zepter, and cargo-release discover config only from the workspace root, so per-component rust/<crate>/.config/*.toml files are not read and have no effect — put new test/lint config at rust/.config/. (rust/op-rbuilder and rust/rollup-boost are separate vendored Cargo workspaces with their own root configs.)
Most of the OP Stack Rust code here was officially migrated into the monorepo in coordination with the upstream repository owners — it is not a vendored copy. The upstream crates have been deleted or deprecated, so the entire Rust OP Stack is now developed here. This applies to:
- op-reth (
rust/op-reth/) - kona-* (
rust/kona/) - op-alloy-* (
rust/op-alloy/) - alloy-op-* (
rust/alloy-op-evm/,rust/alloy-op-hardforks/) - op-revm (
rust/op-revm/)
These crates are owned and edited directly here — do not look upstream for their source. They still depend on generic upstream crates (e.g. reth's engine/provider crates, alloy, revm), which remain external and pinned in rust/Cargo.toml; a change to one of those generic APIs has to go upstream first and then be consumed via a version bump. When changing upstream behavior or API leads to a better overall solution than working around it locally, it is acceptable — and often preferable — to propose that change upstream (a PR to the respective repository); suggest this when it applies.
Known exception: op-alloy-flz has not been migrated yet and is still an external dependency, tracked by #21087.
Still vendored: rust/op-rbuilder/ and rust/rollup-boost/ are vendored copies, slated for deprecation.
Run just --list in rust/ to see all available targets. The key ones:
cd rust
# Build the workspace
just build
# Build the workspace excluding example crates with the fast-build profile
just build-no-examples
# Build in release mode
just build-release
# Build specific binaries
just build-node # kona-node
just build-op-reth # op-rethThe reth-optimism-chainspec crate's build.rs materializes its chain-config archive (res/superchain-configs.tar, gitignored) from the superchain-registry submodule at the repo root. Any op-reth build that enables the superchain-configs feature (the op-reth binary, clippy --all-features, the chainspec tests) needs that submodule checked out, or the build fails. Initialize it with just update-superchain-registry-submodule (or just sync-superchain, which also does it). Once res/ holds the archive, later builds reuse it without touching the submodule (default mode mirrors kona's KONA_SYNC_SUPERCHAIN); set OP_RETH_SYNC_SUPERCHAIN=1 to force a regeneration.
Tests use cargo-nextest (not cargo test) for unit tests:
cd rust
# Run all tests (unit + doc tests)
just test
# Unit tests only (excludes online tests)
just test-unit
# Doc tests only
just test-docsThe op-reth E2E tests (rust/op-reth/tests/proofs/) run a full devnet with op-geth (sequencer) and op-reth (validator). They require two build prerequisites:
-
Forge artifacts — the devnet deploys contracts from compiled artifacts:
cd packages/contracts-bedrock mise exec -- just build-no-tests
-
op-reth release binary — the test harness (
op-devstack/sysgo/rust_binary.go) only searchestarget/release/, nottarget/debug/. Options:# Option A: let the test build it (slow first run, cached after) RUST_JIT_BUILD=1 go test -v -run TestName ./rust/op-reth/tests/proofs/core/ # Option B: pre-build the binary cd rust && just build-op-reth
Run from the monorepo root:
mise exec -- go test -v -run TestExecutePayloadSuccess -count=1 ./rust/op-reth/tests/proofs/core/Kona prestates are built via Docker:
cd rust
just build-kona-prestatescd rust
# Run all lints (format check + clippy + doc lints)
just lint
# Individual lint steps
just fmt-check # formatting (requires nightly)
just lint-clippy # clippy with all features, -D warnings
just lint-docs # rustdoc warningsLint configuration lives in rust/Cargo.toml (workspace lints section), rust/clippy.toml, and rust/rustfmt.toml.
Formatting uses a pinned nightly toolchain (defined as NIGHTLY in rust/justfile). It is installed via mise.
Then use just fmt-fix to auto-format, or just fmt-check to verify.
Many kona and alloy crates must compile without the standard library (for the fault proof VM). If you modify these crates, verify no_std builds:
cd rust
just check-no-stdThis builds affected crates for the riscv32imac-unknown-none-elf target.
The workspace uses cargo-deny for license, advisory, and dependency checks. Configuration is in rust/deny.toml.
cd rust
just denyRun these checks from rust/. Fix all issues — CI enforces zero warnings.
-
Format — only after the final edit, never between edits:
just fmt-fix
The nightly formatter has opinions (e.g., collapsing multi-line
letbindings onto one line) that the Edit tool doesn't replicate — running it mid-session and then editing again leaves unformatted code behind and fails therust-fmtCI check. After formatting, rungit diff --statto confirm the working tree matches what you're about to commit. -
Lint — this checks formatting, clippy, and doc lints:
just lint
-
Test — run tests for changed packages:
just test-unit
-
no_std — if you changed any proof, protocol, or alloy crate:
just check-no-std
Op-reth requires clang / libclang-dev for reth-mdbx-sys bindgen. CI installs this automatically — if you see bindgen errors locally, install clang.
The OP fork → implied L1 (Ethereum) fork mapping is defined once, in OpHardfork::activates_l1_fork in rust/alloy-op-hardforks/src/lib.rs. When a new OP hardfork rides an L1 fork (e.g. Isthmus → Prague, Karst → Osaka), add the single match arm there; the cumulative (implied_l1_fork) and inverse (activating_op_fork) views and all downstream consumers (op-revm, op-reth chainspec, kona) derive from it.
The full guide lives at rust/UPDATING-RETH.md. Read it before bumping the reth pin in rust/Cargo.toml — or run the /update-reth skill (.claude/skills/update-reth/), which wraps the guide in an end-to-end agent workflow.
When reviewing a bump (rather than performing one), use reth-update-review.md and the reth-update-reviewer agent — they surface upstream reth/revm/alloy changes that should have forced a change in our in-tree op- forks but produced no diff in our tree.
Agent-specific tips beyond what's in the guide:
- The bump is iterative — run
cargo check --workspace --tests, fix the first batch of errors, re-run, repeat. Don't try to enumerate every API change up front and don't ask the user to confirm every line of adaptation; just iterate to a green compile and report the diff at the end. - If you have a local checkout of
paradigmxyz/reth, use it to look up upstream trait signatures and rungit log <old-rev>..<new-rev>to find the commit that changed any given symbol — much faster and more reliable than hand-fetching raw GitHub URLs. If you don't know whether one is available, ask the user. Don't assume a path. - For trait methods that gained an ignored parameter, prefix the new param with
_(e.g._block_access_list_hash: Option<B256>) so it doesn't generate an unused-variable warning. Don't invent a meaningful name unless you're actually plumbing the value through. - If upstream removed a trait or re-export that op-reth still uses, vendor it locally with a comment pointing at the upstream removal PR — don't try to refactor op-reth to do without it without first confirming the consumer is actually unused. The "stale" label upstream doesn't mean unused downstream.
- When the new rev pulls in new transitive deps (visible as
Adding <crate>lines fromcargo update), check whether they're from upstream reth's own deps or from a misconfiguration on our side.cargo tree -i <crate>traces the path.
- Fix Rust Formatting (
.claude/skills/fix-rust-fmt/SKILL.md): Fixesrust-fmtCI failures by installing the pinned nightly toolchain and runningjust fmt-fix. Invoke with/fix-rust-fmt.