Skip to content

fix(security): august 2026 audit fixes across runtime, codec, node and system contracts - #491

Open
dmitry123 wants to merge 32 commits into
develfrom
fix/audit-fixes-2026-aug
Open

fix(security): august 2026 audit fixes across runtime, codec, node and system contracts#491
dmitry123 wants to merge 32 commits into
develfrom
fix/audit-fixes-2026-aug

Conversation

@dmitry123

@dmitry123 dmitry123 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

Fixes from the August 2026 security audit: seven independent issues across the runtime, codec, node genesis path, and system contracts, plus regression coverage for an eighth that is reproduced but not fixed here.

42 files, +4343/-292, 11 self-contained commits. Sections below are ordered by blast radius.

Release artifact authentication

New fluentbase-release-verify crate. Genesis allocations, system contract code, and runtime-upgrade payloads are all derived from GitHub release assets, so they are only as trustworthy as the signature check applied to them. The crate treats every artifact as untrusted until authenticated against a pinned release key, and is deliberately fail-closed:

  • artifacts are held in memory until authentication succeeds — nothing is decompressed, parsed, or cached before that;
  • the signature is checked over the exact bytes later used, leaving no verify-to-use window;
  • cached files get the same treatment as freshly downloaded ones and are discarded rather than trusted on failure;
  • digest pins and the signed manifest are checked in addition to the signature, never instead of it;
  • there is no bypass switch.

bins/runtime-upgrade authenticates release artifacts before signing, and reports provenance.

Fail-closed genesis verification (node)

Built-in networks now resolve genesis through download_and_cache_genesis_verified against pinned SHA-256 digests plus a detached OpenPGP signature. Mainnet and the genesis channel became explicit, validated selections. The network table is kept in one place so tests can assert the fail-closed guarantees hold for every entry.

Nested-frame memory exhaustion (runtime)

A suspended frame keeps its whole store — linear memory included — alive in recoverable_runtimes until resumed or forgotten, so a call chain holds depth × frame_size bytes resident simultaneously. Per-frame fuel prices a single allocation but cannot bound the sum across frames:

Largest frame 1023 pages = 63.94 MiB
Its fuel charge 1,047,552 fuel = 52,378 gas
At CALL_STACK_LIMIT frames ~64 GiB, for about half of a 100M gas block

MAX_IN_FLIGHT_MEMORY_BYTES (1.5 GiB) now bounds the memory held by all live frames of a transaction; a frame pushing the total past it is rejected with ExitCode::OutOfMemory. Measured end to end: a chain demanding 63.94 GiB is admitted 24 frames and holds 1.50 GiB.

Two implementation notes:

  • The total is summed from recoverable_runtimes on demand rather than tracked incrementally, so it cannot drift from the frames actually alive.
  • The check runs after a frame is built. A module's declared page count is not a field on RwasmModule — it is encoded in entrypoint bytecode — so checking beforehand would mean decoding that prologue and coupling the runtime to rWasm codegen. Measuring the constructed frame avoids that; overshoot is bounded by one frame.

System runtimes are excluded: their store is cached and reused across calls rather than allocated per frame, so counting it per frame would multiply-count one allocation.

No gas costs change. Behaviour changes only for transactions exceeding the cap. The limit sits above the worst case ordinary contracts can reach (full depth at the toolchain-default 17 initial pages, ~1.06 GiB), so nothing that succeeds today begins to fail — but it is consensus-visible and needs a coordinated upgrade.

Decoder allocation guards (codec)

Collection decoding validated element counts against the remaining buffer before allocating. Header offsets and element offsets are now computed with checked arithmetic, bodies are validated via validate_collection_body before reserving, and capacity is obtained with try_reserve so a malformed length cannot abort on OOM.

System contract ownership

runtime-upgrade — delegated upgrade authority could outlive the owner that granted it. Any ownership transition (change_owner, renounce_ownership) now atomically cancels a pending plan — updater, genesis metadata, and every remaining target/hash pair — emitting UpgradePlanCancelled. A new owner must re-authorize. No-op when nothing is planned, so transitions stay cheap and emit no misleading event.

fee-manager — zero is not a neutral owner value: owner() and only_owner() map an empty slot back to the genesis bootstrap key, so a zero transfer would silently reactivate a retired authority after governance had handed control over. Zero-address transfers are now rejected; renounceOwnership remains the explicit fork-only transition.

EIP-7951 gas accounting

The precompile wrapper moved to p256_verify_osaka / P256VERIFY_BASE_GAS_FEE_OSAKA, correcting both the charged gas and the documented return shape (32 bytes ending in 1, not a single byte).

Reproduced, not fixed: aggregate output growth

e2e/src/ddos.rs characterizes a separate, still-open issue. Fuel is charged per _write but does not bound the aggregate host output buffer: a contract reusing 1 MiB of guest memory appends 64 MiB of output for under 7M gas. Scaled to a 100M gas block that permits over 900 MiB of output.

This is orthogonal to the frame-memory cap above — that bounds linear memory across frames, this is a single frame growing the host-side output Vec. The test asserts the current behaviour so the vector is pinned and any future fix has a baseline; it is not a fix.

Testing

  • fluentbase-release-verify — signature, manifest, asset, key, and load coverage with signed testdata fixtures.
  • crates/runtime — in-flight accounting, admission and rejection at the cap, and a full 1024-iteration call chain proving depth alone never trips the limit while memory-heavy chains are cut off early.
  • contracts/runtime-upgrade, e2e/fee_manager.rs — ownership transitions cancel pending plans; zero-address transfer rejected.
  • e2e/eip7951.rs, e2e/builtins.rs — Osaka gas figures; builtin expectations updated for the initial-memory charge introduced by the rWasm v0.4.6 upgrade.

recursion_demanding_64_gib_is_capped_at_the_in_flight_limit is #[ignore]d — it allocates ~1.5 GiB resident by design, since proving the cap holds means allocating up to it. Run explicitly:

cargo test -p fluentbase-runtime --lib recursion_demanding -- --ignored --nocapture

Summary by CodeRabbit

  • New Features

    • Added authenticated release verification for runtime upgrades and genesis artifacts, including signatures, checksums, manifests, caching, and provenance reporting.
    • Added explicit mainnet and genesis-channel selection with validation.
    • Added Osaka EIP-7951 signature verification support.
    • Added initial-memory and repeated-write gas accounting, plus transaction memory limits.
    • Upgrade plans are automatically cancelled during ownership changes or renunciation.
  • Bug Fixes

    • Improved decoding safety for malformed, oversized, or truncated data.
    • Zero-address ownership transfers are now rejected.
    • Token metadata now validates UTF-8 and 32-byte length limits without partial storage updates.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes add authenticated release verification and provenance tracking, harden codec decoding, add runtime memory and fuel controls, update upgrade-plan ownership behavior, adopt Osaka EIP-7951 accounting, validate contract metadata and ownership inputs, and expand integration coverage.

Changes

Release verification and genesis integration

Layer / File(s) Summary
Verification crate
Cargo.toml, crates/release-verify/*
Adds release assets, pinned OpenPGP verification, digest checks, manifest binding, bounded loading, cache handling, and authenticated genesis parsing.
Genesis loading
crates/node/src/chainspec.rs, crates/node/src/utils.rs
Pins network genesis assets and routes retrieval through authenticated verification.
Runtime-upgrade provenance
bins/runtime-upgrade/*
Adds network validation, verified release loading, provenance reporting, Safe bundle metadata, and upgrade manifest fields.

Runtime and decoding safety

Layer / File(s) Summary
Defensive decoding
crates/codec/src/encoder.rs, crates/codec/src/bytes_codec.rs, crates/codec/src/vec.rs, crates/codec/src/hash.rs
Adds checked arithmetic, bounded slices, collection-size validation, fallible allocation, and malformed-input tests.
Memory and output metering
crates/types/src/lib.rs, crates/runtime/*, e2e/src/ddos.rs, e2e/src/builtins.rs
Charges fuel for initial memory, limits aggregate frame memory, tests repeated output writes, and updates builtin gas expectations.

Contract behavior

Layer / File(s) Summary
Runtime-upgrade plan cancellation
contracts/runtime-upgrade/*
Cancels pending plans during ownership changes and renunciation, clears delegated state, emits cancellation data, and tests success and failure paths.
EIP-7951 integration
contracts/eip7951/src/lib.rs, e2e/src/eip7951.rs, e2e/src/lib.rs
Uses Osaka secp256r1 APIs and gas accounting, with end-to-end verification.
Ownership and token metadata validation
contracts/fee-manager/*, contracts/universal-token/*, crates/sdk/*
Rejects zero-address ownership transfers, malformed metadata, and short strings that exceed encoding limits.

Repository support

Layer / File(s) Summary
Generated output ignore configuration
.gitignore
Ignores the graphify-out/ directory.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: hedwig0x

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the security audit fixes across the main affected components and matches the pull request objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-fixes-2026-aug

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Criterion results (vs baseline)


running 131 tests


Heads-up: runner perf is noisy; treat deltas as a smoke check.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (8)
crates/codec/src/hash.rs (1)

588-622: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The two tests are correct, but they do not cover the Solidity collection paths.

Both fixtures resolve to an empty body, so validate_collection_body fails before try_reserve. The assertions are accurate for 64-bit and 32-bit targets.

The Solidity HashMap and HashSet decoders received the same hardening at lines 264-292 and 514-541, and they use checked_decode_slice_from instead of checked_decode_slice. That path has no regression test here. Add one Solidity fixture for each type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codec/src/hash.rs` around lines 588 - 622, Add regression tests
covering the Solidity collection decoders for both HashMap and HashSet,
alongside the existing compact tests. Build fixtures with an oversized claimed
count and headers that exercise the checked_decode_slice_from path in the
Solidity implementations, then assert decoding fails with BufferTooSmall or
Overflow before allocation. Use the existing Solidity decoder test patterns and
retain separate tests for each collection type.
crates/codec/src/vec.rs (1)

266-296: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Both tests assert the fix, and the fixtures are consistent with the decoders.

The compact fixture makes read_bytes return an empty body, so validate_collection_body(0xffff_ffff, 4, 0) fails before try_reserve. The Solidity fixture places a body offset of 32 and a count of u32::MAX, so checked_decode_slice_from returns an empty slice and validation fails. The BufferTooSmall | Overflow pattern also covers 32-bit targets, where checked_mul overflows instead.

Consider one addition: assert that a count that is one element larger than the body is also rejected. The current fixtures use an empty body, so they do not cover an off-by-one boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codec/src/vec.rs` around lines 266 - 296, Extend both compact and
Solidity vector decoder tests to cover the off-by-one boundary where the
declared element count is exactly one greater than the available body capacity.
Keep the existing empty-body and error assertions, and add fixtures/assertions
that confirm this count is rejected before allocation.
crates/codec/src/encoder.rs (1)

262-299: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Require contiguous input buffers for decoded slices.

checked_decode_slice and checked_decode_slice_from compare chunk.len() instead of buf.remaining(), while nearby decode helpers use buf.remaining() only to validate bounds before reading chunk[...]. Document these as contiguous-buffer-only helpers, or unify the checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/codec/src/encoder.rs` around lines 262 - 299, Clarify in the
documentation for checked_decode_slice and checked_decode_slice_from that they
require the requested range to be available in the current contiguous
buf.chunk(), or update both helpers to validate bounds against buf.remaining()
consistently with nearby decode helpers before slicing chunk. Preserve the
existing overflow and BufferTooSmall error behavior.
crates/release-verify/src/load.rs (1)

205-224: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Use a unique temporary file name in write_atomic.

The temp path is always <path>.tmp. If two processes share the cache directory, both write the same temp file and both rename it, so the cache can receive interleaved content. A later run re-authenticates the cache and rejects it, so the effect is a wasted re-download rather than acceptance of bad bytes. Add the process id, or use tempfile::NamedTempFile::persist in the same directory.

♻️ Proposed fix for concurrent cache writes
 pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<()> {
     let mut tmp_name = OsString::from(path.as_os_str());
-    tmp_name.push(".tmp");
+    tmp_name.push(format!(".{}.tmp", std::process::id()));
     let tmp = PathBuf::from(tmp_name);

A failed write then leaves a process-specific temp file; remove it on the error paths if that matters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/release-verify/src/load.rs` around lines 205 - 224, Update
write_atomic to create a process-specific temporary path in the same directory,
incorporating the current process id into the existing “.tmp” name or using
tempfile::NamedTempFile with persist. Ensure all create, write, sync, and rename
operations use this unique path, preserving the existing atomic replacement and
error reporting behavior.
crates/release-verify/src/tests.rs (1)

580-610: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Verify the published manifest signature cryptographically.

This test checks signature type, digest algorithm, and issuer metadata only. signature_with_a_spoofed_issuer_is_rejected (lines 120-150) states that issuer subpackets are attacker-controlled, so these assertions do not prove the pinned key signed anything. testdata/genesis-manifest-v1.3.2.txt and testdata/genesis-manifest-v1.3.2.txt.asc are both committed, so one real check is available and it binds the embedded key to a production artifact.

💚 Proposed additional assertion
 fn published_manifest_parses_and_binds_its_assets() {
+    // The signed bytes are committed next to the signature, so verify the real thing.
+    verify_detached_signature(
+        include_bytes!("../testdata/genesis-manifest-v1.3.2.txt"),
+        include_bytes!("../testdata/genesis-manifest-v1.3.2.txt.asc"),
+        &ReleaseKey::fluent().expect("embedded key must load"),
+    )
+    .expect("the published manifest signature must verify against the pinned key");
+
     let manifest =
         ReleaseManifest::parse(include_bytes!("../testdata/genesis-manifest-v1.3.2.txt"))
             .expect("published manifest must parse");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/release-verify/src/tests.rs` around lines 580 - 610, Update
published_signatures_are_issued_by_the_pinned_release_key to cryptographically
verify at least one committed manifest/signature pair, such as
testdata/genesis-manifest-v1.3.2.txt with its .asc signature, using the embedded
ReleaseKey and the repository’s existing verification API. Retain the current
metadata checks, but ensure the test asserts successful signature validation
against the actual manifest bytes rather than trusting issuer subpackets.
crates/release-verify/src/manifest.rs (1)

43-50: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Reject repeated version= and commit= lines, as the code already rejects conflicting digests.

A repeated version= line silently overwrites the earlier value, so the last line wins. check uses self.version to bind the artifact to a release, so this field carries the same "pick the convenient one" risk that motivates the digest-conflict guard on Line 68. The bytes are authenticated before parse runs, so this is a hardening gap and not an exploitable path.

♻️ Proposed fix
             if let Some(value) = line.strip_prefix("version=") {
+                if version.is_some() {
+                    return Err(VerifyError::Manifest("multiple version= lines".to_owned()));
+                }
                 version = Some(value.to_owned());
                 continue;
             }
             if let Some(value) = line.strip_prefix("commit=") {
+                if commit.is_some() {
+                    return Err(VerifyError::Manifest("multiple commit= lines".to_owned()));
+                }
                 commit = Some(value.to_owned());
                 continue;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/release-verify/src/manifest.rs` around lines 43 - 50, Update the
manifest parsing logic around the version and commit assignments to reject
duplicate version= and commit= entries instead of overwriting earlier values.
Preserve the existing first-value behavior for unique entries and use the same
conflict/error handling pattern already applied to repeated digest fields.
bins/runtime-upgrade/provenance.rs (1)

171-197: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Two copies of the bounded fetcher will drift. fluentbase-release-verify defines the Fetcher contract but supplies no bounded HTTP implementation, so both consumers reimplement the same logic: the content_length pre-check, take(max_bytes + 1), and the post-read length check. Only the user-agent string and the error crate differ. A change to one copy, such as the HTTP 404 signal needed for the manifest-binding fix, will be missed in the other.

  • bins/runtime-upgrade/provenance.rs#L171-L197: move this implementation behind an optional reqwest feature of fluentbase-release-verify and call the shared fetcher with a configurable user-agent.
  • crates/node/src/utils.rs#L54-L81: delete this copy and call the shared fetcher. Keep eyre conversion at the call site only.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bins/runtime-upgrade/provenance.rs` around lines 171 - 197, Move the bounded
HTTP logic from fetch_inner in bins/runtime-upgrade/provenance.rs#L171-L197 into
fluentbase-release-verify behind an optional reqwest feature, exposing a shared
fetcher that accepts a configurable user-agent; update fetch_inner to call it.
Remove the duplicate implementation in crates/node/src/utils.rs#L54-L81 and call
the shared fetcher there, retaining eyre conversion only at that call site.
crates/node/src/chainspec.rs (1)

22-41: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add release CI for built-in genesis assets.

The tagged devnet/testnet/mainnet genesis .json.gz SHA-256 pins match the published assets, but CI should also fetch the .asc for each asset and assert both signature and digest. This keeps tag swaps from creating a silent startup mismatch for operators.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/node/src/chainspec.rs` around lines 22 - 41, Add CI validation for the
assets returned by devnet_genesis, testnet_genesis, and mainnet_genesis: for
each configured release tag/channel, fetch the corresponding .json.gz and .asc
files, verify the artifact signature, and assert its SHA-256 matches the pinned
digest. Ensure the checks run for all three built-in genesis assets and fail CI
on any fetch, signature, or digest mismatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bins/runtime-upgrade/main.rs`:
- Around line 92-109: Require an explicit genesis channel whenever the --rpc
argument is provided, so pick_rpc cannot use the default devnet artifact for a
custom endpoint. Update the rpc argument definition in CommonArgs with the
appropriate dependency on genesis_channel, while preserving the existing
--mainnet behavior and compatibility with tests that provide --genesis-channel.

In `@bins/runtime-upgrade/provenance.rs`:
- Around line 139-157: Extend FetchError with a not-found signal, set it in
fetch_inner for HTTP 404 and 410 responses, and update the load_verified error
handling in the provenance flow to downgrade only that case to
ManifestBinding::Unavailable. Propagate all other VerifyError::Fetch failures as
fatal errors, and add coverage confirming a non-404 manifest fetch failure
aborts the run.

In `@crates/codec/src/encoder.rs`:
- Around line 301-321: Update validate_collection_body to reject any non-zero
len when element_header_size is zero, returning the existing appropriate
decoding error before calculating required or attempting collection allocation;
preserve valid empty collections and all non-zero-header validation behavior.

In `@crates/codec/src/hash.rs`:
- Around line 585-586: In the tests module, remove the nested hashbrown import
of HashMap and HashSet while retaining the bytes import. Rely on the existing
use super::* to provide those types and eliminate the redundant-import warning.

In `@crates/release-verify/src/asset.rs`:
- Around line 33-57: Validate release asset identifiers before constructing
names or URLs: update ReleaseAsset::new, genesis, and manifest to reject tags,
channels, or resulting names containing unsafe characters or anything that is
not a single path component, using the AssetName error variant and propagating
Result through callers. Ensure externally supplied tags cannot introduce path
traversal or URL separators while preserving valid asset naming.

In `@crates/release-verify/src/key.rs`:
- Around line 44-61: Update ReleaseKey::new and signing_candidates to document
the release-key lifetime and revocation policy, and enforce it by rejecting
revoked or expired primary keys and signing-capable subkeys before they can be
used for verification. Ensure signing_candidates excludes any invalid components
while preserving valid candidates and the existing fingerprint and binding
checks.

In `@crates/release-verify/src/signature.rs`:
- Around line 99-108: Update signing_candidates to exclude revoked or expired
primary keys, user/packet contexts, and signing subkeys before creating
SigningCandidate values; do not rely solely on key_flags().sign(). Apply the
existing OpenPGP revocation and temporal-policy checks, including rejecting
future issuance timestamps and expired signature validity windows, while
preserving valid signing candidates.

---

Nitpick comments:
In `@bins/runtime-upgrade/provenance.rs`:
- Around line 171-197: Move the bounded HTTP logic from fetch_inner in
bins/runtime-upgrade/provenance.rs#L171-L197 into fluentbase-release-verify
behind an optional reqwest feature, exposing a shared fetcher that accepts a
configurable user-agent; update fetch_inner to call it. Remove the duplicate
implementation in crates/node/src/utils.rs#L54-L81 and call the shared fetcher
there, retaining eyre conversion only at that call site.

In `@crates/codec/src/encoder.rs`:
- Around line 262-299: Clarify in the documentation for checked_decode_slice and
checked_decode_slice_from that they require the requested range to be available
in the current contiguous buf.chunk(), or update both helpers to validate bounds
against buf.remaining() consistently with nearby decode helpers before slicing
chunk. Preserve the existing overflow and BufferTooSmall error behavior.

In `@crates/codec/src/hash.rs`:
- Around line 588-622: Add regression tests covering the Solidity collection
decoders for both HashMap and HashSet, alongside the existing compact tests.
Build fixtures with an oversized claimed count and headers that exercise the
checked_decode_slice_from path in the Solidity implementations, then assert
decoding fails with BufferTooSmall or Overflow before allocation. Use the
existing Solidity decoder test patterns and retain separate tests for each
collection type.

In `@crates/codec/src/vec.rs`:
- Around line 266-296: Extend both compact and Solidity vector decoder tests to
cover the off-by-one boundary where the declared element count is exactly one
greater than the available body capacity. Keep the existing empty-body and error
assertions, and add fixtures/assertions that confirm this count is rejected
before allocation.

In `@crates/node/src/chainspec.rs`:
- Around line 22-41: Add CI validation for the assets returned by
devnet_genesis, testnet_genesis, and mainnet_genesis: for each configured
release tag/channel, fetch the corresponding .json.gz and .asc files, verify the
artifact signature, and assert its SHA-256 matches the pinned digest. Ensure the
checks run for all three built-in genesis assets and fail CI on any fetch,
signature, or digest mismatch.

In `@crates/release-verify/src/load.rs`:
- Around line 205-224: Update write_atomic to create a process-specific
temporary path in the same directory, incorporating the current process id into
the existing “.tmp” name or using tempfile::NamedTempFile with persist. Ensure
all create, write, sync, and rename operations use this unique path, preserving
the existing atomic replacement and error reporting behavior.

In `@crates/release-verify/src/manifest.rs`:
- Around line 43-50: Update the manifest parsing logic around the version and
commit assignments to reject duplicate version= and commit= entries instead of
overwriting earlier values. Preserve the existing first-value behavior for
unique entries and use the same conflict/error handling pattern already applied
to repeated digest fields.

In `@crates/release-verify/src/tests.rs`:
- Around line 580-610: Update
published_signatures_are_issued_by_the_pinned_release_key to cryptographically
verify at least one committed manifest/signature pair, such as
testdata/genesis-manifest-v1.3.2.txt with its .asc signature, using the embedded
ReleaseKey and the repository’s existing verification API. Retain the current
metadata checks, but ensure the test asserts successful signature validation
against the actual manifest bytes rather than trusting issuer subpackets.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0239a76-2213-48af-9edb-56bff83558c9

📥 Commits

Reviewing files that changed from the base of the PR and between 9ceca31 and 69d73de.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • Cargo.toml
  • bins/runtime-upgrade/Cargo.toml
  • bins/runtime-upgrade/main.rs
  • bins/runtime-upgrade/provenance.rs
  • contracts/runtime-upgrade/src/tests.rs
  • crates/codec/src/bytes_codec.rs
  • crates/codec/src/encoder.rs
  • crates/codec/src/hash.rs
  • crates/codec/src/vec.rs
  • crates/node/Cargo.toml
  • crates/node/src/chainspec.rs
  • crates/node/src/utils.rs
  • crates/release-verify/Cargo.toml
  • crates/release-verify/src/asset.rs
  • crates/release-verify/src/error.rs
  • crates/release-verify/src/key.rs
  • crates/release-verify/src/lib.rs
  • crates/release-verify/src/load.rs
  • crates/release-verify/src/manifest.rs
  • crates/release-verify/src/signature.rs
  • crates/release-verify/src/test_support.rs
  • crates/release-verify/src/tests.rs
  • crates/release-verify/testdata/genesis-mainnet-v1.0.0.json.gz.asc
  • crates/release-verify/testdata/genesis-manifest-v1.3.2.txt
  • crates/release-verify/testdata/genesis-manifest-v1.3.2.txt.asc
  • crates/release-verify/testdata/genesis-v0.3.4-dev.json.gz.asc
  • crates/release-verify/testdata/genesis-v0.5.7.json.gz.asc

Comment thread bins/runtime-upgrade/main.rs Outdated
Comment thread bins/runtime-upgrade/provenance.rs
Comment thread crates/codec/src/encoder.rs
Comment thread crates/codec/src/hash.rs Outdated
Comment thread crates/release-verify/src/asset.rs Outdated
Comment thread crates/release-verify/src/key.rs
Comment thread crates/release-verify/src/signature.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
crates/runtime/src/runtime/contract_runtime.rs (2)

159-170: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

memory_pages can panic through expect on a consensus path.

u32::try_from(...).expect(...) aborts the node if the page count ever exceeds u32::MAX. The value is bounded by the configured page budget today, so the panic is unreachable. Consider returning a saturating value instead, so a future change to the memory model cannot turn an accounting detail into a node crash.

♻️ Proposed defensive change
-                u32::try_from(store.memory_size_bytes() / page_bytes)
-                    .expect("rwasm: memory page count must fit u32")
+                u32::try_from(store.memory_size_bytes() / page_bytes).unwrap_or(u32::MAX)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/runtime/contract_runtime.rs` around lines 159 - 170,
Update ContractRuntime::memory_pages to avoid panicking when converting the
computed rWasm page count to u32. Replace the expect-based conversion with
saturating behavior that returns u32::MAX on overflow, while preserving the
existing page calculation and Wasmtime return value.

258-305: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for a rejected initializer shape.

The tests cover fuel metering, underfunding, and budget rejection. They do not cover the IllegalOpcode paths in initial_memory_pages, for example two MemoryGrow instructions in the initializer, a MemoryGrow that is not followed by Drop, or a MemoryGrow at index 0. Those branches guard pre-allocation metering, so they deserve direct coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/runtime/contract_runtime.rs` around lines 258 - 305, Add
focused tests for the IllegalOpcode branches in initial_memory_pages, using
modules whose memory initializers contain two MemoryGrow instructions, a
MemoryGrow not followed by Drop, and a MemoryGrow at index 0. Invoke
ContractRuntime::new with each module and assert that initialization is rejected
with TrapCode::IllegalOpcode before allocation.
crates/runtime/src/executor.rs (1)

556-572: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated module_with_initial_memory test fixture in two modules. Both test modules define the same rWasm module builder for initial-memory coverage. One shared fixture prevents the two copies from drifting from the initializer shape that initial_memory_pages accepts.

  • crates/runtime/src/executor.rs#L556-L572: remove the local helper and import the shared fixture.
  • crates/runtime/src/runtime/contract_runtime.rs#L233-L249: move this helper into a shared #[cfg(test)] support module and re-export it for both test modules.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/executor.rs` around lines 556 - 572, The duplicated
module_with_initial_memory test fixture should be centralized. In
crates/runtime/src/runtime/contract_runtime.rs#L233-L249, move the helper into a
shared #[cfg(test)] support module and re-export it; in
crates/runtime/src/executor.rs#L556-L572, remove the local
module_with_initial_memory definition and import the shared fixture, preserving
the existing initializer behavior.
contracts/runtime-upgrade/src/tests.rs (1)

118-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the UpgradePlanCancelled payload in the cancellation tests.

assert_plan_is_cancelled verifies storage state only. The tests check the UpgradePlanCancelled signature at Line 288 and check the absence of the event at Line 428, but no test checks that a cancellation emits the event with the planned targets and hashes. Add a log assertion in assert_plan_is_cancelled to cover the emitted payload.

♻️ Suggested addition
 fn assert_plan_is_cancelled(h: &mut Harness, hash_a: B256, hash_b: B256) {
+    let logs = h.sdk.take_logs();
+    assert!(
+        logs.iter()
+            .any(|log| log.1[0].0 == UpgradePlanCancelled::SELECTOR),
+        "UpgradePlanCancelled was not emitted"
+    );
     assert_eq!(
         h.planned_updater(),
         Address::ZERO,
         "updater still delegated"
     );

Note that Harness::call does not clear logs between calls, so the helper must tolerate earlier UpgradePlanned and OwnerChanged entries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/runtime-upgrade/src/tests.rs` around lines 118 - 146, Update
assert_plan_is_cancelled to assert that cancellation emitted an
UpgradePlanCancelled event containing TARGET_A/hash_a and TARGET_B/hash_b.
Search accumulated logs without assuming Harness::call clears them, and tolerate
earlier UpgradePlanned and OwnerChanged entries while verifying the expected
cancellation payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/runtime-upgrade/src/lib.rs`:
- Around line 347-380: Use the remaining target/hash pair count as the sole
plan-existence check in cancel_planned_upgrade, so it returns without emitting
when no pairs remain. Update remove_planned_upgrade to clear planned_updater and
planned_genesis metadata when it removes the final pair, and add a regression
test covering final-pair execution followed by ownership change with no
UpgradePlanCancelled event.

In `@e2e/src/lib.rs`:
- Around line 41-42: Move the #[cfg(test)] attribute directly before the mod
eip7951; declaration so the EIP-7951 module is compiled only in test builds;
remove the misplaced standalone attribute.

---

Nitpick comments:
In `@contracts/runtime-upgrade/src/tests.rs`:
- Around line 118-146: Update assert_plan_is_cancelled to assert that
cancellation emitted an UpgradePlanCancelled event containing TARGET_A/hash_a
and TARGET_B/hash_b. Search accumulated logs without assuming Harness::call
clears them, and tolerate earlier UpgradePlanned and OwnerChanged entries while
verifying the expected cancellation payload.

In `@crates/runtime/src/executor.rs`:
- Around line 556-572: The duplicated module_with_initial_memory test fixture
should be centralized. In
crates/runtime/src/runtime/contract_runtime.rs#L233-L249, move the helper into a
shared #[cfg(test)] support module and re-export it; in
crates/runtime/src/executor.rs#L556-L572, remove the local
module_with_initial_memory definition and import the shared fixture, preserving
the existing initializer behavior.

In `@crates/runtime/src/runtime/contract_runtime.rs`:
- Around line 159-170: Update ContractRuntime::memory_pages to avoid panicking
when converting the computed rWasm page count to u32. Replace the expect-based
conversion with saturating behavior that returns u32::MAX on overflow, while
preserving the existing page calculation and Wasmtime return value.
- Around line 258-305: Add focused tests for the IllegalOpcode branches in
initial_memory_pages, using modules whose memory initializers contain two
MemoryGrow instructions, a MemoryGrow not followed by Drop, and a MemoryGrow at
index 0. Invoke ContractRuntime::new with each module and assert that
initialization is rejected with TrapCode::IllegalOpcode before allocation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ffb9823-6a40-426a-8b73-f8b43133ee69

📥 Commits

Reviewing files that changed from the base of the PR and between 69d73de and 65509a8.

📒 Files selected for processing (9)
  • contracts/eip7951/src/lib.rs
  • contracts/runtime-upgrade/README.md
  • contracts/runtime-upgrade/src/lib.rs
  • contracts/runtime-upgrade/src/tests.rs
  • crates/runtime/src/executor.rs
  • crates/runtime/src/runtime.rs
  • crates/runtime/src/runtime/contract_runtime.rs
  • e2e/src/eip7951.rs
  • e2e/src/lib.rs

Comment thread contracts/runtime-upgrade/src/lib.rs
Comment thread e2e/src/lib.rs
Comment on lines +41 to +42
mod eip7951;
#[cfg(test)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Guard the EIP-7951 test module with #[cfg(test)].

#[cfg(test)] on Line 42 does not apply to mod eip7951; on Line 41. Move the attribute before the module declaration. This prevents test-only code from compiling in non-test builds.

Proposed fix
-mod eip7951;
 #[cfg(test)]
+mod eip7951;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mod eip7951;
#[cfg(test)]
#[cfg(test)]
mod eip7951;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/src/lib.rs` around lines 41 - 42, Move the #[cfg(test)] attribute
directly before the mod eip7951; declaration so the EIP-7951 module is compiled
only in test builds; remove the misplaced standalone attribute.

@dmitry123
dmitry123 force-pushed the fix/audit-fixes-2026-aug branch from b021c63 to 7798f2e Compare August 6, 2026 19:15
@dmitry123
dmitry123 force-pushed the fix/audit-fixes-2026-aug branch from 7798f2e to 9944bf2 Compare August 7, 2026 05:38
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
crates/runtime/src/runtime/contract_runtime.rs (4)

16-17: 📐 Maintainability & Code Quality | 🔵 Trivial

Run the required Rust checks before merge.

Run cargo fmt --check for the touched Rust code. Run Clippy with -D warnings. Confirm that the declaring Cargo manifest uses Rust edition 2021 and that the runtime path remains no_std.

As per coding guidelines, Rust changes must pass cargo fmt --check, Clippy warnings are errors in CI with -D warnings, and runtime paths must preserve no_std constraints.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/runtime/contract_runtime.rs` around lines 16 - 17, Before
finalizing the Rust change, run cargo fmt --check and Clippy with -D warnings
for the touched code. Verify the declaring Cargo manifest uses Rust edition
2021, and ensure the runtime code around contract runtime imports preserves its
no_std compatibility.

Source: Coding guidelines


215-227: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Make the charge-before-allocation guarantee observable.

The funded test checks only the final fuel amount. The underfunded test checks only OutOfFuel. An implementation that allocates memory first and charges fuel afterward could produce the same results when allocation succeeds.

If the requirement is to charge fuel before instantiation, add an allocation trace, hook, or other observable assertion that proves ConsumeFuel precedes MemoryGrow. Otherwise, narrow the test names and comments to fuel accounting only.

Also applies to: 229-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/runtime/contract_runtime.rs` around lines 215 - 227,
Update the tests meters_initial_memory_before_instantiation and the underfunded
counterpart to observe ordering, not just fuel totals or OutOfFuel: add an
allocation trace, hook, or equivalent assertion proving ConsumeFuel occurs
before MemoryGrow during ContractRuntime::new. If no observable ordering
mechanism is available, rename and revise the tests/comments to describe only
fuel accounting.

173-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Align the test name and failure text with the new behavior.

rwasm_initializer_allocates_maximum_memory_without_a_fuel_charge asserts that the initializer consumes fuel. The comment and expect message still describe an unmetered initializer. Rename the test and update the stale text.

Proposed fix
-fn rwasm_initializer_allocates_maximum_memory_without_a_fuel_charge() {
+fn rwasm_initializer_charges_initial_memory_fuel() {
...
-        // compiler-generated initializer. It must fail with OutOfFuel once that initializer
-        // emits a proportional ConsumeFuel before MemoryGrow.
+        // The compiler-generated initializer must consume a proportional
+        // ConsumeFuel charge before MemoryGrow.
...
-            .expect("the current unmetered initializer allocates before checking fuel");
+            .expect("sufficient fuel must cover the initial-memory charge");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/runtime/contract_runtime.rs` around lines 173 - 213, The
test name rwasm_initializer_allocates_maximum_memory_without_a_fuel_charge
contradicts its fuel-consumption assertions. Rename it to describe the metered
initializer behavior, and update the stale executor.expect message and nearby
comments to state that allocation consumes fuel through the bulk-operation
charge.

135-141: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Move initial_memory_fuel under the test module when moving runtime charge handling.

initial_memory_fuel and its rWasm imports are only called inside #[cfg(test)], so the production ContractRuntime::new path does not use the checked_mul overflow handling and this helper can trigger dead-code warnings with -D warnings. Gate the helper in #[cfg(test)] unless the runtime constructor is updated to charge initial memory fuel before calling create_executor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/runtime/src/runtime/contract_runtime.rs` around lines 135 - 141, Move
the initial_memory_fuel helper and its rWasm-related imports into the
#[cfg(test)] module, since they are only used by tests. Alternatively, update
ContractRuntime::new to charge initial memory fuel before create_executor, but
preserve the existing overflow handling; prefer test-only gating if no
production runtime behavior is required.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bins/runtime-upgrade/main.rs`:
- Around line 708-718: After loading the release in the provenance-gated startup
flow, obtain the RPC chain ID and compare it with genesis.config.chain_id before
entering any plan or transaction logic. Reject mismatches, including explicitly
selected --genesis-channel artifacts against custom RPC endpoints, and add a
regression test covering that case.

---

Nitpick comments:
In `@crates/runtime/src/runtime/contract_runtime.rs`:
- Around line 16-17: Before finalizing the Rust change, run cargo fmt --check
and Clippy with -D warnings for the touched code. Verify the declaring Cargo
manifest uses Rust edition 2021, and ensure the runtime code around contract
runtime imports preserves its no_std compatibility.
- Around line 215-227: Update the tests
meters_initial_memory_before_instantiation and the underfunded counterpart to
observe ordering, not just fuel totals or OutOfFuel: add an allocation trace,
hook, or equivalent assertion proving ConsumeFuel occurs before MemoryGrow
during ContractRuntime::new. If no observable ordering mechanism is available,
rename and revise the tests/comments to describe only fuel accounting.
- Around line 173-213: The test name
rwasm_initializer_allocates_maximum_memory_without_a_fuel_charge contradicts its
fuel-consumption assertions. Rename it to describe the metered initializer
behavior, and update the stale executor.expect message and nearby comments to
state that allocation consumes fuel through the bulk-operation charge.
- Around line 135-141: Move the initial_memory_fuel helper and its rWasm-related
imports into the #[cfg(test)] module, since they are only used by tests.
Alternatively, update ContractRuntime::new to charge initial memory fuel before
create_executor, but preserve the existing overflow handling; prefer test-only
gating if no production runtime behavior is required.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b5f63ed5-9a9d-43dd-b8ba-ec5b88ac7602

📥 Commits

Reviewing files that changed from the base of the PR and between 4a662b7 and 9944bf2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (38)
  • .gitignore
  • Cargo.toml
  • bins/runtime-upgrade/Cargo.toml
  • bins/runtime-upgrade/main.rs
  • bins/runtime-upgrade/provenance.rs
  • contracts/eip7951/src/lib.rs
  • contracts/fee-manager/src/lib.rs
  • contracts/runtime-upgrade/README.md
  • contracts/runtime-upgrade/src/lib.rs
  • contracts/runtime-upgrade/src/tests.rs
  • crates/codec/src/bytes_codec.rs
  • crates/codec/src/encoder.rs
  • crates/codec/src/hash.rs
  • crates/codec/src/vec.rs
  • crates/node/Cargo.toml
  • crates/node/src/chainspec.rs
  • crates/node/src/utils.rs
  • crates/release-verify/Cargo.toml
  • crates/release-verify/src/asset.rs
  • crates/release-verify/src/error.rs
  • crates/release-verify/src/key.rs
  • crates/release-verify/src/lib.rs
  • crates/release-verify/src/load.rs
  • crates/release-verify/src/manifest.rs
  • crates/release-verify/src/signature.rs
  • crates/release-verify/src/test_support.rs
  • crates/release-verify/src/tests.rs
  • crates/release-verify/testdata/genesis-mainnet-v1.0.0.json.gz.asc
  • crates/release-verify/testdata/genesis-manifest-v1.3.2.txt
  • crates/release-verify/testdata/genesis-manifest-v1.3.2.txt.asc
  • crates/release-verify/testdata/genesis-v0.3.4-dev.json.gz.asc
  • crates/release-verify/testdata/genesis-v0.5.7.json.gz.asc
  • crates/runtime/src/runtime/contract_runtime.rs
  • e2e/src/builtins.rs
  • e2e/src/ddos.rs
  • e2e/src/eip7951.rs
  • e2e/src/fee_manager.rs
  • e2e/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (32)
  • crates/node/Cargo.toml
  • crates/release-verify/testdata/genesis-manifest-v1.3.2.txt.asc
  • crates/release-verify/testdata/genesis-mainnet-v1.0.0.json.gz.asc
  • crates/release-verify/testdata/genesis-v0.3.4-dev.json.gz.asc
  • crates/release-verify/testdata/genesis-v0.5.7.json.gz.asc
  • bins/runtime-upgrade/Cargo.toml
  • crates/codec/src/encoder.rs
  • e2e/src/eip7951.rs
  • contracts/runtime-upgrade/README.md
  • crates/node/src/chainspec.rs
  • e2e/src/lib.rs
  • crates/release-verify/Cargo.toml
  • crates/release-verify/src/lib.rs
  • .gitignore
  • e2e/src/ddos.rs
  • contracts/eip7951/src/lib.rs
  • Cargo.toml
  • contracts/runtime-upgrade/src/lib.rs
  • crates/release-verify/src/tests.rs
  • crates/release-verify/src/key.rs
  • crates/release-verify/src/manifest.rs
  • crates/codec/src/hash.rs
  • crates/release-verify/src/test_support.rs
  • crates/release-verify/src/signature.rs
  • crates/release-verify/src/load.rs
  • crates/node/src/utils.rs
  • crates/codec/src/vec.rs
  • contracts/runtime-upgrade/src/tests.rs
  • crates/codec/src/bytes_codec.rs
  • crates/release-verify/testdata/genesis-manifest-v1.3.2.txt
  • bins/runtime-upgrade/provenance.rs
  • crates/release-verify/src/asset.rs

Comment thread bins/runtime-upgrade/main.rs
Signed-off-by: Dmitry Savonin <github@dmitry123.xyz>
@dmitry123 dmitry123 changed the title test: cover repeated output writes bypassing aggregate gas bounds fix(security): August 2026 audit fixes across runtime, codec, node and system contracts Aug 7, 2026
@dmitry123 dmitry123 changed the title fix(security): August 2026 audit fixes across runtime, codec, node and system contracts fix(security): august 2026 audit fixes across runtime, codec, node and system contracts Aug 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/runtime/src/executor.rs`:
- Around line 408-430: The memory limit is checked before execution but not when
an interrupted runtime is retained, allowing execute or resume to grow memory
past the aggregate limit. Update try_remember_runtime to calculate the current
in-flight memory including the runtime being admitted and reject it before
inserting into recoverable_runtimes; preserve the existing rejection
result/cleanup behavior, and add a regression test that grows memory before
interruption.

In `@crates/sdk/src/types/storage.rs`:
- Around line 90-137: Extend the short-string tests around
write_storage_short_string and storage_short_string to cover the non-canonical
value "USDC\0fake": assert the write returns ExitCode::MalformedBuiltinParams
without changing storage, and seed the same bytes through raw storage then
assert the read returns that error.

In `@crates/sdk/src/universal_token/storage.rs`:
- Around line 392-435: Extend the token-name regression tests around
TokenNameOrSymbol::try_from_str and TokenNameOrSymbol::from_word with
non-canonical metadata cases: assert that "USDC\0fake" is rejected and that a
raw B256 word containing nonzero bytes after its first NUL is rejected during
decoding. Verify the assertions cover both encoding and decoding paths without
changing existing valid-name behavior.
- Around line 40-73: Enforce canonical NUL-terminated short strings across all
affected sites: in crates/sdk/src/universal_token/storage.rs lines 40-73, reject
embedded NULs in text constructors and reject nonzero bytes after the first NUL
during decoding; in crates/sdk/src/universal_token.rs lines 182-194, make
try_build use the canonical validator and ensure build cannot bypass it; in
crates/sdk/src/types/storage.rs lines 22-40, validate canonical form on both
reads and writes; in contracts/universal-token/src/lib.rs lines 720-729, return
ExitCode::MalformedBuiltinParams before either storage write; and in
contracts/universal-token/src/tests.rs lines 1086-1155, add constructor and
stored-slot regressions for USDC\0fake.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bb2794c-4d90-4975-a31c-1aa29300c91a

📥 Commits

Reviewing files that changed from the base of the PR and between 9944bf2 and fd2aa79.

📒 Files selected for processing (9)
  • contracts/universal-token/src/lib.rs
  • contracts/universal-token/src/tests.rs
  • crates/runtime/src/executor.rs
  • crates/runtime/src/runtime.rs
  • crates/runtime/src/runtime/contract_runtime.rs
  • crates/sdk/src/types/storage.rs
  • crates/sdk/src/universal_token.rs
  • crates/sdk/src/universal_token/storage.rs
  • crates/types/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/runtime/src/runtime/contract_runtime.rs

Comment thread crates/runtime/src/executor.rs
Comment thread crates/sdk/src/types/storage.rs
Comment thread crates/sdk/src/universal_token/storage.rs
Comment thread crates/sdk/src/universal_token/storage.rs
@dmitry123
dmitry123 force-pushed the fix/audit-fixes-2026-aug branch from 604cc4a to 6fcfbae Compare August 7, 2026 10:26
@dmitry123
dmitry123 force-pushed the fix/audit-fixes-2026-aug branch from 6fcfbae to 2b91e95 Compare August 7, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants