Skip to content

fix: address MorphTx audit findings#134

Open
panos-xyz wants to merge 19 commits into
mainfrom
audit/fix-feedback
Open

fix: address MorphTx audit findings#134
panos-xyz wants to merge 19 commits into
mainfrom
audit/fix-feedback

Conversation

@panos-xyz

@panos-xyz panos-xyz commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix MorphTx parsing, RPC construction, and validation parity issues found in the audit, including V0 routing, version inference, gas price conflicts, zero references, chain ID defaulting, and request input validation.
  • Align EVM and txpool behavior with Morph geth for token-fee fee-cap validation, conservative token budget admission, block gas-limit revalidation, and canonical head lookup.
  • Update the Bernoulli precompile reference comment; Rec-2 and Rec-3 have no net code change after follow-up review.

Test plan

  • cargo fmt --all -- --check
  • cargo check -p morph-primitives -p morph-rpc -p morph-revm -p morph-txpool -p morph-engine-api

Summary by CodeRabbit

  • Bug Fixes
    • Fixed MorphTx version handling to treat zero references as absent and correctly decode legacy-compatible 0x00 payloads across decoding and validation flows.
    • Updated RPC MorphTx request-to-transaction conversion to select the correct MorphTx version, normalize references, default missing chain ID, and reject conflicting data vs input.
    • Made fee and txpool checks more consistent: priority-fee validation applies whenever fee charging is enabled, token-fee admission uses the conservative max-fee cap, and MorphTxs exceeding the canonical block gas limit are rejected early.
  • Documentation
    • Updated Bernoulli precompile inline reference text.

panos-xyz added 16 commits June 17, 2026 23:10
Align legacy MorphTx version routing with geth so zero-prefixed payloads enter the V0 decoder instead of failing as unsupported versions.
Apply the EIP-1559 fee-cap checks to MorphTx regardless of fee asset so token-fee blocks cannot diverge from geth validation.
Build fee-token-only MorphTx requests as V0 while reserving V1 for reference or memo fields, matching geth RPC defaults.
Return an explicit RPC construction error when legacy gasPrice is mixed with Morph fields instead of silently changing fee semantics.
Allow V0 MorphTx requests with an all-zero reference so RPC version inference and validation match geth normalization.
Revalidate pooled MorphTx gas limits against the latest block gas limit so stale over-limit transactions are removed with their descendants.
Use max_fee_per_gas for token-fee MorphTx pool admission so mempool validation matches geth's conservative affordability check.
Fill missing MorphTx request chain IDs from the EVM environment on tx-env conversion so simulation paths do not depend on implicit alloy defaults.
Reject conflicting data/input fields and empty contract creation in MorphTx RPC construction to match geth request validation.
Point the Bernoulli precompile comment at the matching geth contract map instead of the Morph203 section.
Run the same MorphTx structural validation on tx-env conversion so eth_call and estimate paths reject requests that real construction would reject.
Treat failed committed-storage reads during Morph SLOAD/SSTORE original-value restoration as fatal instead of continuing with uncertain gas accounting state.
Apply rustfmt to earlier audit fixes so the branch remains format-clean.
Use reth's latest sealed header lookup so canonical head number, hash, and timestamp come from the same provider snapshot.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab61877c-47dc-4cec-95e5-6d5a72aa9685

📥 Commits

Reviewing files that changed from the base of the PR and between 1f41fdc and f60c2b2.

📒 Files selected for processing (2)
  • crates/rpc/src/eth/transaction.rs
  • crates/rpc/src/types/request.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/rpc/src/eth/transaction.rs

📝 Walkthrough

Walkthrough

Multiple MorphTx correctness fixes across the stack: V0 reference=zero is treated as absent in validation and decoding; EIP-1559 basefee enforcement is extended to all MorphTx types; RPC transaction conversion dynamically selects MorphTx version from request fields and validates required fields; txpool token-fee budget switches to the conservative max_fee_per_gas cap; and the pool maintenance loop evicts transactions exceeding the block gas limit.

Changes

MorphTx versioning, fee validation, and txpool hardening

Layer / File(s) Summary
TxMorph V0 reference validation and decode routing
crates/primitives/src/transaction/morph_transaction.rs
validate_version accepts Some(B256::ZERO) as equivalent to absent for V0; decode_fields routes a leading 0x00 byte to the V0 path; rlp_decode_with_signature and Decodable impl aligned with new routing; two new tests cover the passing validation and RLP-error cases.
Broaden EIP-1559 basefee check to all MorphTx in validate_env
crates/revm/src/handler.rs
Removes uses_token_fee gating so the priority-fee/basefee check applies to all MorphTx when fee charging is enabled; adds test imports and new test asserting a token-fee MorphTx below the basefee is rejected with GasPriceLessThanBasefee.
RPC MorphTx version derivation and request validation
crates/rpc/src/eth/transaction.rs, crates/rpc/src/types/request.rs
Consolidates imports; updates doc comments; adds morph_tx_version, is_nonzero_reference, and normalize_reference helpers; try_into_tx_env backfills missing chain_id from EvmEnv and sets version dynamically; try_build_morph_tx_from_request rejects gas_price alongside Morph fields, requires chain_id, validates initcode for contract creation, and derives version; adds create_morph_transaction_request test helper and comprehensive coverage for all paths including version 0 for fee-token-only transactions; MorphTransactionRequest adds optional version field with inference behavior when omitted.
Txpool token-fee budget and gas-limit enforcement
crates/txpool/src/morph_tx_validation.rs, crates/txpool/src/maintain.rs, crates/txpool/src/validator.rs
MorphTxValidationInput removes base_fee_per_gas field; validate_morph_tx removes effective_gas_price derivation and uses conservative gas_fee cap for token-fee budget; maintain_morph_pool adds exceeds_block_gas_limit helper and evicts MorphTxs exceeding the block gas limit before revalidation; validator test updated to expect InsufficientTokenBalance against max-fee-derived amount; all test constructors updated to remove base_fee_per_gas.
Engine API canonical head via latest_header
crates/engine-api/src/builder.rs
Adds BlockReaderIdExt trait bounds to implementation and build_l2_payload where clauses; rewrites current_head() to call provider.latest_header() directly instead of the two-step chain_info + sealed_header_by_hash approach.
Bernoulli precompile doc update
crates/revm/src/precompiles.rs
Updates the inline doc for bernoulli() to point to a specific go-ethereum contracts.go line range.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • morph-l2/morph-reth#34: Modifies the same validate_env EIP-1559 priority-fee/gas-price gating for ETH-fee MorphTxs, directly adjacent to this PR's removal of token-fee gating.
  • morph-l2/morph-reth#19: Adds/enhances MorphTransaction reference field support, directly related to this PR's treatment of reference=zero as absent in version selection and V0 validation.
  • morph-l2/morph-reth#36: Adjusts TxMorph RLP encode/decode version-byte handling, overlapping with this PR's decode_fields routing of a leading 0x00 byte to the V0 path.

Suggested reviewers

  • chengwenxi

🐇 A zero reference? No problem at all,
V0 hops through without hitting a wall.
Token fees checked at the basefee gate,
Gas limits culled before it's too late.
The version now comes from the fields, not a guess —
This bunny approves of this tidy finesse! 🌿

🚥 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 'fix: address MorphTx audit findings' directly describes the main purpose of the changeset—addressing multiple audit findings across MorphTx parsing, RPC construction, validation, and EVM behavior.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 audit/fix-feedback

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 and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/rpc/src/eth/transaction.rs (1)

108-136: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize zero references before storing them.

is_nonzero_reference and morph_tx_version treat B256::ZERO as absent, but tx_env.reference and TxMorph.reference can still store Some(B256::ZERO). That leaves V0 MorphTxs with an in-memory/RPC reference that their V0 encoding/signing omits, which can break reference indexing and response parity.

Proposed canonicalization
@@
-        let reference = self.reference;
+        let reference = self.reference.filter(|reference| *reference != B256::ZERO);
@@
 fn try_build_morph_tx_from_request(
     req: &alloy_rpc_types_eth::TransactionRequest,
     fee_token_id: U64,
     fee_limit: U256,
     reference: Option<alloy_primitives::B256>,
     memo: Option<alloy_primitives::Bytes>,
 ) -> Result<Option<TxMorph>, &'static str> {
+    let reference = reference.filter(|reference| *reference != B256::ZERO);
     let fee_token_id_u16 = u16::try_from(fee_token_id.to::<u64>()).map_err(|_| "invalid token")?;

Also applies to: 183-195, 230-231

🤖 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/rpc/src/eth/transaction.rs` around lines 108 - 136, The reference
value is being stored in tx_env.reference without normalizing zero values. Since
is_nonzero_reference and morph_tx_version treat B256::ZERO as absent, any
B256::ZERO value should be converted to None before assignment. Modify the code
where tx_env.reference is set to normalize the reference value by checking if it
is Some(B256::ZERO) and converting it to None. Apply the same normalization
pattern to the other locations mentioned in the comment where reference is being
stored or used (around lines 183-195 and 230-231).
crates/primitives/src/transaction/morph_transaction.rs (1)

393-400: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply the zero-byte V0 route to the primary decode paths too.

Line 393 only updates decode_fields, but this file documents rlp_decode_with_signature as the primary signed decode path, and both it and Decodable::decode still return "unsupported morph tx version" for first_byte == 0. A zero-prefixed 2718/signed payload will miss the new V0/RLP-level error behavior.

Proposed consistency fix
@@
-        } else if first_byte >= 0xC0 {
+        } else if first_byte == MORPH_TX_VERSION_0 || first_byte >= 0xC0 {
             MORPH_TX_VERSION_0
         } else {
             return Err(alloy_rlp::Error::Custom("unsupported morph tx version"));
@@
-        } else if first_byte < 0xC0 {
+        } else if first_byte != MORPH_TX_VERSION_0 && first_byte < 0xC0 {
             // Invalid: not a version we support and not an RLP list
             return Err(alloy_rlp::Error::Custom("unsupported morph tx version"));
         }
🤖 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/primitives/src/transaction/morph_transaction.rs` around lines 393 -
400, The zero-byte V0 route fix has been applied to decode_fields but not to the
other primary decode paths. Apply the same zero-byte V0 handling logic (checking
if first_byte == 0 to treat it as V0 format with direct RLP decode) to the
rlp_decode_with_signature method and the Decodable::decode trait implementation,
ensuring they handle first_byte == 0 consistently by routing to the appropriate
V0 decoding path instead of returning "unsupported morph tx version" error.
🧹 Nitpick comments (1)
crates/txpool/src/morph_tx_validation.rs (1)

135-139: The base_fee_per_gas field in MorphTxValidationInput is no longer referenced in validation logic.

After this change, token_gas_fee is set directly to gas_fee (which uses max_fee_per_gas), and base_fee_per_gas is not read within validate_morph_tx. The field is still populated by callers (validator.rs line 448 and maintain.rs line 261), but it serves no purpose in the current validation logic. Consider removing it from the struct and its call sites if it's no longer needed elsewhere, or add a comment explaining it's retained for future use.

🤖 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/txpool/src/morph_tx_validation.rs` around lines 135 - 139, The
`base_fee_per_gas` field in the `MorphTxValidationInput` struct is no longer
being read in the `validate_morph_tx` function, as `token_gas_fee` is now set
directly to `gas_fee`. Either remove the `base_fee_per_gas` field from the
`MorphTxValidationInput` struct and its assignments in the call sites
(validator.rs and maintain.rs), or if this field is needed for future use, add
an explanatory comment to the struct definition clarifying why it is retained
despite not being currently used in the validation logic.
🤖 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/rpc/src/eth/transaction.rs`:
- Around line 129-137: The try_into_tx_env method currently lacks validation to
reject gas_price when combined with Morph fields, creating inconsistency with
try_build_morph_tx_from_request which performs this check. Add validation in
try_into_tx_env that rejects requests combining legacy gas_price with
Morph-specific fields (fee_token_id, reference, or memo). This check should
occur before the is_morph_tx detection logic to ensure eth_call and
eth_estimateGas reject the same invalid shapes that signing and simulation
reject.

---

Outside diff comments:
In `@crates/primitives/src/transaction/morph_transaction.rs`:
- Around line 393-400: The zero-byte V0 route fix has been applied to
decode_fields but not to the other primary decode paths. Apply the same
zero-byte V0 handling logic (checking if first_byte == 0 to treat it as V0
format with direct RLP decode) to the rlp_decode_with_signature method and the
Decodable::decode trait implementation, ensuring they handle first_byte == 0
consistently by routing to the appropriate V0 decoding path instead of returning
"unsupported morph tx version" error.

In `@crates/rpc/src/eth/transaction.rs`:
- Around line 108-136: The reference value is being stored in tx_env.reference
without normalizing zero values. Since is_nonzero_reference and morph_tx_version
treat B256::ZERO as absent, any B256::ZERO value should be converted to None
before assignment. Modify the code where tx_env.reference is set to normalize
the reference value by checking if it is Some(B256::ZERO) and converting it to
None. Apply the same normalization pattern to the other locations mentioned in
the comment where reference is being stored or used (around lines 183-195 and
230-231).

---

Nitpick comments:
In `@crates/txpool/src/morph_tx_validation.rs`:
- Around line 135-139: The `base_fee_per_gas` field in the
`MorphTxValidationInput` struct is no longer being read in the
`validate_morph_tx` function, as `token_gas_fee` is now set directly to
`gas_fee`. Either remove the `base_fee_per_gas` field from the
`MorphTxValidationInput` struct and its assignments in the call sites
(validator.rs and maintain.rs), or if this field is needed for future use, add
an explanatory comment to the struct definition clarifying why it is retained
despite not being currently used in the validation logic.
🪄 Autofix (Beta)

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

Run ID: 02bfda11-83f4-4ebf-82b8-96ea88697717

📥 Commits

Reviewing files that changed from the base of the PR and between db1f388 and c85bb02.

📒 Files selected for processing (8)
  • crates/engine-api/src/builder.rs
  • crates/primitives/src/transaction/morph_transaction.rs
  • crates/revm/src/handler.rs
  • crates/revm/src/precompiles.rs
  • crates/rpc/src/eth/transaction.rs
  • crates/txpool/src/maintain.rs
  • crates/txpool/src/morph_tx_validation.rs
  • crates/txpool/src/validator.rs

Comment thread crates/rpc/src/eth/transaction.rs Outdated
@panos-xyz panos-xyz changed the title Fix MorphTx audit findings fix: address MorphTx audit findings Jun 18, 2026
panos-xyz and others added 3 commits June 18, 2026 11:06
Align MorphTx simulation validation with signing, canonicalize zero references, extend zero-byte V0 decode routing, and remove unused txpool validation input.
Treat requests that include legacy gas_price as standard transactions even when Morph-specific fields are present, matching geth's transaction type precedence.
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.

1 participant