Skip to content

feat: add morph rpc crate#20

Merged
panos-xyz merged 20 commits intomainfrom
feat/morph-rpc
Feb 6, 2026
Merged

feat: add morph rpc crate#20
panos-xyz merged 20 commits intomainfrom
feat/morph-rpc

Conversation

@panos-xyz
Copy link
Contributor

@panos-xyz panos-xyz commented Feb 5, 2026

Summary by CodeRabbit

  • New Features

    • Added Node and RPC crates with a Morph-specific ETH RPC adapter.
    • RPC: L1 fee estimation, ERC‑20 fee support, and fee token/fee limit fields on transactions.
    • Receipts and RPC transaction types now include L1-related metadata.
    • Execution/block payloads include additional block fields.
  • Refactor

    • Serialization (serde/serde-bincode-compat) consolidated and made opt-in via feature flags.
  • Breaking Changes

    • Receipt L1 fee is now a concrete numeric value (ZERO when absent) instead of optional.

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Adds new workspace crates (crates/node, crates/rpc), implements a Morph-specific ETH RPC layer and types (receipts/transactions/requests), introduces MorphReceiptEnvelope and changes receipt l1_fee to U256, gates serde derives behind a serde feature across payload types, adds serde-bincode-compat feature and RlpBincode impls, and refactors token-fee/revm APIs (fetch, balance EVM-fallback, mapping helpers).

Changes

Cohort / File(s) Summary
Workspace & manifests
Cargo.toml, crates/node/Cargo.toml, crates/rpc/Cargo.toml, crates/chainspec/Cargo.toml, crates/primitives/Cargo.toml
Added crates/node and crates/rpc workspace members; added workspace deps morph-node, morph-rpc, and git dep reth-tasks; updated crate descriptions and primitives features (added serde-bincode-compat).
RPC crate & types
crates/rpc/src/lib.rs, crates/rpc/src/error.rs, crates/rpc/src/eth/..., crates/rpc/src/types/..., crates/rpc/Cargo.toml
New Morph RPC crate: MorphEthApi wrapper, builder, MorphRpcTypes/converter, MorphReceiptConverter, MorphReceiptBuilder, full error mapping (MorphEthApiError), EthCall/EstimateCall/Call implementations, and Morph RPC types (receipt/transaction/request).
Primitives — receipts & envelopes
crates/primitives/src/receipt/envelope.rs, crates/primitives/src/receipt/mod.rs, crates/primitives/src/receipt/receipt.rs, crates/primitives/src/transaction/envelope.rs, crates/primitives/src/header.rs, crates/primitives/src/lib.rs
Added MorphReceiptEnvelope with EIP-2718 encoding/decoding; exported it; changed l1_fee from Option<U256>U256 (zero default); added serde-bincode-compat RlpBincode impls and adjusted feature gating.
revm token-fee & handler
crates/revm/src/token_fee.rs, crates/revm/src/handler.rs, crates/revm/src/lib.rs
Refactored token-fee API: try_fetchfetch (and fetch_storage_only), calculate_token_amounteth_to_token_amount, added EVM-fallback balance reader (erc20_balance_of/encode_balance_of), and added mapping_slot helpers; adapted call sites.
txpool validation
crates/txpool/src/morph_tx_validation.rs, crates/txpool/Cargo.toml
Updated tx validation to use new TokenFeeInfo API (fetch, eth_to_token_amount) and switched morph-primitives feature to serde-bincode-compat.
Payload types — conditional serde & fields
crates/payload/types/src/*, crates/payload/types/Cargo.toml, crates/payload/builder/Cargo.toml, crates/evm/src/lib.rs
Gated Serialize/Deserialize derives and serde attributes behind feature = "serde" via cfg_attr; expanded ExecutableL2Data with fields (parent_hash, receipts_root, logs_bloom, withdraw_trie_root, hash); adjusted default features and morph-primitives feature usage.
New node crate root
crates/node/src/lib.rs, crates/node/Cargo.toml
New node crate that re-exports morph_rpc as rpc and provides crate metadata and lint settings.
Primitives feature/gating & header changes
crates/primitives/src/header.rs, crates/primitives/Cargo.toml
Switched NodePrimitives gating to serde-bincode-compat, added RlpBincode impl for MorphHeader under that feature; reorganized dependencies/features in primitive manifest.
Large new code additions
crates/primitives/src/receipt/envelope.rs, crates/rpc/src/eth/transaction.rs, crates/rpc/src/eth/receipt.rs
Significant new implementations: receipt envelope (312 LOC), RPC transaction/receipt conversions and builders (hundreds LOC), and many trait impls for Morph integration. Review attention: encoding/decoding, trait impls, and serde/rlp compatibility.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MorphEthApi
    participant TokenFeeInfo
    participant Database
    participant EVM
    Client->>MorphEthApi: eth_call / eth_estimateGas
    MorphEthApi->>MorphEthApi: build EVM env, compute L1 fee
    alt fee_token_id present
        MorphEthApi->>TokenFeeInfo: fetch(db, token_id)
        TokenFeeInfo->>Database: load_registry_entry / read storage
        Database-->>TokenFeeInfo: registry data
        MorphEthApi->>Database: read token balance (mapping_slot_for) or call EVM
        Database-->>MorphEthApi: balance (or EVM->Database->EVM path)
        MorphEthApi->>MorphEthApi: eth_to_token_amount(L1_fee), compute token allowance
    else pay with ETH
        MorphEthApi->>MorphEthApi: compute ETH-based allowance from balance
    end
    MorphEthApi->>EVM: simulate call with computed gas_limit
    EVM->>Database: state reads
    Database-->>EVM: state
    EVM-->>MorphEthApi: simulation result
    MorphEthApi-->>Client: return result
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • anylots
  • chengwenxi

Poem

🐰 A hop through crates both new and old,
I stitched RPC wings and wrapped receipts in gold.
Fees now fetch, envelopes sing, serde sleeps till told,
New nodes and types — a brave codefold.
Hooray, I nibble bugs (not bold)! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add morph rpc crate' accurately describes the main change—adding a new morph-rpc crate with comprehensive RPC implementations for Morph chain.
Docstring Coverage ✅ Passed Docstring coverage is 80.47% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/morph-rpc

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.

@panos-xyz panos-xyz requested review from anylots and chengwenxi and removed request for anylots February 5, 2026 05:45
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
crates/payload/types/src/attributes.rs (1)

17-23: ⚠️ Potential issue | 🟠 Major

Gate #[serde(flatten)] to match other serde attributes.

The unconditional #[serde(flatten)] on line 22 will cause compile errors when the serde feature is disabled. Gate it with #[cfg_attr(feature = "serde", ...)] to match the serde derives on lines 18-19 and the attribute on lines 29-32.

✅ Suggested fix
-    #[serde(flatten)]
+    #[cfg_attr(feature = "serde", serde(flatten))]
crates/payload/types/src/executable_l2_data.rs (1)

126-190: ⚠️ Potential issue | 🟡 Minor

Serde tests need feature gating.

The serde roundtrip tests (test_serde_roundtrip, test_serde_without_base_fee, test_serde_camel_case) use serde_json serialization but are not gated behind #[cfg(feature = "serde")]. These tests will fail to compile when the serde feature is disabled since the Serialize/Deserialize derives won't be present.

🔧 Proposed fix
+    #[cfg(feature = "serde")]
     #[test]
     fn test_serde_roundtrip() {
         // ...
     }
 
+    #[cfg(feature = "serde")]
     #[test]
     fn test_serde_without_base_fee() {
         // ...
     }
 
+    #[cfg(feature = "serde")]
     #[test]
     fn test_serde_camel_case() {
         // ...
     }
🤖 Fix all issues with AI agents
In `@crates/payload/types/src/safe_l2_data.rs`:
- Around line 15-50: The serde-based tests test_serde_roundtrip and
test_serde_without_optional_fields must be compiled only when the "serde"
feature is enabled; add #[cfg(feature = "serde")] (or wrap them in a
#[cfg(feature = "serde")] mod) above each test to gate them, and ensure any uses
of serde_json in those tests are only present under that cfg so builds with
--no-default-features compile cleanly.

In `@crates/rpc/Cargo.toml`:
- Around line 12-17: The morph-primitives dependency in Cargo.toml currently
enables features ["serde","reth-codec"] but should follow the repository
migration to serde-bincode-compat; update the morph-primitives entry (the line
starting with morph-primitives = { workspace = true, features = [...] }) to use
features = ["serde-bincode-compat"] instead of ["serde","reth-codec"] so the RPC
crate matches other crates and removes the unused reth-codec feature.
🧹 Nitpick comments (5)
crates/revm/src/token_fee.rs (2)

209-225: Clarify expected key encoding for mapping_slot.

A short doc tweak reduces the chance of callers passing non-ABI-encoded keys.

✏️ Suggested doc clarification
-/// For `mapping(keyType => valueType)` at slot `base_slot`,
-/// the value for `key` is at `keccak256(key ++ base_slot)`.
+/// For `mapping(keyType => valueType)` at slot `base_slot`,
+/// `key` should be ABI-encoded to 32 bytes for static types; then the value is at
+/// `keccak256(key ++ base_slot)`.

369-393: Add a rounding-up test case to lock in token fee behavior.

Current coverage focuses on exact division and zero ratio; a remainder case would protect the round-up behavior.

✅ Suggested test addition
@@
         let token_amount = info.eth_to_token_amount(eth_amount);
         assert_eq!(token_amount, U256::from(500_000_000_000_000_000u128)); // 0.5 tokens
+
+        let info_round = TokenFeeInfo {
+            price_ratio: U256::from(3_000_000_000_000_000_000u128), // 3 ETH per token
+            scale: U256::from(1_000_000_000_000_000_000u128),
+            ..Default::default()
+        };
+        let token_amount_round = info_round.eth_to_token_amount(eth_amount);
+        assert_eq!(token_amount_round, U256::from(333_333_333_333_333_334u128));
Based on learnings: When reviewing token fee logic, rounding up via calculate_token_amount is intentional (geth-consistent) and should be protected with regression tests.
crates/primitives/src/lib.rs (1)

33-34: Clarify the purpose of the unused import.

The as _ pattern indicates this import is for side effects (likely trait implementations). Consider adding a brief comment explaining why this import is needed under this feature flag.

📝 Suggested documentation
 #[cfg(feature = "serde-bincode-compat")]
+// Required for trait implementations from reth_ethereum_primitives
 use reth_ethereum_primitives as _;
crates/payload/types/src/executable_l2_data.rs (1)

64-65: Consider using Bloom type instead of Bytes for logs_bloom.

The logs_bloom field is typed as Bytes, but logs bloom filters have a fixed 256-byte size. Using alloy_primitives::Bloom would provide compile-time size guarantees and better type safety. The test at line 139 uses Bytes::from(vec![0; 256]), suggesting the fixed size is expected.

♻️ Suggested change
-use alloy_primitives::{Address, B256, Bytes};
+use alloy_primitives::{Address, B256, Bloom, Bytes};
 
 // ...
 
     /// Bloom filter for logs.
-    pub logs_bloom: Bytes,
+    pub logs_bloom: Bloom,
crates/primitives/src/receipt/envelope.rs (1)

40-67: Consider accepting owned logs to avoid unconditional cloning.

from_parts always clones logs. Accepting owned Log items lets callers pass already-owned logs without extra cloning (borrowed callers can still use .iter().cloned()).

♻️ Proposed tweak
-    pub fn from_parts<'a>(
-        status: bool,
-        cumulative_gas_used: u64,
-        logs: impl IntoIterator<Item = &'a Log>,
-        tx_type: MorphTxType,
-    ) -> Self {
-        let logs = logs.into_iter().cloned().collect::<Vec<_>>();
+    pub fn from_parts(
+        status: bool,
+        cumulative_gas_used: u64,
+        logs: impl IntoIterator<Item = Log>,
+        tx_type: MorphTxType,
+    ) -> Self {
+        let logs = logs.into_iter().collect::<Vec<_>>();

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/rpc/src/eth/transaction.rs`:
- Around line 241-259: The encoding branch in encode_tx_for_l1_fee currently
treats any presence of tx_env.fee_limit as a signal to build a Morph
transaction; update the condition to only check fee_token_id (i.e., use
tx_env.fee_token_id.unwrap_or_default() > 0) so Morph encoding via
build_morph_tx_from_env(...) and encode_2718(...) happens only when fee_token_id
> 0; otherwise call inner.build_typed_simulate_transaction() and encode_2718 on
that envelope as before. Ensure you remove the "|| tx_env.fee_limit.is_some()"
part and keep error mapping to EthApiError::InvalidParams unchanged.
🧹 Nitpick comments (2)
crates/rpc/Cargo.toml (1)

46-50: Reorganize misplaced dependencies.

tokio and revm are categorized under "Serialization" but are not serialization libraries. Consider moving them to a more appropriate section (e.g., "Async runtime" for tokio, and grouping revm with the other EVM-related dependencies).

♻️ Suggested reorganization
 # Serialization
 serde.workspace = true
 derive_more.workspace = true
-tokio.workspace = true
-revm.workspace = true
+
+# Async runtime
+tokio.workspace = true
+
+# EVM
+revm.workspace = true
crates/rpc/src/eth/transaction.rs (1)

117-152: Consider avoiding redundant clone.

The inner variable is cloned on line 130 for try_into_tx_env, but it's also passed by value to encode_tx_for_l1_fee on line 147. This works but involves two separate uses of inner. If the order were restructured, one clone could potentially be avoided.

♻️ Suggested optimization
     fn try_into_tx_env<Spec>(
         self,
         evm_env: &EvmEnv<Spec, MorphBlockEnv>,
     ) -> Result<MorphTxEnv, Self::Err> {
         let fee_token_id = self.fee_token_id;
         let fee_limit = self.fee_limit;
         let inner = self.inner;
         let access_list = inner.access_list.clone().unwrap_or_default();
 
-        let inner_tx_env = inner
-            .clone()
-            .try_into_tx_env(evm_env)
-            .map_err(EthApiError::from)?;
+        let inner_tx_env = inner.try_into_tx_env(evm_env).map_err(EthApiError::from)?;
 
         let mut tx_env = MorphTxEnv::new(inner_tx_env);
         tx_env.fee_token_id = match fee_token_id {
             Some(id) => Some(
                 u16::try_from(id.to::<u64>())
                     .map_err(|_| EthApiError::InvalidParams("invalid token".to_string()))?,
             ),
             None => None,
         };
         tx_env.fee_limit = fee_limit;
         if tx_env.fee_token_id.unwrap_or_default() > 0 {
             tx_env.inner.tx_type = morph_primitives::MORPH_TX_TYPE_ID;
         }
 
-        let rlp_bytes = encode_tx_for_l1_fee(&tx_env, access_list, evm_env, inner)?;
+        let rlp_bytes = encode_tx_for_l1_fee(&tx_env, access_list, evm_env)?;
 
         tx_env.rlp_bytes = Some(rlp_bytes);
         Ok(tx_env)
     }

This would require encode_tx_for_l1_fee to reconstruct the request from tx_env fields, or accept only what it needs. Given the current structure, the clone may be acceptable for clarity.

Copy link
Contributor

@chengwenxi chengwenxi left a comment

Choose a reason for hiding this comment

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

lgtm

@panos-xyz panos-xyz merged commit 3d56de6 into main Feb 6, 2026
9 checks passed
@panos-xyz panos-xyz deleted the feat/morph-rpc branch February 6, 2026 06:24
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